/* User 2026-05-18: Lumi + dreamer text → Play. Import the webfont here
   so 'Play' resolves wherever this stylesheet loads — the site (which
   also supplies --font-play via next/font) and the porthole standalone. */
@import url('https://fonts.googleapis.com/css2?family=Barlow:wght@400;600;700;800&family=Play:wght@400;700&family=Plus+Jakarta+Sans:wght@600;700;800&display=swap');

:root {
  --stage-w: 1440;
  --stage-h: 1058;
  --design-scale: 0.8333333333;
  --scale: 1;
  --bg-a: #1a1534;
  --bg-b: #2d2557;
  --cyan: #85d5e6;
  --pink: #f486e5;
  --cream: #fcf38f;
  --text: #f4f4f6;
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  min-height: 100%;
  background: var(--bg-a);
  color: var(--text);
  font-family: "Plus Jakarta Sans", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

body {
  overflow-x: hidden;
  overflow-y: auto;
}

button {
  font: inherit;
}

.viewport-shell {
  min-height: 100vh;
  width: 100vw;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  background: linear-gradient(144deg, #1a1534 0%, #2d2557 50%, #1a1534 100%);
}

.scale-host {
  width: min(100vw, 1440px);
  height: calc(1058px * var(--scale));
  position: relative;
  overflow: hidden;
}

.figma-stage {
  position: absolute;
  left: 0;
  top: 0;
  width: 1728px;
  height: 1269px;
  transform: scale(calc(var(--scale) * var(--design-scale)));
  transform-origin: top left;
  overflow: hidden;
  background:
    radial-gradient(390px 255px at 50% 55%, rgba(252, 243, 143, 0.19), rgba(244, 174, 245, 0.16) 50%, transparent 78%),
    linear-gradient(144deg, #1a1534 0%, #2d2557 50%, #1a1534 100%);
  /* User 2026-05-15: "Lumi text / dreamer avatar looks pixelated".
     Force high-quality text + image rendering on the entire scaled
     stage so non-integer transform doesn't cause nearest-neighbor
     blurring. backface-visibility:hidden promotes to a GPU layer
     (smoother sub-pixel resampling). */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: geometricPrecision;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}

/* User 2026-05-16: "I need nice transitions between floors, right now
   it's like a jump cut." When the React phase flips data-screen
   (F0→F1→F2→F2.1) the layout swaps instantly. F0.tsx re-triggers this
   animation on every screen change so the new floor settles in with a
   soft fade instead of a hard cut. opacity-only — the stage's
   transform carries the scale and must not be touched by the anim. */
@keyframes floor-cross-fade {
  0%   { opacity: 0.15; }
  100% { opacity: 1; }
}
/* All images inside the stage (dreamer avatar, agent avatars, Lumi
   orb, monday/Gemini/Grok/Claude logos, adventure logo) — use high-
   quality bicubic resampling instead of nearest-neighbor. */
.figma-stage img {
  image-rendering: -webkit-optimize-contrast;
  image-rendering: high-quality;
}

.glow-main {
  position: absolute;
  left: 686px;
  top: 472px;
  width: 422px;
  height: 270px;
  border-radius: 9999px;
  opacity: 0.92;
  filter: blur(64px);
  background: linear-gradient(123deg, rgba(252, 243, 143, 0.3), rgba(244, 174, 245, 0.2) 50%, transparent);
}

.title {
  /* User 2026-05-15: "make the adventure text 1.5 bigger" → 40 → 60.
     line-height tightened from 96 → 1 so the line-box equals the
     visible glyph height (no excess leading), so the Adventure
     text occupies y=13→~73 instead of y=13→109. This frees space
     above the agents while keeping the visible text bigger. */
  position: absolute;
  left: 0;
  width: 1728px;
  margin: 0;
  text-align: center;
  font-weight: 800;
  font-size: 60px;
  line-height: 1;
  letter-spacing: 0;
}

.title-top {
  /* User 2026-05-15: "less padding between top adventure section and
     logo" → 13 → 4. Then "more padding between bottom hero to agents
     above the agents, more 8px. so agents sections and logo goes down
     8" → 4 → 12. */
  top: 12px;
  left: 50%;
  width: max-content;
  transform: translateX(-50%);
  color: rgba(0, 0, 0, 0);
  background-image: linear-gradient(
    90deg,
    #85d5e6 0%,
    #accbea 14%,
    #ccb1ef 29%,
    #f4aef5 50%,
    #f7ced0 71%,
    #fae1b3 86%,
    #fcf38f 100%
  );
  background-clip: text;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  filter: drop-shadow(0 0 20px rgba(255, 255, 255, 0.3));
}

.title-bottom {
  /* User 2026-05-15: "remove the dream team text" + "remove also this
     line for f1 — business and strategy, and for F2 — branding". The
     bottom title is now hidden on all floors. ::before content rules
     for F1/F2 below still set `content:` but the parent is hidden so
     nothing renders. */
  display: none;
}
/* Belt-and-suspenders: the F1/F2 ::before content also gets 800
   (was overriding to 600 earlier). */
.figma-stage[data-screen]:not([data-screen="F0"]) .title-bottom::before {
  font-weight: 800;
}
/* Belt-and-suspenders: the F1/F2 floor-specific ::before pseudo holds
   the text via `content:`. Pseudos inherit font-weight from the
   element, but some browsers serialize the rule cascade weirdly when
   the parent also has background-clip: text. Pin 600 on the pseudo too. */
.figma-stage[data-screen]:not([data-screen="F0"]) .title-bottom::before {
  font-weight: 600;
}

.team {
  /* User 2026-05-15: "move 2 agents to the right of the logo and two
     agents two the left then move all element below upwards, with 48
     px padding from top row" — agents share the top row with the logo
     at top: 4. Then "more padding between bottom hero to agents above
     the agents, more 8px. so agents sections and logo goes down 8"
     → 4 → 12. Top-row bottom now at 12 + 157 = 169; panels stay at
     top 209 (40 px gap). F1/F2 keep their own .member-monday top:164
     override via the non-F0 cascade below. */
  position: absolute;
  top: 12px;
  width: 150px;
  height: 156px;
  text-align: center;
  color: #edecf0;
  font-weight: 800;
}

/* Figma F0 team avatar absolute x positions (avatar = team-left + 39):
   monday=548, Gemini=745, Grok=925, Claude=1105. */
/* User 2026-05-15: "move two agents to the right of the logo and two
   agents to the left" — 2 agents on each side of the logo (logo
   centered at stage x=832, visible x=599-1065). Avatar centers
   symmetric around logo: 175, 435 | 1229, 1489 (gaps 260, 397, 397, 260). */
/* User 2026-05-18: swapped monday ↔ Gemini on F0 — Gemini now sits on
   the far left, monday next to it. (F1/F2 keep their own overrides.) */
/* User 2026-05-20 (Batch 10): symmetric flank pattern — each partner
   sits over an outer EDGE of the panel below it. See site/ copy for
   the full math. */
.member-monday { left: 64px; }
.member-gemini { left: 276px; }
.member-grok { left: 1302px; }
.member-claude { left: 1514px; }

.team-name,
.team-role {
  /* Shared positioning + size — font-family differs per element below
     (user 2026-05-15: "match only font style for names and roles" —
     names stay sans, roles switch to serif). */
  position: absolute;
  left: -54px;
  width: 259px;
  font-size: 20px;
  line-height: 25px;
}

.team-name {
  top: 0;
  height: 63px;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Sans-serif name — matches Figma agent label */
  /* User 2026-05-18: agent names → Plus Jakarta Sans. */
  font-family: var(--font-plus-jakarta), "Plus Jakarta Sans", system-ui, sans-serif;
  font-weight: 800;
}

.team-role {
  top: 132px;
  /* User 2026-05-15: serif font for roles. Bumped weight 500 → 700
     ("make roles more bold"). */
  /* User 2026-05-18: agent roles → Plus Jakarta Sans. */
  font-family: var(--font-plus-jakarta), "Plus Jakarta Sans", system-ui, sans-serif;
  font-weight: 700;
}

.team-avatar {
  position: absolute;
  left: 39px;
  top: 51px;
  /* User 2026-06-16: was 73x72 (1px off → faint oval). Squared up to
     72x72 with aspect-ratio + border-radius:50% so EVERY cascade path
     (base / non-F0 / mobile reflow / per-agent halo) inherits a true
     circle. The dreamer reported tiles reading as "ovals on the side"
     even after the previous round of mobile overrides; the asymmetry
     came from this base rule leaking through when override
     specificity couldn't reach. */
  width: 72px;
  height: 72px;
  aspect-ratio: 1 / 1;
  border-radius: 50%;
  display: grid;
  place-items: center;
  /* User 2026-05-17: neumorphic surface — soft light gradient. The
     per-agent coloured glow (below) stays as the outer halo; the
     inset highlight/shadow on each glow rule adds the extruded depth. */
  background: linear-gradient(145deg, #ffffff, #e6e6ee);
  overflow: hidden;
}

.team-avatar img {
  width: 48px;
  height: 48px;
  display: block;
  object-fit: contain;
}

.panel {
  /* User 2026-05-15: agents in row with logo. Panels shifted up
     cumulative −14 → 195. F1/F2 panels stay at 282 via non-F0
     override below.
     User 2026-05-17: neumorphism — soft-extruded translucent surface,
     borderless. Outer light/dark shadows lift it off the stage; the
     inset highlight/shadow give the soft-plastic curvature. */
  position: absolute;
  top: 195px;
  width: 362px;
  height: 670px;
  border-radius: 20px;
  background: linear-gradient(150deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.015));
  box-shadow:
    -8px -8px 20px rgba(255, 255, 255, 0.045),
    12px 12px 30px rgba(0, 0, 0, 0.55),
    inset 2px 2px 5px rgba(255, 255, 255, 0.10),
    inset -3px -3px 8px rgba(0, 0, 0, 0.32);
  overflow: hidden;
}

.status-panel { left: 64px; }
.inputs-panel { left: 1302px; }

.panel-head {
  position: absolute;
  left: -2px;
  top: 0;
  width: 362px;
  height: 48px;
  display: grid;
  place-items: center;
  border-bottom: 1px solid rgba(255, 255, 255, 0.2);
  color: rgba(255, 255, 255, 0.95);
  /* User 2026-05-18: panel headers (Status / Outputs) → Barlow. */
  font-family: var(--font-barlow), "Barlow", system-ui, sans-serif;
  font-size: 20px;
  font-weight: 800;
  line-height: 30px;
}

.status-head {
  background: linear-gradient(90deg, rgba(133, 213, 230, 0.4), rgba(133, 213, 230, 0.3), rgba(133, 213, 230, 0.4));
}

.inputs-head {
  background: linear-gradient(90deg, rgba(248, 134, 229, 0.4), rgba(248, 134, 229, 0.3), rgba(248, 134, 229, 0.4));
}

.status-row {
  position: absolute;
  left: 16px;
  width: 330px;
  height: 64px;
  display: flex;
  align-items: center;
  padding: 0 13px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.05);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
  /* User 2026-05-20 (Batch 9): status-row text → Poppins (was Barlow).
     The DxD body voice everywhere on the homepage is Poppins. */
  font-family: var(--font-poppins), "Poppins", system-ui, sans-serif;
  font-size: 18px;
  font-weight: 600;
  line-height: 24px;
}

.active-row {
  top: 80px;
  justify-content: space-between;
}

/* Status panel rows: Active, Floor, Venture ID.
   Score row removed (2026-05-22 — score is internal-only).
   2026-06-08: id-row + mvp shifted up 88px to close the gap. */
.floor-row { top: 168px; }
.score-row { top: 256px; }
.id-row { top: 256px; }

/* MVP progress strip — restored 2026-05-18 (user). Lives in the Status
   panel on every floor F0→Roof; the fill reflects REAL pipeline
   progress (intake → F1 → F2 → build → roof), driven inline by React. */
.status-mvp {
  /* bottom:32px pins MVP to bottom of the status panel.
     Matches the effective top padding above Active row (~32px).
     Status row cards centered (left:16px). */
  position: absolute;
  left: 16px;
  width: 330px;
  bottom: 32px;
  display: flex;
  flex-direction: column;
}
.status-mvp-label {
  margin: 0 0 10px;
  text-align: center;
  font-size: 16px;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.95);
}
.status-mvp-frame {
  /* Figma F3 node 3:83/3:84 — the FRAME around the bar: a rounded-20 box,
     translucent cyan→pink→cream gradient (0.2), 2px white-0.3 border, soft
     drop-shadow. Flex-centres the bar inside (Figma 3:84 padding 20px 0).
     User 2026-05-23 "progress bar +2px padding": 64 → 68 so the track gets
     2px more breathing room top and bottom (gaps ~21 → ~23px). */
  height: 68px;
  box-sizing: border-box;
  padding: 0 19px;
  display: flex;
  align-items: center;
  border-radius: 20px;
  background: linear-gradient(
    90deg,
    rgba(133, 213, 230, 0.2) 0%,
    rgba(244, 174, 245, 0.2) 50%,
    rgba(252, 243, 143, 0.2) 100%
  );
  border: 2px solid rgba(255, 255, 255, 0.3);
  box-shadow: 0 8px 32px rgba(133, 213, 230, 0.3);
}
.status-mvp-track {
  /* Figma F3 node 3:82 — the recessed progress bar INSIDE the frame:
     rounded-32, translucent white, 1px border, inset shadow.
     overflow visible so the marble logo can ride the fill edge. */
  width: 100%;
  height: 28px;
  border-radius: 32px;
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.3);
  box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.1);
  overflow: visible;
}
.status-mvp-fill {
  /* The progressed portion — the signature gradient at full opacity. */
  position: relative;
  height: 100%;
  border-radius: 32px;
  background: linear-gradient(90deg, #85d5e6 0%, #f4aef5 50%, #fcf38f 100%);
  transition: width 0.8s ease;
}
.status-mvp-marble {
  /* The Dream x Destiny marble logo riding the fill's leading edge.
     User 2026-06-26: was right:-13 (half-marble juts OUT past the
     fill's right edge → looked like the marble was slipping ahead
     of the progress). Now right:0 — marble sits fully INSIDE the
     fill with its right edge aligned to the fill's right edge.
     (Mobile already used right:0 via F0.tsx; this matches it.)
     The transition is killed where it matters in F0.tsx so the
     marble snaps to its anchor on phase changes rather than gliding. */
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 26px;
  height: 26px;
  object-fit: contain;
  transition: none;
}
/* User 2026-05-19: before the vision is accepted (data-prestart) the
   bar sits EMPTY — the frame and recessed track stay visible (they ARE
   the progress-bar UI), the fill is 0% so there is no gradient streak,
   and the marble simply rests tucked at the very start of the track.
   (Earlier this stripped the frame + track entirely, which read as a
   broken/missing UI — a lone marble under the label.) */
.status-mvp[data-prestart='true'] .status-mvp-fill {
  background: transparent;
}
.status-mvp[data-prestart='true'] .status-mvp-marble {
  right: auto;
  left: 0;
}

/* All status rows: split label (left, muted) from value (right, bright)
   so they don't stack on top of each other. .active-row already had
   justify-content space-between; this generalises that pattern. */
.status-row {
  justify-content: space-between;
}
.status-label {
  /* User 2026-05-15: "text in the status section for tabs is off."
     Per Figma F0 nodes 63:245/278/280: labels are Plus Jakarta Sans
     SemiBold 16px / leading 24, plain case (no uppercase), #f4f4f6.
     Was: 14px / 500 / uppercase / muted. */
  color: #f4f4f6;
  font-weight: 600;
  /* User 2026-05-18: status text +2px → 18px. */
  font-size: 18px;
  letter-spacing: 0;
  text-transform: none;
}
.status-value {
  /* User 2026-05-15: "remove the dash and container around — for both
     Vision Score and Venture ID. This information will just appear
     there later, no style." Plain text only — no pill background,
     no inset shadow, no letter-spacing tricks. The JSX no longer
     renders the "—" placeholder either; the span only appears when
     a real value lands. */
  color: #f4f4f6;
  font-weight: 600;
  /* User 2026-05-18: status text +2px → 18px. */
  font-size: 18px;
  letter-spacing: 0;
  white-space: nowrap;
}

.switch {
  /* User 2026-05-14: Neumorphism (matches .next-button + .add-button).
     Track appears "carved into" the dark panel via INSET shadows
     (dark BR depth + light TL highlight = pressed-in look), while the
     thumb is "raised" via OUTSET shadows that mirror the button
     neumorphism. Off = neutral grey; .on swaps the track tint to
     green #7feba0 per Figma F1/F2/F2.1 state. */
  width: 54px;
  height: 24px;
  padding: 3px;
  border-radius: 50px;
  background: linear-gradient(145deg, #2a214c, #3a2e64);
  box-shadow:
    inset 3px 3px 6px rgba(0, 0, 0, 0.55),
    inset -2px -2px 5px rgba(255, 255, 255, 0.10);
  display: flex;
  align-items: center;
}

.switch.on {
  background: linear-gradient(145deg, #5cd687, #87f3a8);
  box-shadow:
    inset 3px 3px 6px rgba(0, 0, 0, 0.40),
    inset -2px -2px 5px rgba(255, 255, 255, 0.25);
}
.switch.on span {
  margin-left: auto;
}

.switch span {
  /* Thumb — raised via OUTSET shadow language used on the +/next
     buttons (light TL highlight + dark BR depth + inner bevel). */
  display: block;
  width: 19px;
  height: 19px;
  border-radius: 999px;
  background: linear-gradient(145deg, #ffffff, #d6d6d6);
  box-shadow:
    -2px -2px 5px rgba(255, 255, 255, 0.18),
    3px 3px 7px rgba(0, 0, 0, 0.55),
    inset 1px 1px 2px rgba(255, 255, 255, 0.85),
    inset -1px -1px 2px rgba(0, 0, 0, 0.18);
}

/* User 2026-05-15: Figma F1/F2/F3/Roof all show the Active toggle in
   its ON state (green track, thumb right) — being on any non-F0 floor
   means the adventure is running. Force the visual ON state via CSS
   so the toggle reflects reality even when the polling API returns a
   stale "off" state (e.g., on ?id=draft preview routes). */
.figma-stage[data-screen]:not([data-screen="F0"]) .switch {
  background: linear-gradient(145deg, #5cd687, #87f3a8);
  box-shadow:
    inset 3px 3px 6px rgba(0, 0, 0, 0.40),
    inset -2px -2px 5px rgba(255, 255, 255, 0.25);
}
.figma-stage[data-screen]:not([data-screen="F0"]) .switch span {
  margin-left: auto;
}

.glass-card {
  /* User 2026-05-17: neumorphism — the frosted-glass surface (backdrop
     blur kept) now reads as a soft-extruded panel: outer light/dark
     shadows lift it off the dark stage, inset highlight/shadow give the
     curvature, borderless. Glass + neumorphism is the locked F0
     aesthetic. The blur keeps it from vanishing against the gradient. */
  position: absolute;
  left: 518px;
  width: 692px;
  height: 310px;
  border-radius: 20px;
  background: linear-gradient(150deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.05));
  box-shadow:
    -8px -8px 20px rgba(255, 255, 255, 0.05),
    12px 12px 30px rgba(0, 0, 0, 0.50),
    inset 2px 2px 5px rgba(255, 255, 255, 0.18),
    inset -3px -3px 8px rgba(0, 0, 0, 0.22);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
}

.prompt-card {
  /* Matches .panel — cumulative −14 from below-agents shifts → 195. */
  top: 195px;
  height: 310px;
}

.prompt-line {
  position: absolute;
  left: 27px;
  margin: 0 0 12px;
  color: rgba(255, 255, 255, 0.93);
  font-size: 17px;
  line-height: 24px;
  letter-spacing: 0;
  white-space: nowrap;
}

.prompt-hey {
  top: 28px;
}

.prompt-build {
  top: 58px;
}

.prompt-exchange {
  top: 88px;
}

.prompt-cash {
  left: 50px;
  top: 136px;
}

.prompt-free {
  left: 50px;
  top: 174px;
}

.prompt-equity {
  left: 50px;
  top: 212px;
}

.prompt-question {
  top: 250px;
}

.dot {
  display: inline-flex;
  flex-shrink: 0;
  width: 14px;
  height: 14px;
  border-radius: 999px;
  margin-top: 4px;
}

.dot-pink {
  background: linear-gradient(90deg, #f886e5, #f4aef5);
  box-shadow: 0 0 10px rgba(248, 134, 229, 0.5);
}

.dot-green {
  background: linear-gradient(90deg, #8dfab0, #9ff7b2);
  box-shadow: 0 0 10px rgba(141, 250, 176, 0.5);
}

.dot-blue {
  background: linear-gradient(90deg, #86e9f8, #85d5e6);
  box-shadow: 0 0 10px rgba(134, 233, 248, 0.5);
}

/* List items that contain a .dot span sit in a flex row so the dot
   stays left of the text on the same line. */
li:has(> .dot) {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  list-style: none;
}

.lower-card {
  /* Panels at top:195 + height:670 → bottom 865. Lower-card
     height 310 → top 555 (bottom-aligned with panels). */
  top: 555px;
}

.ai-avatar {
  /* User 2026-05-15 iter: scaled DOWN 20% (140→112), now scaled UP
     20% (112→134) per "make Lumi bigger 20%". Plus "Lumi avatar need
     to go down 5px" → top 586 → 591. Then shifted up 73 with rest of
     below-row → 518. Then cumulative −9 with below-agents → 509.
     Then "fix lumi position … up, to be about the middle of the
     bottom framer box" → 470 (straddles lower-card top edge).
     Then another −5 with below-agents → 465.
     MCP-measured 2026-05-15: PNG (1024×1536) only fills y=404→1227,
     so 26 % empty padding on top + 20 % on bottom. With container at
     465, the VISIBLE character bottom landed at design y ≈ 609 (53
     px below lower-card top 555) — Lumi looked floating mid-card.
     User: "keep size, move her HIGHER … with only her feet touching
     the top edge". Visible bottom must equal lower-card top (555):
       container_top + 180·(1227/1536) = 555
       container_top = 555 − 143.9 = 411
     So top: 465 → 411. Now her visible character sits FULLY above
     the lower-card, with only her feet on the seam. */
  position: absolute;
  /* User 2026-05-16: "center Lumi's avatar" — was left 817 (center
     884, 20px right of the stage centre). 134-wide avatar centred on
     the stage centre 864 → left = 864 − 67 = 797. Matches the
     dreamer-ring, which is already centred. */
  left: 797px;
  top: 411px;
  width: 134px;
  height: 180px;
  overflow: hidden;
  z-index: 50;
}

.ai-avatar img {
  /* Image scaled to match the up-sized container (134×180). */
  position: absolute;
  left: 0;
  top: 0;
  width: 120px;
  height: 180px;
  display: block;
  object-fit: contain;
  pointer-events: none;
}

.tiny-trail {
  position: absolute;
  left: 27px;
  top: 20px;
  width: 42px;
  height: 24px;
  color: #fff;
  font-size: 16px;
  font-weight: 700;
  line-height: 24px;
}

.tiny-trail span {
  position: absolute;
  left: 0;
  top: 0;
}

.tiny-trail i {
  position: absolute;
  left: 32px;
  top: 0;
  width: 2px;
  height: 15px;
  background: linear-gradient(180deg, #85d5e6, #bdd9e0);
  box-shadow: 0 0 10px rgba(133, 213, 230, 0.6);
}

.tiny-trail b {
  position: absolute;
  left: 29px;
  top: 13px;
  width: 8px;
  height: 8px;
  border: 2px solid #85d5e6;
  border-radius: 999px;
  background: #fff;
  box-shadow: 0 0 15px rgba(133, 213, 230, 0.8);
}

.add-button {
  /* Figma F0 node 63:299 / F2.1 node 2:18: button at panel-relative
     (163, 600). 51×51 cyan→pink→yellow gradient circle with pink-glow
     neumorphism — sibling of .next-button.

     User 2026-06-18: removed the line-height + padding-top + font-size
     fight that compensated for the asymmetric '+' text glyph. The
     button now wraps an SVG `+` (geometrically pure), so flex
     align-items + justify-content: center are enough. Rule: every
     icon-in-a-circle control on the site uses SVG geometry, not a
     font glyph. */
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  top: 587px;
  width: 51px;
  height: 51px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #85d5e6, #f4aef5 58%, #fcf38f);
  color: #2b1748;
  box-shadow:
    0 8px 20px -4px rgba(244, 174, 245, 0.45),
    5px 5px 13px rgba(0, 0, 0, 0.40),
    inset 1.5px 1.5px 3px rgba(255, 255, 255, 0.45),
    inset -2px -2px 4px rgba(0, 0, 0, 0.22);
}

/* Outputs-panel download button. Stays greyed until /api/adventure/:id/outputs
   reports download_locked:false (F2 PASS / manifest ARCHIVED).
   Figma F2.1 node 2:18: panel-relative (157, 599), 48×46. The + button
   that shares this class on F0/F1/F2 cooking sits at (163, 600) per
   F0 node 63:299 — override here for the download variant only. */
.download-button {
  /* User 2026-05-16: "there is no button, it's just a square" — the
     rule set position/opacity/colour but NO background, border, radius
     or size, so the browser rendered the native <button> default: a
     grey square. Now styled with the SAME neumorphism as .add-button
     (its sibling in this panel slot) — 48×46 per Figma F2.1 node 2:18,
     translucent-light surface extruded from the dark panel via layered
     light/dark shadows + inset highlights. */
  /* User 2026-05-18: identical to .next-button — a 51×51 cyan→pink→
     yellow gradient circle with the same neumorphism.
     User 2026-05-19: the arrow inside points DOWN (it's a download),
     in the same #59534D ink as the next-button arrow. */
  position: absolute;
  left: 156px;
  top: 597px;
  width: 51px;
  height: 51px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #85d5e6, #f4aef5 58%, #fcf38f);
  color: #2b1748;
  box-shadow:
    0 8px 20px -4px rgba(244, 174, 245, 0.45),
    5px 5px 13px rgba(0, 0, 0, 0.40),
    inset 1.5px 1.5px 3px rgba(255, 255, 255, 0.45),
    inset -2px -2px 4px rgba(0, 0, 0, 0.22);
  opacity: 0.45;
  cursor: not-allowed;
  transition: transform 0.12s ease, filter 0.15s ease, opacity 0.15s ease;
}

.download-button:not([disabled]) {
  opacity: 1;
  cursor: pointer;
}

.download-button:not([disabled]):hover {
  transform: scale(1.06);
}

.download-button:not([disabled]):active {
  transform: translateY(1px);
}

.download-button img,
.download-button svg {
  display: block;
  width: 20px;
  height: 20px;
}

/* ─── Outputs panel 5-PDF grid (F2.1 redesign 2026-05-13, Figma
   DBykjAWT5vLNHaKMq4kQF7 node 9:44) ────────────────────────────────
   Five rows: agent BADGE on the left (panel-x 64) + PDF icon on the
   right (panel-x 249). Each row paired by Y. Stride 98px.
   PDFs are smaller now (53×50 vs the previous 71×71). */
.outputs-grid {
  /* Hide the legacy upload wrap if any sneaks in. */
}

.outputs-grid .inputs-upload-wrap {
  display: none;
}

.outputs-pdf-slot {
  /* F2.1 redesign: right-side PDF column. Per Figma F2.1 (2026-05-15),
     each slot recolors the shared pdf-icon.svg to its agent's accent —
     pink/green/purple/cyan/red. Rendered via CSS mask so a single SVG
     asset drives all five tints. */
  position: absolute;
  left: 249px;
  width: 53px;
  height: 50px;
  cursor: pointer;
  -webkit-mask: url(/porthole/v2/assets/pdf-icon.svg) center / contain no-repeat;
          mask: url(/porthole/v2/assets/pdf-icon.svg) center / contain no-repeat;
  background-color: var(--pdf-color, #EB5757);
  /* User 2026-05-18: the PDF icons read as pale — give each one a glow
     in its own agent colour so they look lit, not washed out. */
  filter: drop-shadow(0 0 7px var(--pdf-color, #EB5757));
  transition: transform 0.18s ease, filter 0.18s ease, opacity 0.18s ease;
}

/* User 2026-06-15: Figma F2.1 shows ALL 5 PDF icons in the same
   red/orange — the variance per row lives on the AVATAR, not the PDF.
   Earlier per-kind tints (FFD080, A0F5BE, E8D0FF, A8E8F5, FFB0B0)
   drifted from intent. Single uniform red/orange across every row. */
.outputs-pdf-slot { --pdf-color: #EB5757; }

/* User 2026-06-09: flex-row layout for the new outputs grid (replaces
   the absolute-positioned 5-slot redesign). Each row is a horizontal
   strip with badge | PDF tile | download anchor. Overrides the
   default .outputs-pdf-slot { position: absolute; left: 249px } so
   the icon flows in the flex row instead of jumping to design-x 249. */
.outputs-rows {
  display: flex;
  flex-direction: column;
  /* User 2026-06-16: match the Status panel cadence exactly.
     Status panel: .panel-head is absolute (48px tall, top:0) and
     .status-row starts at top: 80px → 32px clear above the first
     row. Rows step by 88px (top: 80 → 168 → 256) → 24px gap between.
     PDF panel mirrors: padding-top = 48 (header) + 32 (clear) = 80px,
     24px row gap, 16px sides (mirrors .status-row left: 16). */
  gap: 24px;
  padding: 80px 16px 20px;
}
/* User 2026-06-15: the legacy red PDF-icon tile lives between the
   avatar and the download chip in the OLD layout. The premium row
   redesign dropped it (the avatar is the agent identifier + the row
   is itself the PDF link). Hide it whenever the new label is present
   so any cached SLOTS or third-party callers still rendering the
   old <span class="outputs-pdf-slot" /> don't fall through with a
   visible red splash. */
.outputs-row .outputs-row-label ~ .outputs-pdf-slot,
.outputs-row .outputs-pdf-slot:has(~ .outputs-row-label) {
  display: none;
}
/* ─────────────────────────────────────────────────────────────────
   Outputs row — premium redesign (User 2026-06-15).
   Prior layout: [avatar | bright-red PDF clipart | tiny grey ring]
   centered, no label. Read as cluttered + unlabelled.

   New layout: the whole row is one <a> link (single click target,
   full hit area). Avatar 80 px on the left, name+role stack mid,
   refined cyan→pink→cream gradient download chip on the right.
   No more PDF clipart — the row IS the PDF; the avatar identifies
   the agent; the chip indicates the action.

   Hover lifts the whole row 1 px with a soft cyan glow + tints the
   download chip. Accessible focus state for keyboard tab. */
.outputs-row {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 16px;
  padding: 10px 14px;
  border-radius: 18px;
  background: rgba(255, 255, 255, 0.025);
  border: 1px solid rgba(255, 255, 255, 0.06);
  text-decoration: none;
  color: inherit;
  transition: background 0.18s ease, border-color 0.18s ease, transform 0.18s ease, box-shadow 0.18s ease, opacity 0.18s ease;
}
/* User 2026-06-15 (progressive unlock pivot): is-locked rows render
   all the chrome (avatar + label + button) but with reduced opacity
   and no hover affordance. The dreamer sees the FULL future set
   from F1 and watches it light up floor by floor. */
.outputs-row.is-locked {
  cursor: not-allowed;
  opacity: 0.45;
}
.outputs-row.is-locked .outputs-avatar { filter: grayscale(0.4); }
.outputs-row.is-ready { cursor: pointer; }
.outputs-row.is-ready:hover {
  background: rgba(133, 213, 230, 0.06);
  border-color: rgba(133, 213, 230, 0.32);
  transform: translateY(-1px);
  box-shadow: 0 6px 18px -8px rgba(133, 213, 230, 0.45);
}
.outputs-row.is-ready:focus-visible {
  outline: none;
  border-color: rgba(133, 213, 230, 0.55);
  box-shadow: 0 0 0 3px rgba(133, 213, 230, 0.22);
}
.outputs-row.is-ready:active { transform: translateY(0); }

.outputs-row .outputs-avatar { flex-shrink: 0; }

/* Label stack — agent name (bold, bright) over role (muted, smaller).
   Flex 1 takes the available space so the chip sits flush right. */
.outputs-row-label {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  line-height: 1.2;
}
/* User 2026-06-15: text grew from 14/12 → 18/15 to MATCH the weight
   of the Adventure floor's .team-name/.team-role labels (20/20px at
   800/700). PDF row labels read as a peer to the agent row above,
   not a footnote. Avatar already matches (80 px vs .team-avatar 73). */
.outputs-row-name {
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 800;
  font-size: 18px;
  color: #f4f1f8;
  letter-spacing: 0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.outputs-row-role {
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 600;
  font-size: 15px;
  color: rgba(254, 251, 251, 0.7);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* User 2026-06-17: PDF row labels render on ONE line as
   "<Name> - <Role>" (e.g. "Dream x Destiny - Confirmation"). The
   prior 2-row stack wrapped to 3 lines on the Adventure column,
   which read as a footnote rather than the headline. */
.outputs-row-label--inline {
  flex: 1;
  min-width: 0;
  flex-direction: row !important;
  align-items: center;
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 700;
  font-size: 18px;
  color: #f4f1f8;
  letter-spacing: 0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  line-height: 1.2;
}

/* Refined download chip. User 2026-06-15 (progressive unlock):
   ready rows ALREADY show the full .next-button gradient at rest —
   the chip IS the action affordance and reads as a real CTA. Locked
   rows keep the soft cyan glass + outline so the dreamer sees the
   chip exists but doesn't read as actionable. Hover only fires on
   ready rows (locked rows have pointer-events: none on the chip,
   handled by the row-level .is-locked opacity + cursor). */
.outputs-dl {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 999px;
  text-decoration: none;
  flex-shrink: 0;
  transition: background 0.18s ease, border-color 0.18s ease, transform 0.16s ease, box-shadow 0.18s ease;
}
.outputs-row.is-locked .outputs-dl {
  background: rgba(133, 213, 230, 0.10);
  border: 1px solid rgba(133, 213, 230, 0.32);
  color: #85d5e6;
}
.outputs-row.is-ready .outputs-dl {
  background: linear-gradient(135deg, #85d5e6, #f4aef5 58%, #fcf38f);
  border: 1px solid transparent;
  color: #2b1748;
  box-shadow:
    0 4px 10px -4px rgba(244, 174, 245, 0.40),
    inset 1px 1px 2px rgba(255, 255, 255, 0.40);
}
.outputs-row.is-ready:hover .outputs-dl {
  background: linear-gradient(135deg, #85d5e6, #f4aef5 58%, #fcf38f);
  border-color: transparent;
  color: #2b1748;
  box-shadow:
    0 6px 14px -4px rgba(244, 174, 245, 0.45),
    inset 1px 1px 2px rgba(255, 255, 255, 0.45);
}
.outputs-dl-arrow {
  width: 14px;
  height: 14px;
  transform: rotate(90deg);
}
/* Arrow color follows the row state: dark on the gradient (ready),
   cyan on the glass outline (locked). */
.outputs-row.is-ready .outputs-dl-arrow path { fill: #2b1748; }
.outputs-row.is-locked .outputs-dl-arrow path { fill: #85d5e6; }
.outputs-dl-all {
  margin-top: 12px;
  align-self: stretch;
  padding: 12px 22px;
  border-radius: 9999px;
  border: 0;
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.04em;
  /* User 2026-06-15: match the .next-button color stack exactly so
     the dreamer-facing CTA pair (Download all + Next) reads as ONE
     palette, not two near-misses.
     - gradient: 135deg, 58% pink stop (was 90deg, 50% pink)
     - text:    #2b1748 (was #0a1230 - off by 8 hue notches)
     - shadow:  same pink halo + dark drop + inset highlight/shadow
     - dropped the layered white-overlay scrim (Next doesn't have it). */
  color: #2b1748;
  background: linear-gradient(135deg, #85d5e6, #f4aef5 58%, #fcf38f);
  box-shadow:
    0 8px 20px -4px rgba(244, 174, 245, 0.45),
    5px 5px 13px rgba(0, 0, 0, 0.40),
    inset 1.5px 1.5px 3px rgba(255, 255, 255, 0.45),
    inset -2px -2px 4px rgba(0, 0, 0, 0.22);
  cursor: pointer;
  transition: transform 0.12s ease, box-shadow 0.16s ease;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
.outputs-dl-all:hover { transform: translateY(-1px); }
/* User 2026-06-15: leading download glyph - a downward-pointing arrow
   so the button reads as "download" at a glance, parity with .next-
   button's right arrow as a visual cue. */
.outputs-dl-all-icon {
  display: inline-block;
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}

.outputs-pdf-slot img {
  /* The icon body now comes from the CSS mask — hide the inline <img>
     to avoid double-rendering. Kept in the DOM for alt/a11y. */
  display: none;
}

.outputs-pdf-slot.is-locked {
  /* User 2026-05-18: locked still glows (keeps the base drop-shadow) —
     just a touch dimmer than ready so the gate still reads. Was 0.45
     (too pale). */
  cursor: not-allowed;
  opacity: 0.78;
}

.outputs-pdf-slot.is-ready:hover {
  transform: scale(1.06);
  filter: drop-shadow(0 0 14px var(--pdf-color, #EB5757));
}

/* User 2026-05-19: F3's Outputs panel is display-only — the 5 venture
   PDFs are saved to the dreamer's personal zone, never downloaded here.
   The slot keeps its lit agent colour but is not interactive. */
.outputs-pdf-slot.is-personal-zone {
  cursor: default;
}
.outputs-pdf-slot.is-personal-zone:hover {
  transform: none;
  filter: drop-shadow(0 0 7px var(--pdf-color, #EB5757));
}

/* Agent badges — left column of the Outputs panel. Lumi (slot 1) is
   the full orb portrait (88×135); the other 4 are circular logos
   (54×54), confirmation seal is slightly larger (60×60). Clean look —
   no extra background rings; the logos already have their own bg.
   Default left:62 matches Figma ellipse 181 (Gemini) at (62, 274).
   Lumi (left:47) and seal (left:61) override below. */
.outputs-agent-badge {
  position: absolute;
  left: 62px;
  width: 54px;
  height: 54px;
  display: grid;
  place-items: center;
  pointer-events: none;
}

.outputs-agent-badge img {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 999px;
  object-fit: contain;
}

.outputs-agent-badge--lumi {
  /* 80% of the 54px agent circles = ~43px. */
  left: 65px;
  width: 43px;
  height: 43px;
  overflow: hidden;
  border-radius: 999px;
}

.outputs-agent-badge--lumi img {
  border-radius: 999px;
  object-fit: cover;
}

.outputs-agent-badge--logo {
  /* F2.1 logo badge — a 54×54 disc holding the vendor mark (~32×32).
     User 2026-05-17: match the floor agent avatars exactly — the same
     neumorphic light-gradient surface, the same inset highlight/shadow,
     and the same per-agent coloured halo (set by [data-agent] below).
     A logo badge with no data-agent match keeps the neutral white halo
     from `.team .team-avatar`. */
  background: linear-gradient(145deg, #ffffff, #e6e6ee);
  border-radius: 999px;
  box-shadow:
    0 0 14px rgba(255, 255, 255, 0.22),
    inset 2.5px 2.5px 5px rgba(255, 255, 255, 0.70),
    inset -3px -3px 6px rgba(0, 0, 0, 0.12);
}

/* Per-agent halo — identical rgba/blur to the floor `.team-avatar`
   per-agent rules so the Outputs logos read as the same component as
   the agents on F0/F1/F2 (user 2026-05-17). */
.outputs-agent-badge--logo[data-agent="monday"] {
  box-shadow:
    0 0 16px rgba(133, 213, 230, 0.50),
    0 0 32px rgba(133, 213, 230, 0.24),
    inset 2.5px 2.5px 5px rgba(255, 255, 255, 0.70),
    inset -3px -3px 6px rgba(0, 0, 0, 0.12);
}

.outputs-agent-badge--logo[data-agent="Gemini"] {
  box-shadow:
    0 0 16px rgba(244, 174, 245, 0.52),
    0 0 32px rgba(244, 174, 245, 0.26),
    inset 2.5px 2.5px 5px rgba(255, 255, 255, 0.70),
    inset -3px -3px 6px rgba(0, 0, 0, 0.12);
}

.outputs-agent-badge--logo[data-agent="Grok"] {
  box-shadow:
    0 0 16px rgba(252, 243, 143, 0.46),
    0 0 32px rgba(252, 243, 143, 0.22),
    inset 2.5px 2.5px 5px rgba(255, 255, 255, 0.70),
    inset -3px -3px 6px rgba(0, 0, 0, 0.12);
}

.outputs-agent-badge--logo img {
  /* Inner mark sits centered inside the white disc per Figma ellipse
     181 + ellipse 176/162 pairing (32px mark on 54px disc). */
  width: 32px;
  height: 32px;
  border-radius: 0;
  object-fit: contain;
}

.outputs-agent-badge--seal {
  background: transparent;
  border-radius: 0;
  box-shadow: none;
  overflow: visible;
}

.outputs-agent-badge--seal img {
  width: 100%;
  height: 100%;
  border-radius: 0;
  object-fit: contain;
}

.outputs-agent-badge--seal svg {
  /* Fallback SVG checkmark (only used when no badge.src is supplied);
     kept for safety until every adventure ships a real dxd asset. */
  display: block;
  width: 28px;
  height: 28px;
  filter: drop-shadow(0 0 6px rgba(252, 243, 143, 0.45));
}

/* Outputs panel avatar wrappers (F3 / f2_pass view).
   User 2026-06-15: bumped 54 → 80 px - "PDF avatars + Lumi too small"
   feedback. Matches the agent-row avatar weight (.team-avatar 73x72)
   so the row chip-cluster reads as proper portrait icons, not
   pinhead dots. */
/* User 2026-06-16: sized to match the F1/F2 agents-stripe weight —
   .team-avatar 72×72 with the inner logo at 47×47 (~65% fill). The
   previous build set the logo at 100% which made every PDF row
   visually heavier than the agent stripe even though the circle
   was the same diameter. Now circle + logo BOTH match the stripe.
   Seal (Dream × Destiny wordmark) stays transparent — circle clip
   would crop the wordmark. */
.outputs-avatar {
  position: relative;
  width: 72px;
  height: 72px;
  border-radius: 999px;
  overflow: hidden;
  display: grid;
  place-items: center;
  background: linear-gradient(145deg, #ffffff, #e6e6ee);
  flex-shrink: 0;
}
.outputs-avatar img {
  display: block;
  width: 47px;
  height: 47px;
  object-fit: contain;
  border-radius: 0;
}
/* User 2026-06-16: Lumi PDF row dropped from the panel — her PDF
   ships in the Roof venture-suite bundle. The .outputs-avatar--lumi
   rule is retained for safety (any stale render falls through to
   the bare circle) but the dreamer never sees it. */
.outputs-avatar--lumi {
  background: transparent;
  border-radius: 0;
  overflow: visible;
  box-shadow: none;
}
.outputs-avatar--lumi img {
  width: 72px;
  height: 72px;
  object-fit: contain;
  border-radius: 0;
}
/* Dream × Destiny seal — the cream/navy wordmark shouldn't sit in
   a white circle (the seal has its own brand container; doubling
   up reads as clumsy). Drop the circle clip and the white bg.
   User 2026-06-16: seal sized to match the agent-stripe weight
   (72px slot) so it sits at the same visual altitude as Gemini /
   Grok / monday logos in the rows below. */
.outputs-avatar--seal {
  background: transparent;
  border-radius: 0;
  overflow: visible;
  box-shadow: none;
}
.outputs-avatar--seal img {
  width: 72px;
  height: 72px;
  object-fit: contain;
  border-radius: 0;
}

/* Lumi typing-cursor: cyan vertical line + white-cyan dot, positioned
   right after the "..." placeholder inside the lower (dreamer) card.
   Mimics a static text caret so the dreamer knows where their typing
   will land. Hidden once they start typing (see figma-runtime.js). */
.lumi-typing-cursor {
  /* User 2026-06-18: decorative cursor RETIRED — it was rendering
     ALONGSIDE the native cyan caret (.figma-textarea already sets
     caret-color: #85d5e6), giving a visible double-cursor that
     drifted "V away from the last letter" by design (intentional
     padding gap, but reads as broken because the native caret sits
     flush with the text). Hiding the overlay leaves the native
     caret — geometrically correct, browser-positioned, blinks like
     a normal text input. Display: none !important keeps the JSX +
     positioning useEffect harmlessly inert; all sibling CSS rules
     that reference .lumi-typing-cursor (e.g. the focus pulse, the
     non-F0 hide rule) still match their selectors with no effect,
     so no other CSS breaks. Rule: text inputs never carry both a
     native caret and a decorative overlay. */
  display: none !important;
  /* Figma F0 node 63:288: line + dot caret, positioned past the "..."
     placeholder with a real text-cursor padding.

     User 2026-05-14: final dial-in.
       - Placeholder ends at design x≈579 (with trailing letter-spacing).
       - Caret container is 8px wide and holds two visible glyphs:
           i (vertical line) at internal left:3 → 2px wide
           b (round dot)     at internal left:0 → 8px wide
         So the dot's LEFT edge equals the container's left edge.
       - 590 (too far) → 578 (dot overlapped last placeholder dot by
         ~1px) → 584: gives 5 design px clear space from placeholder
         end to the cursor-dot left edge — the "nice padding next to
         it like a real mouse cursor relative to text" feel. */
  /* User 2026-05-15 (final): placeholder tightened to "···" at 17px /
     1px letter-spacing = ~22 design px wide. Textarea origin at design
     x=546, placeholder ends at ~568. left:575 puts the caret-dot 7
     design px past the last placeholder dot — tight real-text-cursor
     gap, no overlap. */
  position: absolute;
  left: 575px;
  /* User 2026-05-15: cursor centered on the "···" placeholder line
     (lower-card.top + 21). After −5 below-agents shift lower-card
     top is 555 → placeholder at 576 → cursor at 578.
     User 2026-06-15 (MCP probe): empty-state cursor rendered 14
     viewport px ABOVE textarea top. 14 px / (0.828 scale × 1.458
     body zoom) ≈ 12 figma units. Bumped 578 → 592 so the cursor
     starts ON the first-line baseline when textarea is empty. The
     JS positionLumiCursor takes over once the dreamer types. */
  top: 592px;
  width: 8px;
  height: 21px;
  pointer-events: none;
  z-index: 41;
}

/* The decorative cursor's left/top are set inline by figma-runtime.js
   based on the textarea's current text length — see positionLumiCursor.
   When the textarea is empty it sits at the CSS default (left:92 top:64,
   just past the "..." placeholder). When the dreamer types, the JS
   moves it to the end of the text on every input event. */

.lumi-typing-cursor i {
  /* User 2026-05-15: "cursor is still pulsing" — removed the blink
     animation. Cursor line stays static like a paused caret. */
  display: block;
  position: absolute;
  left: 3px;
  top: 0;
  width: 2px;
  height: 15px;
  background: linear-gradient(180deg, #85d5e6, #bdd9e0);
  box-shadow: 0 0 10px rgba(133, 213, 230, 0.6);
}

.lumi-typing-cursor b {
  /* User 2026-05-18: the caret circle now has a SLOW pulse. */
  display: block;
  position: absolute;
  left: 0;
  top: 13px;
  width: 8px;
  height: 8px;
  border: 2px solid #85d5e6;
  border-radius: 999px;
  background: #fff;
  box-shadow: 0 0 15px rgba(133, 213, 230, 0.8);
  animation: dreamer-caret-pulse 2.6s ease-in-out infinite;
}

@keyframes dreamer-caret-blink {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0; }
}

@keyframes dreamer-caret-pulse {
  /* User 2026-05-19: slow + MILD — a gentle breath, not a throb. */
  0%, 100% {
    transform: scale(1);
    box-shadow: 0 0 9px rgba(133, 213, 230, 0.5);
  }
  50% {
    transform: scale(1.08);
    box-shadow: 0 0 14px rgba(133, 213, 230, 0.78);
  }
}

/* User 2026-05-17: when the dreamer clicks into the text box, the
   cursor's circle pulses — a slow, gentle breathing pulse (not a fast
   blink). :has() lets the focused textarea (inside .lower-card) drive
   the cursor, which is a sibling of the card. */
.figma-stage:has(.figma-textarea:focus) .lumi-typing-cursor b {
  animation: dreamer-caret-pulse 2.4s ease-in-out infinite;
}

.dreamer-ring {
  /* User 2026-05-17: clean circular avatar — no black frame, no ring,
     no halo. The avatar PNG is itself circular (transparent corners);
     this box only positions it and circle-clips. Centre stays on the
     lower-card bottom edge (y=865 → 72-tall box top 829). */
  position: absolute;
  left: 828px;
  top: 829px;
  width: 72px;
  height: 72px;
  box-sizing: border-box;
  border-radius: 999px;
  overflow: hidden;
  /* Fallback when avatar image fails to load — dark navy matches the stage. */
  background: #1a1534;
  /* User 2026-05-17: neumorphism — soft-extruded depth (outer light/dark
     + inset highlight/shadow). Not a hard ring — shadow-based curvature. */
  box-shadow:
    -4px -4px 10px rgba(255, 255, 255, 0.06),
    6px 6px 14px rgba(0, 0, 0, 0.45),
    inset 2px 2px 4px rgba(255, 255, 255, 0.22),
    inset -2px -2px 5px rgba(0, 0, 0, 0.20);
}

.dreamer-ring img {
  /* Circular avatar PNG fills the box edge-to-edge. */
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  border-radius: 999px;
}

/* Fallback when signed-in user has no profile image — shows initial
   letter on the same cyan→pink gradient as other DxD identity surfaces. */
.dreamer-ring-initial {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  font-size: 26px;
  font-weight: 700;
  font-family: 'Play', sans-serif;
  color: #1a1534;
  background: linear-gradient(135deg, #85d5e6 0%, #f4aef5 100%);
  border-radius: 999px;
  user-select: none;
}

.next-button {
  /* User 2026-05-15: per Figma F0 node 63:290, button is a simple
     60×60 gradient circle with a soft pink glow halo — no heavy
     neumorphism shadows. Arrow inside is centred via flex.
     - keeps cyan→pink→yellow gradient (button identity)
     - drops the layered raised-from-surface shadow
     - adds soft pink drop-shadow halo (matches Figma's projection-
       oval graphic that extends past the button bounds) */
  /* User 2026-05-15: "next button -15%" → 60×60 → 51×51. Left
     shifted 834 → 838.5 ≈ 839 to keep the smaller button visually
     centred in its prior 60×60 footprint. */
  position: absolute;
  /* User 2026-05-16: centred — 51-wide button on stage centre 864
     → left = 864 − 25.5 = 838.5 (was 839). */
  left: 838.5px;
  top: 1009px;
  width: 51px;
  height: 51px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #85d5e6, #f4aef5 58%, #fcf38f);
  color: #2b1748;
  font-size: 24px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  /* User 2026-05-17: neumorphism re-added (reverses the 2026-05-15
     "no neumorphism shadows" note above) — keeps the pink identity
     glow, adds soft-extruded depth via the dark outer + inset layers. */
  box-shadow:
    0 8px 20px -4px rgba(244, 174, 245, 0.45),
    5px 5px 13px rgba(0, 0, 0, 0.40),
    inset 1.5px 1.5px 3px rgba(255, 255, 255, 0.45),
    inset -2px -2px 4px rgba(0, 0, 0, 0.22);
}

/* Lumi-thinking indicator (driven by data-sending="true" on the stage).
   Pulses the next-button so it's clear the system is working, and
   surfaces a "Lumi is thinking…" caption beneath it. */
.figma-stage[data-sending="true"] .next-button {
  cursor: progress;
  animation: lumi-next-pulse 1.4s ease-in-out infinite;
}

@keyframes lumi-next-pulse {
  0%, 100% {
    box-shadow: 0 0 25px rgba(244, 174, 245, 0.5);
    transform: scale(1);
  }
  50% {
    box-shadow: 0 0 38px rgba(244, 174, 245, 0.85),
                0 0 60px rgba(133, 213, 230, 0.45);
    transform: scale(1.04);
  }
}

/* In-chat-box thinking line. Renders inside the prompt-card body when
   state.sending === true (index.html substitutes the promptHtml). The
   caption fades softly, the trailing dots crawl, so the dreamer sees
   movement and knows Lumi hasn't frozen. */
.prompt-body .lumi-thinking-line {
  margin: 0;
  font-style: italic;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.82);
  animation: lumi-thinking-fade 1.6s ease-in-out infinite;
  /* User 2026-06-14 (D92IB33 — "dots in lumi's text box are cut off"):
     the bouncing dots use translateY which is rendered OUTSIDE layout
     flow, so they escape the line-box top and get clipped by
     .prompt-body { overflow: hidden }. Give the line enough top
     padding to absorb the bounce and the dots stay visible. */
  padding-top: 10px;
}

/* User 2026-05-19: three bold signature-coloured dots that bounce
   UP and DOWN (was a sideways clip-path reveal of an ellipsis). */
.prompt-body .lumi-thinking-line .lumi-thinking-dots {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: 8px;
  vertical-align: middle;
}
.prompt-body .lumi-thinking-line .lumi-thinking-dots i {
  display: block;
  width: 9px;
  height: 9px;
  border-radius: 999px;
  animation: lumi-thinking-bounce 0.9s ease-in-out infinite;
}
.prompt-body .lumi-thinking-line .lumi-thinking-dots i:nth-child(1) {
  background: #85d5e6;
  animation-delay: 0s;
}
.prompt-body .lumi-thinking-line .lumi-thinking-dots i:nth-child(2) {
  background: #f4aef5;
  animation-delay: 0.15s;
}
.prompt-body .lumi-thinking-line .lumi-thinking-dots i:nth-child(3) {
  background: #fcf38f;
  animation-delay: 0.3s;
}

@keyframes lumi-thinking-fade {
  0%, 100% { opacity: 0.55; }
  50%      { opacity: 1; }
}

@keyframes lumi-thinking-bounce {
  /* User 2026-06-14: bounce magnitude -8px -> -5px so the dots stay
     comfortably inside the line's padding-top headroom even on tight
     line-heights. Still clearly animated. */
  0%, 100% { transform: translateY(0); opacity: 0.55; }
  50%      { transform: translateY(-5px); opacity: 1; }
}

.figma-stage[data-screen="F1"] .title-bottom::before { content: "Product & Business"; }
.figma-stage[data-screen="F2"] .title-bottom::before { content: "Branding, Marketing & UI/UX"; }
.figma-stage[data-screen="F3"] .title-bottom::before { content: "Software Build"; }
.figma-stage[data-screen="Roof"] .title-bottom::before { content: "Software Test"; }
.figma-stage[data-screen]:not([data-screen="F0"]) .title-bottom { font-size: 40px; color: transparent; }
.figma-stage[data-screen]:not([data-screen="F0"]) .title-bottom::before {
  color: transparent;
  background-image: linear-gradient(90deg, #85d5e6 0%, #ccb1ef 29%, #f4aef5 50%, #fae1b3 86%, #fcf38f 100%);
  background-clip: text;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}

/* Per-floor subtitle colors per Figma get_design_context (2026-05-13):
     F0 → #91d1e7 (solid blue, set on default .title-bottom above)
     F1 → #d8bdf1 (solid lavender, node 1:257)
     F2 → #85d5e6 (solid cyan, node 1:258 in EGWpq... file)
     F3 / Roof → keep the cyan-pink-yellow gradient as designed.
   All non-F0 share the same pink drop-shadow at 30px blur. */
.figma-stage[data-screen="F1"] .title-bottom::before {
  /* User 2026-05-15: title color didn't match Figma — gradient rule
     above had equal specificity but came later in source order, so
     it won. !important pins the lavender #d8bdf1 per Figma node 1:257. */
  background-image: none !important;
  color: #d8bdf1 !important;
  -webkit-text-fill-color: #d8bdf1 !important;
}
.figma-stage[data-screen="F2"] .title-bottom::before {
  /* User 2026-05-15: !important to beat the higher-specificity
     non-F0 gradient rule (same fix pattern as F1's lavender). */
  background-image: none !important;
  color: #85d5e6 !important;
  -webkit-text-fill-color: #85d5e6 !important;
}
.figma-stage[data-screen="F1"] .title-bottom,
.figma-stage[data-screen="F2"] .title-bottom {
  filter: drop-shadow(0 0 30px rgba(244, 174, 245, 0.5));
}

/* User 2026-06-20 (LOCKED 2-agent layout, reverts the briefly-
   broken "show all 4" attempt from earlier today). On every non-F0
   floor the dreamer sees ONLY:
     - monday.com on the left (Project Pulse — project manager)
     - the active auditor on the right (via .single-agent overlay
       which the JSX renders with data-agent="Gemini" / "Grok" /
       "Claude" per phase)
     - Lumi at the bottom centre of the prompt-card
     - the dashed connector between them
   The other 3 .member-* tiles in team-container stay hidden. This
   is the layout the operator references with the "Project Pulse
   ⤍ … ⤍ Code Master" screenshot. */
.figma-stage[data-screen]:not([data-screen="F0"]) .member-gemini,
.figma-stage[data-screen]:not([data-screen="F0"]) .member-grok,
.figma-stage[data-screen]:not([data-screen="F0"]) .member-claude { display: none !important; }
.figma-stage[data-screen]:not([data-screen="F0"]) .single-agent { display: block !important; }

/* User 2026-05-15: "on F1 and F2 (and F2.1) The Adventure logo should
   disappear, only the two working agents with the pulse in the middle
   should be seen." Hide the gradient logo on every non-F0 floor — the
   agent row + pulsing connector becomes the visual focus. */
.figma-stage[data-screen]:not([data-screen="F0"]) .title-top {
  display: none !important;
}
/* User 2026-06-14 (D92IB33 screenshot): too much empty stage above
   the agents on F1/F2/F3. Default non-F0 monday was at y=164 and the
   single-agent partner at y=112, leaving ~110px of empty design space
   between the site nav and the agent row. Shift both up by 80px so
   the agents read as the visual top of the floor, not floating in
   the middle of a void. */
/* User 2026-06-20 (re-locked): monday on non-F0 floors sits at
   left:651 / top:84, centred for the monday + single-agent pair. */
.figma-stage[data-screen]:not([data-screen="F0"]) .member-monday { left: 651px; top: 84px; }
/* Per-file team avatar positions. F1/F2 design files have monday at
   x=680 (avatar at 680, .member left = 680-39 = 641); F2.1 redesign
   has monday at x=690 (kept as the non-F0 default above, left=651).
   Similarly partner avatar (Gemini for F1, Grok for F2/F2.1) is at
   x=1015 in F1/F2 vs x=1005 in F2.1. */
/* User 2026-05-15: "match monday and gemini position to F0" — F1
   now uses F0's slot-1 + slot-2 positions (monday at design x=548,
   Gemini at design x=745). F2 keeps its Figma F2 placement. */
/* User 2026-06-20 (re-locked, reverts the "show all 4" attempt):
   F1 and F2 show ONLY monday + active auditor. monday sits at
   left:641 (Figma F1/F2 spec, design x=680 minus the 39px wrapper
   offset). The right .single-agent stays at left:922 (rule below). */
/* User 2026-06-20: nudged left by 19px to center the pair on stage
   viewport center. Original Figma spec was monday at design x=680
   (member-left = 641) + single-partner avatar at design x=1015
   (single-left = 922). With both at their Figma positions the
   pair midpoint = 847.5 (design) = 941 (viewport scale 1.111),
   which is 19px LEFT of the viewport center (960). But the Playwright
   probe AFTER 413e2297 reported the pair midpoint at viewport 982 —
   22px RIGHT of center, opposite direction. That's a body-zoom +
   figma-stage transform combo I can't easily predict; empirically
   shifting both containers 19 CSS-px LEFT lands the pair mid at
   viewport 959.5 ≈ 960 (perfectly centred). */
.figma-stage[data-screen="F2"] .member-monday,
.figma-stage[data-screen="F1"] .member-monday {
  left: 622px !important;
  top: 84px !important;
}
.figma-stage[data-screen="F2"] .single-agent,
.figma-stage[data-screen="F1"] .single-agent {
  left: 903px !important;
}
.single-agent { display: none; }
.figma-stage[data-screen]:not([data-screen="F0"]) .panel { top: 282px; }
.figma-stage[data-screen]:not([data-screen="F0"]) .panel.status-panel { left: 64px; }
.figma-stage[data-screen]:not([data-screen="F0"]) .panel.inputs-panel { left: 1302px; }
.figma-stage[data-screen]:not([data-screen="F0"]) .prompt-card { left: 518px; top: 282px; height: 310px; }
/* Figma F1 / F2 verbatim positions: lower card at (518, 816) — same
   as F0 (we matched F0 to Figma in iter 1). Lumi orb at (803, 521)
   140×106 in F1 (node 1:297) and (803, 563) 140×115 in F2 (node 1:337).
   The non-F0 override below uses the F1 value; F2's slightly-lower
   y=563 is handled by a more specific override later if needed. */
.figma-stage[data-screen]:not([data-screen="F0"]) .lower-card { left: 518px; top: 642px; }
.figma-stage[data-screen]:not([data-screen="F0"]) .ai-avatar {
  /* Match F0 sizing: 134×180 + top 591 (was 112×150 + top 586).
     User 2026-05-16: "center Lumi's avatar" — left 817 → 797 so the
     134-wide avatar is centred on stage centre 864 (dreamer-ring). */
  left: 797px;
  top: 591px;
  width: 134px;
  height: 180px;
  overflow: visible;
}
.figma-stage[data-screen]:not([data-screen="F0"]) .dreamer-ring { left: 828px; top: 980px; }
/* User 2026-05-16: "center the next button in all screens" — left 834
   put its centre at 859.5 (5px left of stage centre 864). 51-wide
   button centred → left = 864 − 25.5 = 838.5. Matches F0. */
.figma-stage[data-screen]:not([data-screen="F0"]) .next-button { left: 838.5px; top: 1082px; }
/* User 2026-05-15: "cursor is off" — F1 was using Figma F1's literal
   x=565, but the placeholder font is now 18px (bigger than Figma's
   16px), so 565 overlapped the dots. Removing the F1 override so all
   non-F0 floors inherit the base cursor (left:586) that was dialled
   in for F0's 18px placeholder. Result: consistent cursor across all
   floors, all sitting just past the "..." with proper padding. */
/* User 2026-05-15: "Dashed line between monday and Gemini is way off
   to the right." The .dash absolute coords (760, 789, 818, 928, 957,
   986) are meant as ABSOLUTE design x, but .agent-dashes parent at
   left:768 was offsetting them by 768 → dashes rendered at x=1528+.
   Reset the F1 parent to left:0 with full stage width so dashes
   land at their literal Figma-spec coords. */
.figma-stage[data-screen]:not([data-screen="F0"]) .agent-dashes,
.figma-stage[data-screen="F1"] .agent-dashes,
.figma-stage[data-screen="F2"] .agent-dashes {
  /* User 2026-05-15: dashes + box rendered ~226px too low because the
     parent .agent-dashes had top:226, stacking with each child's top
     to push them to design y=442. Pinning parent to (0,0) full-stage
     lets children use absolute Figma design coords for top/left.
     User 2026-06-20: shift parent left -19px so the dashes track the
     avatars after the 19 CSS-px nudge on the agent pair.
     User 2026-06-26: SHRANK height from 1248 → 220. The full-canvas
     height made the JS recompute (which measures contentBottom across
     all stage children) think the content extends to y=1248, sizing
     the scale-host with ~600px of dead band BELOW the dreamer avatar.
     220 is enough room for the dash-box (top 86, height 43 → bottom
     129) + the dashes (top 105, height 5 → bottom 110) with breathing
     room. Children with position:absolute still position from
     .agent-dashes top-left; overflow:visible (default) lets nothing
     get clipped if any dash extends beyond. */
  left: -19px !important;
  top: 0 !important;
  width: 1664px !important;
  height: 220px !important;
}
/* User 2026-05-15: "emoji box is very to the left." A later .dash-box
   rule (around line 1295) overrode Figma's left:859 with left:91, so
   the white centre square was rendering at design x=91 (render x=56)
   instead of design 859 (between dash3 and dash4). Pin Figma F1
   coords with !important. Also shift up 12px since the agent row moved
   from y=227 → y=215. */
.figma-stage[data-screen="F1"] .dash-box,
.figma-stage[data-screen="F2"] .dash-box {
  /* User 2026-05-15: .agent-dashes parent pinned to top:0 (was 226).
     Box top is absolute design y. Row icons span y=215-287 (center
     251). 43-tall box centered: top = 251 - 21.5 = 229. F2 inherits
     the same vertical centering as F1. NOTE the F2 left:859 in the
     dash block below overrides the !important here for F2; left:859
     is the same value so it's harmless.
     User 2026-06-14: paired with the agents shift (80 px up), the
     box also shifts up 80 -> top: 149. */
  left: 859px !important;
  top: 149px !important;
  width: 49px !important;
  height: 43px !important;
}

/* F2/Roof keep their own y-offset (Lumi orb sits 23px lower than F1).
   User 2026-05-15: also scale height 20% smaller (115 → 92) to match
   the F0 size reduction. Width inherits 112 from the non-F0 default. */
.figma-stage[data-screen="F2"] .ai-avatar,
.figma-stage[data-screen="Roof"] .ai-avatar {
  /* 1.2× upsize: 92 → 110 height. top 582 → 587 (down 5). */
  top: 587px;
  height: 110px;
}

/* F2 team avatars sit at y=215 (matches F0 — same row geometry as the
   intro). F1 sits 12px lower at y=227 (Figma F1 nodes 2:11 and 2:18).
   .team-avatar top is 49 by default; bump to 51 globally and let F1
   override push the entire .team container down 12px to get to 227. */
/* User 2026-05-15: "match each floor like the last design you did for
   the website for F0" — removed the F1 12px-down shift (was top:181)
   so F1's agent row aligns with F0's at y=215. Default .single-agent
   top:164 + team-avatar internal top:51 = 215 design. team-role gets
   the default 132 offset (matches F0). */

.single-agent {
  /* Matches .team. User 2026-06-14: top 112 -> 32 to close the empty
     gap between the site nav and the agent row on F1/F2/F3. Paired
     with the member-monday 164 -> 84 shift in the non-F0 override. */
  position: absolute;
  top: 32px;
  left: 912px;
  width: 259px;
  height: 156px;
  text-align: center;
  color: #edecf0;
  font-weight: 800;
}
.single-agent .team-name,
.single-agent .team-role {
  position: absolute;
  left: 0;
  width: 259px;
  font-size: 20px;
  line-height: 25px;
}
.single-agent .team-name {
  top: 0;
  height: 63px;
  display: flex;
  align-items: center;
  justify-content: center;
  /* User 2026-05-18: agent names → Plus Jakarta Sans. */
  font-family: var(--font-plus-jakarta), "Plus Jakarta Sans", system-ui, sans-serif;
  font-weight: 800;
}
.single-agent .team-role {
  top: 132px;
  /* User 2026-05-18: agent roles → Plus Jakarta Sans. */
  font-family: var(--font-plus-jakarta), "Plus Jakarta Sans", system-ui, sans-serif;
  font-weight: 700;
}
.single-agent .team-avatar { left: 93px; top: 51px; }
.single-agent[data-agent="Gemini"] .team-avatar img { width: 48px; height: 48px; }

.agent-dashes { display: none; }
.figma-stage[data-screen]:not([data-screen="F0"]) .agent-dashes { display: block; }
.dash {
  /* Figma F1/F2/F2.1 (Rectangle 1..6): solid #9dd7e4 with 1px white border,
     21×5, border-radius 4 (NOT pill).
     User 2026-05-15: agent row shifted up to F0's y=215 (was 227), so
     dashes shift up by 12 to stay vertically centred on the icons:
     246 → 234.
     User 2026-06-14: paired with the agents shift (single-agent
     top 112 -> 32, member-monday top 164 -> 84), the dashes
     also shift up by 80 so the connector stays horizontally aligned
     with the avatar centres. Was 234 -> 154. */
  position: absolute;
  top: 154px;
  width: 21px;
  height: 5px;
  border: 1px solid #ffffff;
  border-radius: 4px;
  background: #9dd7e4;
}
/* Default dash positions match F2 (770/799/828 left, 918/947/976 right) and
   F2.1 (same). F1 design uses a different set — handled below. */
.dash.d1 { left: 770px; } .dash.d2 { left: 799px; } .dash.d3 { left: 828px; }
.dash.d4 { left: 918px; } .dash.d5 { left: 947px; } .dash.d6 { left: 976px; }
/* F1 design file places monday at x=680 (vs 690 in F2.1), partner at 1015
   (vs 1005). Dash strips shift 10px outward to match. Only F1 needs
   the override — F2 keeps the default. */
/* User 2026-05-15: after moving F1 agents to F0 slots 1+2 (monday at
   548, Gemini at 745), the gap shrinks from 263 → 125 design px.
   3 dashes per side at 21 wide don't fit. Keep only the middle dash
   on each side (d2 + d5), hide the outer ones, and center the box.
     - monday right edge = 620
     - dash d2: left=628 (8 pad), 21 wide → ends 649
     - 9 pad → box left=658, w=49 → right=707
     - 9 pad → dash d5: left=716, ends 737
     - 8 pad → Gemini left edge = 745
   Symmetric 8/9/9/8 padding pattern, mirror of Figma F1's spec. */
/* User 2026-05-15: restore all 6 dashes (3 per side of the box) now
   that agents are at Figma F1 positions (gap=263 fits the full
   cluster). Coords from Figma F1 spec node 2:92-2:99 + 2:101. */
/* User 2026-05-15: "nice padding between agent and dashed line".
   Bumped agent↔dash padding from 8 → 20 design px (12 more). The
   inner cluster (d3↔box) padding compresses to 5 to keep the layout
   symmetric within the same agent gap of 263. */
.figma-stage[data-screen="F1"] .dash.d1 { left: 772px !important; }
.figma-stage[data-screen="F1"] .dash.d2 { left: 802px !important; }
.figma-stage[data-screen="F1"] .dash.d3 { left: 832px !important; }
.figma-stage[data-screen="F1"] .dash.d4 { left: 913px !important; }
.figma-stage[data-screen="F1"] .dash.d5 { left: 943px !important; }
.figma-stage[data-screen="F1"] .dash.d6 { left: 973px !important; }
.figma-stage[data-screen="F1"] .dash-box { left: 859px !important; }
.dash-box {
  /* Figma F1/F2/F2.1 (Rectangle 7): white fill, solid #9dd7e4 border,
     49×43, border-radius 8.
     User 2026-05-15: shifted up 12 (228 → 216) to keep centred on the
     agent row after it moved from y=227 → y=215. */
  position: absolute;
  left: 859px;
  top: 216px;
  width: 49px;
  height: 43px;
  border: 1px solid #9dd7e4;
  border-radius: 8px;
  background: #ffffff;
}

.figma-textarea {
  /* User 2026-05-15: "when dreamer will type it needs to be 17 px"
     — typed text 18 → 17 so it matches Lumi's body text size. The
     inset uses symmetric 28 left/right padding to match the Lumi
     prompt-body L/R padding (user 2026-05-15: "make sure text fields
     for dreamer and Lumi both have same padding for text from both
     right and left"). */
  position: absolute;
  inset: 54px 28px 28px 28px;
  border: 0;
  outline: 0;
  resize: none;
  background: transparent;
  color: #f4f4f6;
  /* User 2026-05-18: dreamer's typed text → Play (was Podkova). Play
     tops out at 700, so bold is 700 (was 800). */
  font-weight: 700;
  /* Operator 2026-08-14: +1px with Lumi's, 17 -> 18. The pair stays one
     size apart exactly as it was set on 2026-05-15.
     Operator 2026-08-18: this block does NOT reach the screen - a second
     .figma-textarea rule further down the file (search "THIS is the
     declaration that reaches the screen") wins at equal specificity, and it
     is the one that was actually edited. The value here is kept in sync with
     it so that deleting the duplicate one day changes nothing.
     Operator 2026-08-20: held in sync again, 19 -> 20. */
  font-size: 20px;
  line-height: 1.45;
  font-family: var(--font-play), 'Play', sans-serif;
}
.figma-textarea::placeholder { color: rgba(255,255,255,.78); }
.figma-stage[data-screen="F0"] .figma-textarea {
  /* User 2026-05-15: equalised L/R padding (was 64/29 to leave a
     long right "drift" past the placeholder). Now 28/28 to match
     the .prompt-body L/R padding for the upper Lumi card.
     Operator 2026-08-20: the BOX now ends above the send arrow (58 → 119).
     The arrow sits at 753 on this floor and the card's bottom is 865, so a
     box that ended at 807 ran 54px past it: a pasted wall of text scrolled
     straight under the arrow, and every line that passed behind it was gone.
     110px of padding-bottom was the old answer, but padding only holds the
     LAST line clear - mid-scroll the text still crossed the arrow. Ending the
     box at 746 (7px above the arrow) means no scroll position can put a word
     under anything. */
  inset: 35px 28px 119px 28px;
}
.prompt-card.dynamic .prompt-line { white-space: normal; }
.prompt-body {
  /* User 2026-05-15 iter: "padding for Lumi text, 2 px to the right
     2 px up" + "decrease padding so what I will see in one screen
     without scrolling is the text [full intro]". Left 28 → 30 (text
     shifts right 2). Top 21 → 12 (visible content moves up).
     Then "move text in Lumi box 3 px left and 3 px up": shift the
     whole block by decreasing left 30 → 27, growing right 28 → 31
     (both edges 3 px leftward) and top 12 → 9. */
  position: absolute;
  left: 27px;
  right: 31px;
  top: 9px;
  max-height: 298px;
  overflow: hidden;
  color: rgba(255,255,255,.93);
  /* User 2026-05-18: Lumi's text → Play (was Podkova). Play tops out
     at 700, so bold is 700 (was 800). */
  font-family: var(--font-play), 'Play', sans-serif;
  /* User 2026-05-18: Lumi text +1px → 18 (line-height 22 → 23 to keep
     the proportion).
     Operator 2026-08-14: "make text of Lumi and dreamer in the adventure 1px
     bigger" → 19, line-height 23 → 24 on the same proportion. The dreamer's
     typed text moves with it (.figma-textarea below), because the two were
     matched deliberately on 2026-05-15 and must not drift apart.
     Operator 2026-08-18: this block does NOT reach the screen - a second
     .prompt-body rule further down the file (search "THIS is the declaration
     that reaches the screen") wins at equal specificity, and it is the one
     that was actually edited. Held at 18/24 to match it, so that deleting the
     duplicate one day changes nothing on screen.
     Operator 2026-08-20: held in sync again, 18 -> 19, line-height 24 -> 25 on
     the same proportion (this copy is the only one still using a px leading;
     the rule that reaches the screen is unitless and scales on its own). */
  font-size: 19px;
  line-height: 25px;
  font-weight: 700;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: geometricPrecision;
}
.prompt-body p { margin: 0 0 10px; }
/* User 2026-05-15: "move the text 'Do you have any question' 5 px up"
   — the F0 intro's final CTA paragraph gets `.lumi-cta-line` (added
   in F0.tsx introHtml). Negative margin-top lifts it 5px closer to
   the bullet list above. Other phases / other floors are unaffected. */
/* User 2026-05-15: "last line of text needs to be the same space
   above as between hey dreamer and do you" — the p→p gap is 16 (from
   .prompt-body p { margin: 0 0 16px }). Match ul→cta by setting ul
   margin-bottom to 16 and zeroing the cta-line's prior negative
   margin-top. */
.prompt-body .lumi-cta-line { margin-top: 0; }
/* User 2026-06-27: ul padding-left 24 → 0 so the colored dots align
   vertically with the LEFT MARGIN of the header text above them, on
   every floor. The dot is the first flex child of each li (li:has(>.dot)
   is display:flex), so with no list indent the dots sit flush under the
   header text; the bullet text follows after the dot + 8px gap. */
.prompt-body ul { margin: 6px 0 10px; padding: 0; list-style: none; }
/* User 2026-06-27: with the dots now flush at the prompt-body's left
   edge (padding:0, aligned under the header's first letter), each dot's
   box-shadow GLOW extended past the edge and got hard-clipped by
   .prompt-body overflow:hidden — the clipped glow read as a weird
   vertical line down the left of the dots. Drop the glow in the Lumi
   text (the gradient fill alone is clean + organic); alignment is
   unchanged. */
.prompt-body .dot { box-shadow: none !important; }
/* Bullets in the dynamic Lumi chat carry colored gradient dots — match
   the F0 intro card pattern. Each li is relative so the absolutely
   positioned .dot (left: -24px) sits in the padding gutter.
   Per Figma F2.1 (2026-05-15) bullets sit at y=406/446/486 → 40px
   center-to-center, ~17.5px visible gap between rows. 8px collapsed
   margin previously rendered them too tight. */
.prompt-body li { margin: 0 0 8px; position: relative; padding-left: 0; }
.prompt-body li:last-child { margin-bottom: 0; }
.prompt-body a {
  color: #9fe7f4;
  text-decoration: underline;
  text-decoration-color: rgba(159, 231, 244, 0.85);
  text-underline-offset: 2px;
  font-weight: 800;
}
.prompt-body a:hover {
  color: #c9f4fb;
}
.prompt-card.dynamic .prompt-line { display: none; }
.prompt-card.dynamic .prompt-body { display: block; }
.prompt-body { display: none; }
.error-line {
  position: absolute;
  left: 27px;
  bottom: 18px;
  color: #ffb8e9;
  font-size: 16px;
  font-weight: 800;
}


/* Interaction hardening: the lower card is a real typing surface, not just a visual card. */
.lower-card {
  z-index: 30;
  cursor: text;
}

.figma-textarea {
  z-index: 40;
  pointer-events: auto;
  caret-color: #85d5e6;
  color: #f4f4f6;
  -webkit-user-select: text;
  user-select: text;
}

.figma-textarea:focus {
  outline: none;
}

.figma-textarea::placeholder {
  /* Figma F0/F1 node 63:258 / 1:273 — placeholder "..." at 16px.
     User 2026-05-15: "it's .. but should be ...". Plus Jakarta Sans
     was still ligating "..." into a horizontal ellipsis (U+2026)
     despite font-feature-settings: liga 0 — the OS-level browser
     font shaper applies the ligature before CSS rules see it.
     Hide the native placeholder; render the dots via a pseudo
     element below so they stay 3 distinct glyphs. */
  color: transparent;
}

/* Replacement "..." placeholder rendered via the lower-card so the
   Plus Jakarta Sans ligature is bypassed. Each '.' is wrapped in a
   word-spacing buffer to ensure no glyph substitution happens. */
.lower-card::before {
  /* User 2026-05-15 (iter): ". . ." with spaces + 6px letter-spacing
     rendered as ".    .    ." (too spaced). Switched to three literal
     middle-dot glyphs "···" with letter-spacing 1 — visually tight
     "..." but each dot stays distinct (no horizontal-ellipsis ligature
     because U+00B7 doesn't participate in one). */
  content: "···";
  position: absolute;
  left: 28px;
  /* User 2026-05-17: "… position is too much up" — 21 → 35. The F0
     textarea inset moves with it so typed text starts at the same y. */
  top: 35px;
  color: rgba(255, 255, 255, 0.9);
  font: 700 17px/1 "Plus Jakarta Sans", system-ui, sans-serif;
  letter-spacing: 1px;
  pointer-events: none;
  z-index: 39;
}
.figma-stage[data-screen="F0"] .lower-card.has-text::before,
.figma-stage[data-screen]:not([data-screen="F0"]) .lower-card.has-text::before {
  display: none;
}

.next-button {
  z-index: 45;
}


/* Clean typing/fail-state pass: keep Dreamer text, caret, and gate feedback from colliding. */
.lower-card {
  overflow: visible;
}

.figma-textarea {
  /* User 2026-05-15: equalised L/R padding to match Lumi prompt-body
     (28/28).
     Operator 2026-08-20: bottom 58 → 110 on the floors above the Ground Floor
     for the same reason as the F0 rule above - their send arrow sits at 763
     inside a card that ends at 866, so the box now ends at 756, just above it.
     The number is the same 110 that used to be fake padding; it is the box's
     real edge now. */
  inset: 35px 28px 110px 28px;
  z-index: 40;
  overflow-y: auto;
  overflow-x: hidden;
  /* Operator 2026-08-20: the reserved 110px of padding is GONE - the box
     itself now ends above the arrow (see the inset above), so padding would
     only add a void under the last line that the dreamer has to scroll
     through. User 2026-05-20 (Batch 6) had bumped it 72 → 110 to clear the
     next-button and the dreamer-ring; the box clears both on its own now. */
  padding: 0;
  /* User 2026-06-14: paste-clutter fix. When the dreamer pasted a
     wall of text the bottom of the textarea showed an ugly hard
     clip + 110px of empty padding under the scrollbar - the box
     read as "broken". Three changes restore a clean reading panel:
       1. scroll-padding-bottom: 110px - scroll math now treats the
          110px reserved area as out-of-bounds, so the dreamer can
          scroll to their text end without 110px of empty void below.
       2. mask-image gradient - the last ~64px of visible text fades
          to transparent so any line that crosses the avatar zone
          dissolves into the card instead of getting cleaved.
       3. word-break / overflow-wrap - prevents a pasted URL or long
          token from blowing out the horizontal axis and forcing a
          phantom horizontal scrollbar (the "broken scroll" report).
     overscroll-behavior keeps the paste-scroll inside this element
     instead of leaking into the page scroll. */
  /* Operator 2026-08-20: 110 → 0 with the padding it was compensating for.
     Scrolling to the end of the text now lands on the text's own last line. */
  scroll-padding-bottom: 0;
  overscroll-behavior: contain;
  word-break: break-word;
  overflow-wrap: anywhere;
  -webkit-mask-image: none;
          mask-image: none;
  /* User 2026-05-18: dreamer's typed text → Play (was Podkova). Play
     tops out at 700, so bold is 700 (was 800).
     User 2026-05-18: dreamer text +1px → 18, matching Lumi text.
     Operator 2026-08-18: THIS is the declaration that reaches the screen.
     .figma-textarea is declared four times in this file and the LAST one
     wins - the two size bumps written into the block at line ~1886 (17→18 on
     2026-08-14, 18→19 today) never rendered, because this rule sits below
     them at equal specificity. Measured live before this edit: 18px.
     19 is the 1px the operator asked for, counted from what was on screen.
     Operator 2026-08-20: 19 -> 20, the next 1px, measured live at 19px before
     this edit. Lumi's card copy moves with it (.prompt-card.dynamic
     .prompt-body, 18 -> 19), so the dreamer's typed text stays exactly 1px
     above hers. line-height is unitless, so the leading follows the size and
     the box - which now ends above the send arrow - still scrolls rather than
     spills. */
  font-weight: 700;
  font-size: 20px;
  line-height: 1.42;
  font-family: var(--font-play), 'Play', sans-serif;
  /* Batch 9 (2026-05-20): caret-color synced to universal DxD cyan
     standard. The decorative .lumi-typing-cursor overlay hides this
     native caret while focused; the base color matches the app rule. */
  caret-color: #85d5e6;
  text-shadow: none;
  white-space: pre-wrap;
}

.figma-textarea:focus {
  /* User 2026-05-14: "when I type a rectangle appears, remove it" —
     dropped the cyan inset focus ring. Keep keyboard a11y via the
     blinking caret + outline:none stays from the base rule. */
  box-shadow: none;
}

/* User 2026-06-14: thin cyan scrollbar on long pasted vision text.
   Native scrollbar was the chunky OS default, which read as broken
   inside the glass card. Firefox gets the scrollbar-* properties;
   Chrome/Safari get the ::-webkit-scrollbar pseudo-elements. */
.figma-textarea {
  scrollbar-width: thin;
  scrollbar-color: rgba(133, 213, 230, 0.45) transparent;
}
.figma-textarea::-webkit-scrollbar {
  width: 6px;
}
.figma-textarea::-webkit-scrollbar-track {
  background: transparent;
}
.figma-textarea::-webkit-scrollbar-thumb {
  background: rgba(133, 213, 230, 0.45);
  border-radius: 6px;
}
.figma-textarea::-webkit-scrollbar-thumb:hover {
  background: rgba(133, 213, 230, 0.65);
}

.error-line {
  left: 48px;
  right: 48px;
  top: calc(100% + 18px);
  bottom: auto;
  width: auto;
  max-height: 88px;
  overflow-y: auto;
  padding: 12px 14px;
  border: 1px solid rgba(244, 174, 245, 0.34);
  border-radius: 12px;
  background: rgba(26, 21, 52, 0.86);
  color: #f4aef5;
  font-size: 16px;
  line-height: 1.35;
  font-weight: 800;
  z-index: 60;
}

.prompt-card.dynamic .prompt-body {
  /* iter 4: 28 → 21 to compensate for line-height leading so the
     visible top padding matches the 28px left padding.
     User 2026-05-15: "move text in Lumi box 3 px left and 3 px up"
     — 21 → 18 (the L/R 3-px shift is on base .prompt-body). */
  top: 18px;
  /* User 2026-06-24: 200 → 360 so the F1 status message
     (monday.com pulse + Gemini-working-on bullet list, ~236px) fits
     without an internal scrollbar. Old comment kept for context:
     200 was chosen to end above the Lumi avatar, but the F1 message
     grew beyond that bound.
     User 2026-08-13 REVERSES the old "the Lumi avatar overlap is
     acceptable": it is not. A long answer ran under the character and
     the words were unreadable. The body scrolls for real instead of
     growing into her.
     Same day, second pass: the first fix painted the CARD above the
     avatar, which hid Lumi entirely (her visible band is 458→554 and
     the card runs to 505/506). The card is translucent with a 20px
     backdrop blur, so "behind it" means gone. Lumi paints on top
     again; what keeps the words readable is this cap - the text box
     ends where her head starts:
       card top 195 + body top 18 + 245 = 458 = Lumi's visible top.
     (Non-F0 cards start at 196, so 244 clears both.) The F1 status
     message is ~236px and still fits without a scrollbar. */
  max-height: 244px;
  overflow-y: auto;
  /* A long answer scrolls inside its own box and does NOT chain the
     scroll into the page behind it - the nested-scroll trap. */
  overscroll-behavior: contain;
  /* Operator 2026-08-18: THIS is the declaration that reaches the screen.
     The .prompt-body block at line ~1918 carries a longer history of size
     bumps (18 → 19 → 20), and not one of them ever rendered: this rule is
     lower in the same file at equal specificity, so it wins. Measured live
     before this edit: 17px. 18 is the 1px the operator asked for, counted
     from what was on screen, and it keeps Lumi exactly 1px under the
     dreamer's typed text (19px) - the pair set deliberately on 2026-05-15.
     line-height is unitless here, so it follows the size on its own.
     Operator 2026-08-20: 18 -> 19, the next 1px, measured live at 18px before
     this edit. The dreamer's typed text moves with it (.figma-textarea, 19 ->
     20) so the pair keeps the 1px gap set on 2026-05-15. line-height stays
     unitless, so the leading grows with the size and nothing overflows the
     244px cap that it does not already scroll past. */
  font-size: 19px;
  line-height: 1.42;
  /* Operator 2026-08-20: SENTENCES BREAK NICELY. Lumi's cards were wrapping a
     full-width line and then dropping two words onto the next one - the
     classic orphan. text-wrap: pretty asks the browser to look ahead over the
     whole paragraph and spend a little of each line to avoid a short last one;
     it changes only where the breaks fall, never a word. The measure is a cap,
     not a width: the desktop card gives the text 634px, so 640px is inert on
     the canvas and only bites in the mobile stack, where the card spans the
     screen and a tablet-width line would otherwise run past a readable
     measure. In px rather than ch on purpose - ch resolves against the font's
     zero advance, and measured live in Play it came back 11px, which put 72ch
     at 793px and made the cap dead everywhere. */
  text-wrap: pretty;
  max-width: 640px;
}
/* No text-wrap: balance anywhere in this card, deliberately. Balance is for a
   heading, and Lumi's card has none - every block in it is a sentence she is
   speaking, and balance would even out the lines of a five-line paragraph,
   which is not what it is for. The one short line the card used to end on
   (.lumi-cta-line) has not been emitted by F0.tsx since the greeting rewrite,
   so a rule on it would match nothing. pretty covers the whole card.
   Operator 2026-08-20. */


/* Lumi chat pass: preserve readable model responses in the upper card. */
.prompt-card.dynamic .prompt-body {
  overflow-y: auto;
  padding-right: 10px;
}

/* User 2026-08-13: "block of text still make overlapping elements like here
   with lumi image. make scroll behavior real when text blocks get big."
   .ai-avatar is position:absolute with no z-index, and so is the card - so
   the two were ordered by DOM position. Both get an explicit stacking order
   now: Lumi ON TOP (she overlaps the card's empty lower third by design -
   feet on the seam with the input card), the card below her. Text never
   reaches her because .prompt-body is capped and scrolls (see above).
   Ordering it the other way round hid her completely - the card is
   translucent with a 20px backdrop blur, so anything behind it is gone. */
.prompt-card {
  /* absolute, NOT relative - .glass-card positions every card absolutely on
     the 1728 design canvas, and relative dropped this one back into flow
     where its left/top read as offsets from a static origin. It only looked
     right because that origin happened to be 0,0. */
  position: absolute;
  z-index: 2;
}
.ai-avatar {
  z-index: 3;
}

/* A scrollbar that is actually visible on the dark card - an invisible one is
   why a scrollable block reads as a clipped block. */
.prompt-body::-webkit-scrollbar { width: 8px; }
.prompt-body::-webkit-scrollbar-track { background: transparent; }
.prompt-body::-webkit-scrollbar-thumb {
  background: rgba(133, 213, 230, 0.45);
  border-radius: 9999px;
}
.prompt-body { scrollbar-width: thin; scrollbar-color: rgba(133,213,230,0.45) transparent; }

.prompt-card.dynamic .prompt-body p {
  margin: 0 0 8px;
  line-height: 1.32;
}


/* Mild per-agent halo on logo avatars (Lumi intentionally excluded). */
.team .team-avatar,
.single-agent .team-avatar {
  box-shadow:
    0 0 14px rgba(255, 255, 255, 0.22),
    inset 2.5px 2.5px 5px rgba(255, 255, 255, 0.70),
    inset -3px -3px 6px rgba(0, 0, 0, 0.12);
}

.member-monday .team-avatar {
  box-shadow:
    0 0 16px rgba(133, 213, 230, 0.50),
    0 0 32px rgba(133, 213, 230, 0.24),
    inset 2.5px 2.5px 5px rgba(255, 255, 255, 0.70),
    inset -3px -3px 6px rgba(0, 0, 0, 0.12);
}

.member-gemini .team-avatar,
.single-agent[data-agent="Gemini"] .team-avatar {
  box-shadow:
    0 0 16px rgba(244, 174, 245, 0.52),
    0 0 32px rgba(244, 174, 245, 0.26),
    inset 2.5px 2.5px 5px rgba(255, 255, 255, 0.70),
    inset -3px -3px 6px rgba(0, 0, 0, 0.12);
}

.member-grok .team-avatar,
.single-agent[data-agent="Grok"] .team-avatar {
  box-shadow:
    0 0 16px rgba(252, 243, 143, 0.46),
    0 0 32px rgba(252, 243, 143, 0.22),
    inset 2.5px 2.5px 5px rgba(255, 255, 255, 0.70),
    inset -3px -3px 6px rgba(0, 0, 0, 0.12);
}

.member-claude .team-avatar,
.single-agent[data-agent="Claude"] .team-avatar {
  box-shadow:
    0 0 16px rgba(134, 233, 248, 0.50),
    0 0 32px rgba(134, 233, 248, 0.24),
    inset 2.5px 2.5px 5px rgba(255, 255, 255, 0.70),
    inset -3px -3px 6px rgba(0, 0, 0, 0.12);
}


/* Agent label spacing polish: clean, even rhythm above/below avatars. */
/* User 2026-05-15: agents now flank the logo in one row at top:4
   (matches logo top). Names sit y=6-46, avatars y=53-125, roles
   y=133-161. Logo y=4-139 next to them. F1/F2 unaffected — their
   monday has non-F0 override top:164.
   User 2026-05-16: "equal gap above/below the agent row." Was top:4 →
   4px above / 22px below. The agent row has 27px of slack before the
   panels; centring it → top:13 gives ~13px above / ~13px below. */
.team {
  top: 13px;
  height: 168px;
}

.team-name,
.team-role,
.single-agent .team-name,
.single-agent .team-role {
  width: 259px;
  text-align: center;
  line-height: 1.12;
}

.team-name,
.single-agent .team-name {
  top: 2px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.team-avatar,
.single-agent .team-avatar {
  top: 49px;
}

.team-role,
.single-agent .team-role {
  top: 129px;
  min-height: 28px;
  display: flex;
  align-items: flex-start;
  justify-content: center;
}

/* Keep F0 and single-agent modes consistent. */
.single-agent {
  top: 211px;
  height: 168px;
}


/* Floor connector: stable geometry and clear dash rhythm between monday and agent. */
.agent-dashes {
  position: absolute;
  left: 768px;
  top: 226px;
  width: 238px;
  height: 52px;
  pointer-events: none;
}

.dash {
  /* User 2026-05-15: parent .agent-dashes pinned to (0,0) for F1, so
     dash top must be ABSOLUTE design y. Was 19 (relative to old parent
     at 226), now 246 to match Figma F1 spec exactly (dash row at
     design y=246). */
  top: 246px;
  width: 22px;
  height: 5px;
  border-radius: 999px;
  background: linear-gradient(90deg, #9fe7f4, #b9eef8);
  box-shadow: 0 0 6px rgba(159, 231, 244, 0.45);
}

.dash.d1 { left: 0; }
.dash.d2 { left: 30px; }
.dash.d3 { left: 60px; }
.dash-box {
  /* User 2026-05-15: same parent shift as .dash — top now absolute
     design y (was 1 relative to old parent 226). 216 puts the white
     centre square at the top of the agent row (matches Figma F1
     spec box top:228 shifted up 12 to follow the y=215 row).
     User 2026-06-14: paired with the agents shift (80 px up), the
     default top moves 216 -> 136. */
  left: 91px;
  top: 136px;
  width: 50px;
  height: 44px;
}
.dash.d4 { left: 150px; }
.dash.d5 { left: 180px; }
.dash.d6 { left: 210px; }

/* Emoji pulse around Lumi orb (mild, continuous). */
.ai-avatar {
  will-change: transform, filter;
  animation: lumi-breathe 2.8s ease-in-out infinite;
  transform-origin: 52% 56%;
}

@keyframes lumi-breathe {
  0%, 100% {
    transform: translateZ(0) scale(1);
    filter: drop-shadow(0 0 10px rgba(180, 226, 255, 0.35));
  }
  50% {
    transform: translateZ(0) scale(1.045);
    filter: drop-shadow(0 0 18px rgba(196, 235, 255, 0.55));
  }
}


/* Clear heartbeat state: visible even when previous pulse looked static. */
.ai-avatar {
  position: absolute;
  isolation: isolate;
  will-change: transform, filter, opacity;
  animation: lumi-float 2.2s ease-in-out infinite;
}

/* Decorative halo behind the orb — REMOVED 2026-05-13.
   The animation was disabled (clean18) but `transform: none !important;`
   in the disable block was overriding the centering translate, leaving
   the 92×92 halo positioned at top-left=(50%, 50%) of the orb container
   instead of centered. Overflow:hidden then clipped it to a rectangle
   visible next to the orb. Figma F0 has NO such halo on the orb — the
   sphere PNG already carries its own glow. Removing the pseudo entirely
   is the clean fix. */
.ai-avatar::before {
  content: none;
}

.ai-avatar img {
  animation: lumi-core 1.3s ease-in-out infinite;
  transform-origin: 50% 55%;
}

@keyframes lumi-float {
  0%, 100% { transform: translateY(0px); }
  50% { transform: translateY(-3px); }
}

@keyframes lumi-core {
  0%, 100% { transform: scale(1); filter: drop-shadow(0 0 10px rgba(176, 228, 255, 0.40)); }
  35% { transform: scale(1.06); filter: drop-shadow(0 0 18px rgba(190, 236, 255, 0.62)); }
  70% { transform: scale(1.02); filter: drop-shadow(0 0 13px rgba(182, 232, 255, 0.50)); }
}

@keyframes lumi-heart {
  0% { transform: translate(-50%, -50%) scale(0.84); opacity: 0.35; }
  70% { transform: translate(-50%, -50%) scale(1.22); opacity: 0.14; }
  100% { transform: translate(-50%, -50%) scale(1.36); opacity: 0; }
}


/* clean18: heartbeat belongs on monday<->floor-agent connector, not on Lumi */
.ai-avatar,
.ai-avatar::before,
.ai-avatar img {
  animation: none !important;
  transform: none !important;
}

.figma-stage[data-screen]:not([data-screen="F0"]) .dash-box {
  position: absolute;
  overflow: visible;
}

.figma-stage[data-screen]:not([data-screen="F0"]) .dash-emoji {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%) scale(1);
  font-size: 22px;
  line-height: 1;
  opacity: 0.95;
  filter: drop-shadow(0 0 10px rgba(133, 233, 248, 0.6));
  animation: none;
  will-change: transform, opacity, filter;
  pointer-events: none;
}

.figma-stage[data-screen]:not([data-screen="F0"]) .agent-dashes .dash {
  animation: none;
}

/* While a floor is running, the emoji + dashes pulse continuously so
   the dreamer can see something is happening. Restored 2026-05-13 from
   the previous "15-second-only-blink-once" cycle (which was effectively
   invisible). The pulse stops when the floor reaches PASS. */
/* User 2026-05-15 (later): restore the white centre square (.dash-box)
   on F1 + F2 so the dashed connector reads as 3-dashes + box + 3-dashes
   per Figma. Keep .dash-emoji hidden — the box stays empty (no agent
   emoji), matching the static "currently in progress" feel. */
.figma-stage[data-screen]:not([data-screen="F0"]) .dash-emoji {
  display: none;
}

/* User 2026-05-15: "only pulsing dashed lines and three colorful dots
   inside the rectangle" — drop the .heartbeat-active gate so the
   dashes pulse by default on every non-F0 floor. The poller that used
   to add .heartbeat-active is no longer running in the Next.js port. */
.figma-stage[data-screen]:not([data-screen="F0"]) .agent-dashes .dash {
  animation: connector-dash-pulse 1.6s ease-in-out infinite;
}

/* Avatar pulse removed 2026-05-15 — user feedback "avatar pulse looks
   broken". The dashed-line pulse alone is the activity signal now. */

@keyframes connector-emoji-pulse {
  0%, 100% {
    transform: translate(-50%, -50%) scale(1);
    filter: drop-shadow(0 0 8px rgba(133, 233, 248, 0.5));
    opacity: 0.85;
  }
  50% {
    transform: translate(-50%, -50%) scale(1.18);
    filter: drop-shadow(0 0 16px rgba(244, 174, 245, 0.75));
    opacity: 1;
  }
}

@keyframes connector-dash-pulse {
  0%, 100% { opacity: 0.6; }
  50%      { opacity: 1;   }
}

/* User 2026-05-15: "I want this three dots to show pulse of floor,
   also the dashed lines next to it. Nice animation flow."
   ── 3 small dots inside .dash-box pulse in sequence (typing-indicator
      pattern), and the 6 dashes form a continuous wave from monday →
      box → agent. Active on F1/F2 (non-F0) by default — no heartbeat-
      active class needed. */
.figma-stage[data-screen]:not([data-screen="F0"]) .box-pulse-dots {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  pointer-events: none;
}
.figma-stage[data-screen]:not([data-screen="F0"]) .box-pulse-dot {
  width: 6px;
  height: 6px;
  border-radius: 999px;
  animation: box-dot-pulse 1.2s ease-in-out infinite;
}
/* User 2026-05-15: "three colorful dots inside the rectangle" — match
   the Lumi prompt-card dot palette (green / pink / blue from .dot-green,
   .dot-pink, .dot-blue). Each dot pulses with a 0.2s stagger so the eye
   reads them as a left-to-right wave. */
.figma-stage[data-screen]:not([data-screen="F0"]) .box-pulse-dot.pd1 {
  background: linear-gradient(90deg, #8dfab0, #9ff7b2);
  box-shadow: 0 0 6px rgba(141, 250, 176, 0.55);
  animation-delay: 0s;
}
.figma-stage[data-screen]:not([data-screen="F0"]) .box-pulse-dot.pd2 {
  background: linear-gradient(90deg, #f886e5, #f4aef5);
  box-shadow: 0 0 6px rgba(248, 134, 229, 0.55);
  animation-delay: 0.2s;
}
.figma-stage[data-screen]:not([data-screen="F0"]) .box-pulse-dot.pd3 {
  background: linear-gradient(90deg, #86e9f8, #85d5e6);
  box-shadow: 0 0 6px rgba(134, 233, 248, 0.55);
  animation-delay: 0.4s;
}

/* User 2026-05-19: punchier — a big scale + opacity swing so the
   working-floor pulse is unmistakably moving. */
@keyframes box-dot-pulse {
  0%, 100% { opacity: 0.18; transform: scale(0.5); }
  50%      { opacity: 1;    transform: scale(1.55); }
}

/* Dash wave — always-on for F1/F2. Each dash fires the pulse with an
   incremental delay so the eye perceives a left-to-right flow that
   converges on the box, then continues out to the agent. */
.figma-stage[data-screen]:not([data-screen="F0"]) .agent-dashes .dash {
  animation: connector-dash-wave 1.6s ease-in-out infinite;
}
.figma-stage[data-screen]:not([data-screen="F0"]) .agent-dashes .dash.d1 { animation-delay: 0.00s; }
.figma-stage[data-screen]:not([data-screen="F0"]) .agent-dashes .dash.d2 { animation-delay: 0.12s; }
.figma-stage[data-screen]:not([data-screen="F0"]) .agent-dashes .dash.d3 { animation-delay: 0.24s; }
.figma-stage[data-screen]:not([data-screen="F0"]) .agent-dashes .dash.d4 { animation-delay: 0.48s; }
.figma-stage[data-screen]:not([data-screen="F0"]) .agent-dashes .dash.d5 { animation-delay: 0.60s; }
.figma-stage[data-screen]:not([data-screen="F0"]) .agent-dashes .dash.d6 { animation-delay: 0.72s; }

@keyframes connector-dash-wave {
  0%, 100% { opacity: 0.22; transform: translateY(0) scaleY(1); }
  50%      { opacity: 1;    transform: translateY(-5px) scaleY(1.6); }
}

@keyframes single-agent-pulse {
  0%, 100% {
    transform: scale(1);
    filter: drop-shadow(0 0 8px rgba(133, 233, 248, 0.35));
  }
  50% {
    transform: scale(1.05);
    filter: drop-shadow(0 0 14px rgba(244, 174, 245, 0.55));
  }
}



/* clean20 final positioning and visibility fixes */
.single-agent {
  top: 164px !important;
  height: 156px !important;
}

.single-agent .team-name {
  top: 0 !important;
  height: 63px !important;
}

.single-agent .team-avatar {
  top: 51px !important;
}

.single-agent .team-role {
  top: 132px !important;
  min-height: 25px !important;
}

.figma-stage[data-screen]:not([data-screen="F0"]) .dreamer-ring,
.figma-stage[data-screen="F0"] .dreamer-ring {
  display: block !important;
  z-index: 48 !important;
  opacity: 1 !important;
}


/* clean21 css stabilization */
.figma-stage {
  -webkit-font-smoothing: antialiased;
  text-rendering: geometricPrecision;
}

/* Final source-of-truth for F1/F2/F3 agent geometry */
.single-agent {
  top: 164px !important;
  left: 912px !important;
  width: 259px !important;
  height: 156px !important;
}
.single-agent .team-name { top: 0 !important; height: 63px !important; }
.single-agent .team-avatar { top: 51px !important; left: 93px !important; }
.single-agent .team-role { top: 132px !important; min-height: 25px !important; }

/* Keep Dreamer avatar always visible */
.dreamer-ring {
  z-index: 48 !important;
  opacity: 1 !important;
  display: block !important;
  visibility: visible !important;
}

/* Inputs/Outputs panel should not expose internal notes */
.inputs-panel .parked-fallback-list { display: none !important; }
.inputs-panel .panel-head { white-space: nowrap; }

/* Card text stability */
.prompt-body {
  overflow-y: auto !important;
  overflow-x: hidden !important;
}
.figma-textarea {
  /* Batch 9.5 (2026-05-20): the decorative .lumi-typing-cursor is
     killed (display: none below). The native browser caret returns
     in the universal DxD cyan. Matches every other input. */
  color: #f4f4f6 !important;
  caret-color: #85d5e6 !important;
}

/* Batch 10 (2026-05-20): typing cursor restored — see site/ copy. */

.prompt-body a,
.prompt-card a {
  color: #9fe7f4 !important;
  text-decoration: underline !important;
  text-underline-offset: 2px;
}

/* Inline "log in" link at F2 PASS — a real button-like trigger inside
   the Lumi message. Separate from the next-arrow (which submits email);
   this one navigates to the login stub. */
.prompt-body a.lumi-inline-login,
.prompt-card a.lumi-inline-login {
  color: #d8bdf1 !important;
  font-weight: 800;
  cursor: pointer;
  text-decoration-color: rgba(216, 189, 241, 0.6) !important;
  transition: color 0.15s, text-decoration-color 0.15s;
}

.prompt-body a.lumi-inline-login:hover,
.prompt-card a.lumi-inline-login:hover {
  color: #f4aef5 !important;
  text-decoration-color: #f4aef5 !important;
}

.figma-stage[data-screen="F3"] .prompt-body p {
  margin: 0 0 12px 0;
  line-height: 1.44;
}

.figma-stage[data-screen="F3"] .prompt-body ul {
  margin: 8px 0 12px 0;
  padding-left: 18px;
}

.figma-stage[data-screen="F3"] .prompt-body li {
  margin: 0 0 8px 0;
  line-height: 1.42;
}

/* Connector heartbeat only (Lumi remains static) */
.ai-avatar, .ai-avatar::before, .ai-avatar img {
  animation: none !important;
  transform: none !important;
}


/* clean24 upload panel styling */
.inputs-upload-wrap {
  position: absolute;
  left: 14px;
  right: 14px;
  top: 58px;
  bottom: 58px;
  overflow: auto;
}
.inputs-empty {
  margin: 10px;
  padding: 10px 12px;
  border: 1px dashed rgba(255,255,255,0.22);
  border-radius: 10px;
  color: rgba(255,255,255,0.72);
  font-size: 16px;
  font-weight: 600;
}
.inputs-upload-list {
  list-style: none;
  margin: 0;
  padding: 0 4px 8px;
  display: grid;
  gap: 8px;
}
.inputs-upload-item {
  border: 1px solid rgba(255,255,255,0.16);
  border-radius: 10px;
  background: rgba(255,255,255,0.04);
  padding: 8px 10px;
}
.inputs-upload-item .file-name {
  color: #f4f4f6;
  font-size: 16px;
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.inputs-upload-item .file-meta {
  margin-top: 2px;
  color: rgba(255,255,255,0.68);
  font-size: 16px;
  font-weight: 600;
}

/* F0 only — Next button sits ABOVE the Dreamer avatar with a 16px gap,
   per Figma file jTJhFMdTvwR91g8x8Z5WKb node 63:235:
     - 63:290 (next button)   left=834 top=904 size=60×60
     - 63:286 (dreamer ring)  left=828 top=980 size=72×72
   The base .next-button rule (line 719) places it at top=1082 which is
   below the dreamer — correct for F1/F2/F3 (kept by :not(F0) selector
   on line 852). On F0 the order flips: button up, avatar down. The
   16px gap (980 - (904+60) = 16) is the user-quoted spacing 2026-05-14. */
.figma-stage[data-screen="F0"] .next-button {
  /* User 2026-05-15: F0 row restructure shifted everything below the
     agent-row up by 73 px. Next-button-above-dreamer follows: 904 → 831.
     Then "dreamer and next buttons went way down, move them together"
     — dreamer-ring + next-button both −73 to keep them glued.
     Then another −5 with below-agents shift → 758 → 753. */
  top: 753px !important;
}
/* User 2026-06-16: F1/F2 .next-button override removed. The
   :not(F0) ALIGNMENT CONTRACT rule below (top: 894) is the single
   source of truth for every non-F0 floor — F1, F2, F3 all share
   one chat-box line geometry now. Keeping a stale 904 here was
   how the dreamer ended up with the next-button "too much up"
   (the contract rule moved + this one didn't). */

/* F1 is BEFORE outputs exist — always greyed regardless of polling
   state (the API defaults aria-disabled to false in some env). For F2
   we keep aria-disabled-gated behaviour so it flips to enabled at PASS. */
.figma-stage[data-screen="F1"] .download-button {
  opacity: 0.45 !important;
  cursor: not-allowed !important;
  pointer-events: none;
}
.figma-stage[data-screen="F2"] .download-button[aria-disabled="true"] {
  opacity: 0.45 !important;
  cursor: not-allowed !important;
  pointer-events: none;
}

/* User 2026-05-15: F2 dash padding matches F1 — agent↔dash 20 design
   px instead of the default 8. Same symmetric layout: 3 dashes +
   box + 3 dashes between monday (680, right=752) and Grok (1015). */
.figma-stage[data-screen="F2"] .dash.d1 { left: 772px !important; }
.figma-stage[data-screen="F2"] .dash.d2 { left: 802px !important; }
.figma-stage[data-screen="F2"] .dash.d3 { left: 832px !important; }
.figma-stage[data-screen="F2"] .dash.d4 { left: 913px !important; }
.figma-stage[data-screen="F2"] .dash.d5 { left: 943px !important; }
.figma-stage[data-screen="F2"] .dash.d6 { left: 973px !important; }
.figma-stage[data-screen="F2"] .dash-box { left: 859px !important; }

/* User 2026-05-15: align Grok/Gemini label boxes with monday's.
   The .single-agent .team-name was forced to height:63 + top:0 by
   an earlier !important rule, while .member-monday .team-name kept
   the default height:40 + top:2. These overrides re-align the
   partner's label box to monday's spec.
   User 2026-06-26: extended to ALL non-F0 floors (was F1/F2 only).
   F3 + mobile reflow inherited the unaligned 63-tall .team-name
   which on mobile rendered as a giant gap above the Gemini logo
   while monday's gap stayed tight. Operator screenshot showed the
   Gemini logo sitting ~30px below where monday's logo sat. */
.figma-stage[data-screen]:not([data-screen="F0"]) .single-agent .team-name {
  top: 2px !important;
  height: 40px !important;
}
.figma-stage[data-screen]:not([data-screen="F0"]) .single-agent .team-role {
  top: 129px !important;
  height: 28px !important;
  min-height: 28px !important;
}

/* ═══════════════════════════════════════════════════════════════════
   User 2026-05-15: "match UI of floor 1, 2 and 2.1 to F0 — elements
   below the agent need to go up."

   The non-F0 floors had accumulated a separate set of layout
   coordinates (agent row at y=164, panels at 282, lower-card at 642,
   Lumi at 591, dreamer/next at 980/904) while F0 was tuned to a
   tighter layout (agents at 4, panels 195, lower-card 555, Lumi 411,
   dreamer 829, next 753). This authoritative block — last in the
   cascade, so it wins — pins EVERY non-F0 floor to F0's exact
   coordinates. F1/F2/F2.1 are now visually identical to F0; the only
   difference is the agent-row CONTENT (F0: logo + 4 members; non-F0:
   monday + one single-agent + the pulsing dashed connector, no logo).
   ═══════════════════════════════════════════════════════════════════ */

/* Agent row centred between stage top (0) and panel top (347).
   Block height ≈ 161px → equal margin = (347-161)/2 ≈ 93px. Using 100px
   for a natural slight-lower-than-centre feel. Avatar at 100+49=149,
   centre 149+36=185. Dashes top 183, dash-box top 163. */
/* User 2026-06-26 (v3 compact): equal top/bottom padding around the
   agent row + further trim so the Status + MVP card fit on 900-1000px
   viewports without scrolling.
     agent block height ≈ 156 (name 63 + avatar 72 + role 25).
     padding above the agent row = 20.
     padding below the agent row = 20.
     ⇒ agent top = 20, agent bottom = 176, panel top = 196.
   The dash + dash-box rules later in this file (their selectors win
   on source order) carry the matching 5/106-style alignment. */
.figma-stage[data-screen]:not([data-screen="F0"]) .member-monday,
.figma-stage[data-screen]:not([data-screen="F0"]) .single-agent {
  top: 20px !important;
}
.figma-stage[data-screen]:not([data-screen="F0"]) .agent-dashes .dash {
  /* dash midline target = avatar centre = 20 + 51 + 36 = 107.
     5px tall: top = 107 - 2.5 ≈ 105. */
  top: 105px !important;
}
.figma-stage[data-screen]:not([data-screen="F0"]) .dash-box {
  /* 43px tall: top = 107 - 21 ≈ 86. */
  top: 86px !important;
}

/* Below-agent elements → F0 coordinates. */
/* User 2026-06-11: close the dead 159px gap between prompt-card bottom
   (347+310=657) and lower-card (was 816). Shift lower-card to 687px
   (30px gap) and slide Lumi/dreamer/next up by the same −129px so
   dreamer-ring lands at 851px design = 709px CSS — visible on 800px
   viewports without scrolling. Panel stays at 347px (constrained by
   agent row which ends at ~320px). */
.figma-stage[data-screen]:not([data-screen="F0"]) .panel {
  top: 196px !important;
}
.figma-stage[data-screen]:not([data-screen="F0"]) .prompt-card {
  top: 196px !important;
}
.figma-stage[data-screen]:not([data-screen="F0"]) .lower-card {
  top: 536px !important;
  /* User 2026-06-16 (v2 contract): bump lower-card height 310 → 330
     so its BOTTOM (1017) aligns with the Status + PDF panels' BOTTOM
     (top 347 + height 670 = 1017). The three cards now share one
     baseline. */
  height: 330px !important;
}
/* ALIGNMENT CONTRACT v3 (user 2026-06-16):
   Lumi straddles the BOTTOM of her own text field (prompt-card
   bottom) the same way the dreamer straddles the lower-card
   bottom. Both avatars sit ON the bottom line of their reference
   card — Lumi on her bubble's bottom, dreamer on the input's
   bottom. The pair reads as two bookends, both anchored to a
   bottom edge.

     prompt-card:  top 347, height 310 → BOTTOM 657 (Lumi's line)
     lower-card:   top 687, height 330 → BOTTOM 1017 (dreamer's line)
                   (extended so the three middle-row cards share one
                   bottom line with Status + PDF panels)
     panels:       top 347, height 670 → BOTTOM 1017

   - Lumi: visible CENTER on prompt-card BOTTOM (657). The character
     fills the 134×180 container at proportion 1227/1536; visible
     top = container_top + 47.3, visible bottom = container_top + 143.8,
     so visible center = container_top + 95.55. Top = 657 − 95.55 ≈ 561.

   - Dreamer: avatar CENTER on lower-card BOTTOM (1017). The 72×72
     ring centered on 1017 → top = 1017 − 36 = 981.

   - Next-button: 16px ABOVE dreamer (Figma 63:290 vs 63:286).
     51-tall button, bottom = 981 − 16 = 965, top = 965 − 51 = 914.

   Do NOT touch these values without updating the four card-geometry
   numbers above — the math is the source of truth. "Move it up to
   fit 800px viewports" lost the dreamer the chat line twice; the
   scale transform handles small viewports via --scale. */
.figma-stage[data-screen]:not([data-screen="F0"]) .ai-avatar {
  /* prompt-card bottom = 196 + 310 = 506. Lumi visible-centre on
     that line, top = 506 - 95.55 ≈ 410. */
  top: 410px !important;
  width: 134px !important;
  height: 180px !important;
}
.figma-stage[data-screen]:not([data-screen="F0"]) .dreamer-ring {
  /* lower-card bottom = 536 + 330 = 866. Ring centre on that line,
     top = 866 - 36 = 830. */
  top: 830px !important;
}
.figma-stage[data-screen]:not([data-screen="F0"]) .next-button {
  /* 16px above dreamer; 51-tall button → top = 830 - 16 - 51 = 763. */
  top: 763px !important;
}
/* Lumi typing-cursor. Non-F0 lower-card top = 687; placeholder
   centre = 687 + 21 = 708.
   User 2026-06-15 (MCP probe): same 14 px viewport drift as F0 —
   bumped 708 → 722 so the empty-state cursor lands on the first-
   line baseline instead of floating above it. */
.figma-stage[data-screen]:not([data-screen="F0"]) .lumi-typing-cursor {
  /* lower-card top 536 + 21 + 14 viewport drift ≈ 571. */
  top: 571px !important;
}

/* ═══════════════════════════════════════════════════════════════════
   User 2026-06-07 — FINAL non-F0 agent row alignment to Figma F2.1
   (file lhSVjkJ1p6U19S23NFwflD, node 0:1). This is the single source
   of truth; it sits last in the file so cascade-wise it wins over
   every prior conflicting rule.

   Source coords from Figma F2.1:
     - monday name:    x=583  y=164  (width 259)
     - monday avatar:  x=680  y=215  (72×72)
     - Project Pulse:  x=583  y=295
     - partner name:   x=919  y=164
     - partner avatar: x=1015 y=215  (72×72)
     - partner role:   x=926  y=295
     - dashes:         y=246  (height 5) — sits at avatar centre y≈251
     - box:            y=228  (43 tall) — wraps the dash midline

   User 2026-06-15: live in-browser MCP measurement on F1 showed
   259 viewport px of empty space between nav and the avatar circle
   (178 CSS after zoom). The 2026-06-14 fix at line ~1420 tried
   shift-up-by-80 but lost the cascade to this FINAL block (this
   block has more !important + appears later). Folding the −80
   intent in here so it actually sticks: monday/single-agent name
   y=84 (was 164), dashes y=166 (was 246), dash-box y=148 (was 228).
   Internal name+avatar+role offsets stay the same (flex column
   inside member-monday inherits the shift). Avatar centre lands at
   ~135+36=171, dash midline at ~168.5 — within 3px of each other,
   matches the original alignment intent.
   ═══════════════════════════════════════════════════════════════════ */
/* User 2026-06-20: the 4 ghost rules that used to live here (re-
   positioning monday at left:64 + display:flex column, plus the
   .team-name / .team-avatar / .team-role position:relative
   overrides) are GONE. They were leftovers from the briefly-broken
   "show all 4 agents in F0 slot positions" attempt. With the
   locked 2-agent layout restored, monday belongs at left:641
   (F1+F2 spec) / left:651 (default) via the rules at ~1633/1646,
   and the team-name/avatar/role inside use the original Figma
   absolute positions at lines ~1762+. */
/* User 2026-06-26 (v3 — match the equal-padding agent row at top:20):
   agent top 20 + avatar internal top 51 + half-height 36 = 107.
   Dash midline target 107: 5px tall → top = 107 - 2.5 ≈ 105.
   Box midline target 107: 43px tall → top = 107 - 21 ≈ 86.
   This rule sits LAST in the file so it wins source-order ties. */
.figma-stage[data-screen]:not([data-screen="F0"]) .agent-dashes .dash {
  top: 105px !important;
}
.figma-stage[data-screen]:not([data-screen="F0"]) .dash-box {
  top: 86px !important;
}

/* ═══════════════════════════════════════════════════════════════════
   User 2026-06-26 — F3 COMPACT vertical layout
   ───────────────────────────────────────────────────────────────────
   Operator on a 1680×1050 Mac watching the build screen during a video
   recording: "the padding above and under working agents is too big —
   make it minimal and tight so all elements fit on one screen". On F3
   the dreamer is waiting on Claude — long stretches looking at this
   floor, so the dead vertical space stands out more than on F1/F2.

   Compact deltas (F3 only — F1/F2 keep current spacing):
     agents:       top 100  →  40   (− 60)
     dashes:       top 183  →  123  (− 60, follows agents)
     dash-box:     top 163  →  103  (− 60, follows agents)
     panel:        top 347  →  240  (−107)
     prompt-card:  top 347  →  240  (−107)
     lower-card:   top 687  →  580  (−107)
     Lumi avatar:  top 561  →  454  (−107)
     dreamer-ring: top 981  →  874  (−107)
     next-button:  top 914  →  807  (−107)
     lumi-cursor:  top 722  →  615  (−107)

   Alignment contract preserved: prompt-card BOTTOM (550) carries Lumi;
   lower-card BOTTOM (910) carries dreamer; panel BOTTOM (910) shares
   the baseline. All math still works — just shifted up 107px so the
   used canvas height drops 1017 → 910.
   ═══════════════════════════════════════════════════════════════════ */
/* F3-specific block removed 2026-06-26 — the same values are now
   inlined into the non-F0 group above (operator wanted F1/F2/F3 to
   all share the compact layout). */
