@vibes.diy/base 14.3.0 → 14.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,10 +14,13 @@ import { CodingIndicator } from "./CodingIndicator.js";
14
14
  import { CopyLinkIconButton } from "./LinkLineControls.js";
15
15
  import { AttachmentButton, AttachmentChips } from "./AttachmentRow.js";
16
16
  import { useAttachments } from "../attachments/use-attachments.js";
17
+ import { composerFileDrop } from "../attachments/composer-file-drop.js";
17
18
  import { PeopleIcon } from "./icons/index.js";
18
19
  import { HOME_BRAND_FONT } from "./homeBrandFont.js";
19
20
  import { SAFE_AREA_TOP } from "../theme/safe-area.js";
20
21
  const UNIFIED_VIBE_CARD_DARK_VARS = `
22
+ --uvc-closed-build-bg: #28262f;
23
+ --uvc-closed-build-text: #b5afc2;
21
24
  --color-dark-primary: #e0e0e0;
22
25
  --color-dark-secondary: #aaaaaa;
23
26
  --color-dark-decorative-00: #333333;
@@ -30,7 +33,6 @@ const UNIFIED_VIBE_CARD_DARK_VARS = `
30
33
  values here so an explicit .dark host also darkens fields and borders. */
31
34
  --vibes-field-bg: var(--vibes-ink-deep, #141414);
32
35
  --vibes-card-border: var(--vibes-gray-mid, #555555);
33
- --uvc-card-shadow: rgba(255,255,255,0.75);
34
36
  --uvc-amber-text: #fbbf24;
35
37
  --uvc-blue-text: #93c5fd;
36
38
  --uvc-purple-text: #c4b5fd;
@@ -39,7 +41,6 @@ const UNIFIED_VIBE_CARD_DARK_VARS = `
39
41
  --uvc-suggestion-text: #e5e7eb;
40
42
  --uvc-helper-text: #c2cbd8;
41
43
  --uvc-build-text: #bac4d2;
42
- --uvc-nav-chat: #c4b5fd;
43
44
  --uvc-nav-edit: #fb923c;
44
45
  --uvc-nav-share: #4ade80;
45
46
  --uvc-nav-faq: #cbd5e1;
@@ -51,12 +52,200 @@ const UNIFIED_VIBE_CARD_DARK_VARS = `
51
52
  --color-light-background-00: var(--color-dark-background-00, #1a1a1a);
52
53
  --color-light-background-01: var(--color-dark-background-01, #222222);
53
54
  --color-light-background-02: var(--color-dark-background-02, #222222);`;
55
+ const UNIFIED_VIBE_CARD_GLASS_LIGHT_VARS = `
56
+ --uvc-submit-arrow:var(--vibes-cream, #fffff0);
57
+ --uvc-submit-fill:rgba(64,89,114,.2);
58
+ --uvc-notch-width: 52px;
59
+ --uvc-notch-opacity: .5;
60
+ --uvc-notch-fill: repeating-linear-gradient(0deg,rgba(255,255,255,.1) 0 .4px,transparent .4px 1px),linear-gradient(180deg,rgba(16,26,24,.65),rgba(16,26,24,.4) 30%,rgba(16,26,24,.9) 72%,rgba(16,26,24,.55));
61
+ --uvc-notch-shadow: inset 0 1px 0 rgba(255,255,255,.45),inset 0 -1px 1px rgba(0,0,0,.2),0 1px 2px rgba(16,26,24,.18);
62
+ --uvc-composer-border: rgba(16,26,24,.28);
63
+ --uvc-glass-fill: rgba(255,255,255,.15);
64
+ --uvc-glass-edge: rgba(255,255,255,.3);
65
+ --uvc-glass-highlight: rgba(255,255,255,.8);
66
+ --uvc-glass-shadow: 0 8px 32px rgba(0,0,0,.1), inset 0 1px 0 rgba(255,255,255,.5), inset 0 -1px 0 rgba(255,255,255,.1), inset 0 0 20px 10px rgba(255,255,255,1);
67
+ --color-light-background-00: rgba(255,255,255,.24);
68
+ --color-light-background-01: rgba(255,255,255,.2);
69
+ --uvc-suggestion-bg: rgba(255,255,255,.28);
70
+ --vibes-field-bg: rgba(255,255,255,.2);
71
+ --uvc-nav-home: #2563eb;`;
72
+ const UNIFIED_VIBE_CARD_GLASS_DARK_VARS = `
73
+ --uvc-submit-arrow:#aaa;
74
+ --uvc-submit-fill:#000;
75
+ --uvc-notch-opacity: .25;
76
+ --uvc-notch-width: 52px;
77
+ --uvc-notch-fill: repeating-linear-gradient(90deg,rgba(255,255,255,.18) 0 .6px,transparent .6px 2px),linear-gradient(180deg,rgba(230,255,245,.6),rgba(230,255,245,.3) 40%,rgba(210,239,228,.12) 58%,rgba(210,239,228,.2));
78
+ --uvc-notch-shadow: inset 0 0 0 1px rgba(230,255,245,.2),0 1px 2px rgba(0,0,0,.3);
79
+ --uvc-composer-border: rgba(210,239,228,.28);
80
+ --uvc-glass-fill: rgba(16,26,24,.72);
81
+ --uvc-glass-edge: rgba(210,239,228,.28);
82
+ --uvc-glass-highlight: rgba(230,255,245,.32);
83
+ --uvc-glass-shadow: 0 8px 32px rgba(0,0,0,.24), inset 0 1px 0 rgba(255,255,255,.22), inset 0 -1px 0 rgba(255,255,255,.05), inset 0 0 20px 4px rgba(210,239,228,.07);
84
+ --color-light-background-00: rgba(16,26,24,.28);
85
+ --color-light-background-01: rgba(16,26,24,.24);
86
+ --uvc-suggestion-bg: rgba(16,26,24,.32);
87
+ --vibes-field-bg: rgba(16,26,24,.28);
88
+ --uvc-nav-home: #93b4ff;`;
89
+ const UNIFIED_VIBE_CARD_OBSIDIAN_VARS = `
90
+ --uvc-glass-fill: rgba(6,9,10,.94);
91
+ --uvc-glass-edge: rgba(255,255,255,.12);
92
+ --uvc-glass-highlight: rgba(255,255,255,.25);
93
+ --uvc-glass-shadow: 0 4px 16px rgba(0,0,0,.28), inset 0 1px 0 rgba(255,255,255,.12), inset 0 -1px 0 rgba(255,255,255,.035), inset 0 0 12px rgba(255,255,255,.02);
94
+ --uvc-nav-home: #93b4ff;`;
54
95
  const unifiedVibeCardDarkCss = `
96
+ [data-unified-vibe-card] {${UNIFIED_VIBE_CARD_GLASS_LIGHT_VARS}
97
+ }
55
98
  @media (prefers-color-scheme: dark) {
56
- [data-unified-vibe-card], [data-vibe-handle-menu], [data-vibe-version-menu] {${UNIFIED_VIBE_CARD_DARK_VARS}
99
+ [data-unified-vibe-card], [data-vibe-handle-menu], [data-vibe-version-menu], [data-vibe-closed-build-status] {${UNIFIED_VIBE_CARD_DARK_VARS}
100
+ }
101
+ [data-unified-vibe-card] {${UNIFIED_VIBE_CARD_GLASS_DARK_VARS}
57
102
  }
58
103
  }
59
- .dark [data-unified-vibe-card], .dark [data-vibe-handle-menu], .dark [data-vibe-version-menu] {${UNIFIED_VIBE_CARD_DARK_VARS}
104
+ .dark [data-unified-vibe-card], .dark [data-vibe-handle-menu], .dark [data-vibe-version-menu], .dark [data-vibe-closed-build-status] {${UNIFIED_VIBE_CARD_DARK_VARS}
105
+ }
106
+ .dark [data-unified-vibe-card] {${UNIFIED_VIBE_CARD_GLASS_DARK_VARS}
107
+ }
108
+ /* Navigation uses clear glass in light mode and Obsidian in dark mode.
109
+ Give the navigation light strokes without retheming the card. */
110
+ [data-vibe-card-nav] {${UNIFIED_VIBE_CARD_DARK_VARS}
111
+ }
112
+ [data-vibe-switch-glass] {${UNIFIED_VIBE_CARD_OBSIDIAN_VARS}
113
+ }
114
+ [data-vibe-card-nav] {
115
+ --uvc-glass-fill:rgba(255,255,255,.08);
116
+ --uvc-glass-edge:rgba(255,255,255,.3);
117
+ --uvc-glass-highlight:rgba(255,255,255,.8);
118
+ --uvc-glass-shadow:0 4px 16px rgba(0,0,0,.1),inset 0 1px 0 rgba(255,255,255,.35),inset 0 -1px 0 rgba(255,255,255,.08);
119
+ --uvc-nav-home:#93b4ff;
120
+ }
121
+ /* The card's layout still includes its footer. Only its glass surface ends
122
+ at the former divider: retain the outer 1px border and footer's 1px top
123
+ border as transparent spacing so the circles and switch never move. */
124
+ [data-unified-vibe-card]::before {
125
+ content: ""; position: absolute; inset: -1px -1px 50px; border-radius: 14px;
126
+ }
127
+ /* The agent panel retains its existing 50px switch reservation, without the
128
+ ordinary nav row's extra divider pixel. Its body ends one pixel lower. */
129
+ [data-unified-vibe-card][data-vibe-agent-panel="true"]::before { bottom: 49px; }
130
+ [data-vibe-card-nav] { position: relative; isolation: isolate; }
131
+ [data-vibe-card-nav]::before {
132
+ content: ""; position: absolute; left: 2px; top: 50%; transform: translateY(-50%); z-index: -1;
133
+ width: calc(3 * var(--uvc-nav-size, 38px) + 2 * var(--uvc-nav-gap, 6px) + 12px);
134
+ height: calc(var(--uvc-nav-size, 38px) + 10px); border-radius: 999px;
135
+ }
136
+ [data-unified-vibe-card]::before, [data-vibe-card-nav]::before, [data-vibe-switch-glass]::before {
137
+ box-sizing: border-box; border: 1px solid transparent; pointer-events: none;
138
+ }
139
+ [data-unified-vibe-card]::before, [data-vibe-card-nav]::before, [data-vibe-switch-glass]::before {
140
+ background: linear-gradient(to right, transparent, var(--uvc-glass-highlight), transparent) top / 100% 1px no-repeat,
141
+ linear-gradient(to bottom, var(--uvc-glass-highlight), transparent, var(--uvc-glass-edge)) left / 1px 100% no-repeat,
142
+ var(--uvc-glass-fill);
143
+ backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px);
144
+ box-shadow: var(--uvc-glass-shadow);
145
+ }
146
+ [data-vibe-card-nav]::before { backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px); }
147
+ @media (prefers-color-scheme: dark) {
148
+ [data-vibe-card-nav] {${UNIFIED_VIBE_CARD_OBSIDIAN_VARS}}
149
+ [data-vibe-card-nav]::before { backdrop-filter:blur(24px); -webkit-backdrop-filter:blur(24px); }
150
+ }
151
+ .dark [data-vibe-card-nav] {${UNIFIED_VIBE_CARD_OBSIDIAN_VARS}}
152
+ .dark [data-vibe-card-nav]::before { backdrop-filter:blur(24px); -webkit-backdrop-filter:blur(24px); }
153
+ [data-vibe-card-nav] [data-vibe-nav-selected="true"] svg { stroke-width:3; }
154
+ /* The arrival uses the fitted glass surface, leaving the brand artwork untouched.
155
+ Keying the layer by status replays same-color arrivals without a JS timer. */
156
+ [data-vibe-switch-glass]:has([data-vibe-switch-highlight])::before { visibility:hidden; }
157
+ [data-vibe-switch-rest] {
158
+ background:linear-gradient(to right,transparent,var(--uvc-glass-highlight),transparent) top/100% 1px no-repeat,
159
+ linear-gradient(to bottom,var(--uvc-glass-highlight),transparent,var(--uvc-glass-edge)) left/1px 100% no-repeat,var(--uvc-glass-fill);
160
+ box-shadow:var(--uvc-glass-shadow);
161
+ animation:uvc-notification-rest 7s forwards;
162
+ }
163
+ [data-vibe-switch-highlight] {
164
+ --uvc-arrival-fill:color-mix(in srgb,currentColor 80%,transparent);
165
+ --uvc-arrival-edge:color-mix(in srgb,color-mix(in srgb,currentColor 50%,white) 60%,transparent);
166
+ --uvc-arrival-highlight:color-mix(in srgb,color-mix(in srgb,currentColor 20%,white) 90%,transparent);
167
+ background:linear-gradient(to right,transparent,var(--uvc-arrival-highlight),transparent) top/100% 1px no-repeat,
168
+ linear-gradient(to bottom,var(--uvc-arrival-highlight),transparent,var(--uvc-arrival-edge)) left/1px 100% no-repeat,var(--uvc-arrival-fill);
169
+ box-shadow:0 4px 16px rgba(0,0,0,.2),inset 0 1px 0 var(--uvc-arrival-highlight),inset 0 -1px 0 var(--uvc-arrival-edge),inset 0 0 12px rgba(255,255,255,.06);
170
+ animation:uvc-notification-glass 7s forwards;
171
+ }
172
+ /* Preserve the original Amber material for the yellow/pending status. */
173
+ [data-vibe-switch-highlight][data-arrival-amber="true"] {
174
+ --uvc-arrival-fill:rgba(191,142,66,.8);
175
+ --uvc-arrival-edge:rgba(255,229,167,.6);
176
+ --uvc-arrival-highlight:rgba(255,246,218,.9);
177
+ }
178
+ [data-vibe-switch-highlight], [data-vibe-switch-rest] {
179
+ box-sizing:border-box; border:1px solid transparent; pointer-events:none;
180
+ backdrop-filter:blur(24px); -webkit-backdrop-filter:blur(24px);
181
+ }
182
+ @keyframes uvc-notification-rest {
183
+ 0%,100% { opacity:1; }
184
+ 14.3%,71.4% { opacity:0; }
185
+ }
186
+ @keyframes uvc-notification-glass {
187
+ 0% { opacity:0; }
188
+ 14.3%,71.4% { opacity:1; }
189
+ 100% { opacity:0; }
190
+ }
191
+ @media (prefers-reduced-motion:reduce) {
192
+ [data-vibe-switch-highlight] { animation-name:uvc-notification-glass-reduced; }
193
+ [data-vibe-switch-rest] { animation-name:uvc-notification-rest-reduced; }
194
+ }
195
+ @keyframes uvc-notification-rest-reduced {
196
+ 0%,71.4% { opacity:0; }
197
+ 100% { opacity:1; }
198
+ }
199
+ @keyframes uvc-notification-glass-reduced {
200
+ 0%,71.4% { opacity:1; }
201
+ 100% { opacity:0; }
202
+ }
203
+ /* Fit the painted 102px logo, excluding the SVG's transparent side margins.
204
+ Match the nav oval's height without moving the existing 120×44 hit target. */
205
+ [data-vibe-switch-glass] { isolation: isolate; }
206
+ [data-vibe-switch-glass]::before, [data-vibe-switch-highlight], [data-vibe-switch-rest] {
207
+ content: ""; position: absolute; left: 6px; right: 6px; top: calc(50% + 1px);
208
+ height: calc(var(--uvc-nav-size, 38px) + 10px); transform: translateY(-50%);
209
+ border-radius: 999px; z-index: -1;
210
+ }
211
+ [data-unified-vibe-card] form.vibes-edit-field {
212
+ --vibes-card-border: var(--uvc-composer-border);
213
+ }
214
+ /* The selected Frost header rule fades at both ends at half opacity,
215
+ without consuming any of the card's content height. */
216
+ [data-vibe-card-header]::after {
217
+ content: ""; position: absolute; bottom: 0; left: 0; right: 0; height: 1px;
218
+ background: linear-gradient(90deg, transparent, var(--uvc-glass-edge) 22%, var(--uvc-glass-edge) 78%, transparent);
219
+ opacity: .5; pointer-events: none;
220
+ }
221
+ /* A · Line: the clip edge sits under the painted switch, inside the 120px
222
+ hit box (whose SVG has transparent air at its left). 22px padding leaves
223
+ the resting strip at right:132px without changing the switch geometry. */
224
+ [data-vibe-closed-build-status] {
225
+ position: absolute; left: 12px; right: 110px; bottom: 17px;
226
+ z-index: 2; display: flex; justify-content: flex-end; padding-right: 22px;
227
+ overflow: hidden; pointer-events: none; box-sizing: border-box;
228
+ }
229
+ [data-vibe-closed-build-line] {
230
+ min-width: 0; max-width: 100%; box-sizing: border-box;
231
+ padding: 3px 5px; border-radius: 3px;
232
+ background: var(--uvc-closed-build-bg, #fff);
233
+ color: var(--uvc-closed-build-text, #6d6973);
234
+ font: 12px/18px Inter, system-ui, sans-serif;
235
+ transform: translateX(calc(100% + 22px));
236
+ transition: transform 240ms cubic-bezier(.22,.68,0,1);
237
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
238
+ }
239
+ [data-vibe-closed-build-line][data-revealed="true"] { transform: translateX(0); }
240
+ [data-vibe-closed-build-line] [data-testid="thinking-line"] {
241
+ display: flex; align-items: baseline; gap: 6px; min-width: 0;
242
+ }
243
+ [data-vibe-closed-build-line] [data-testid="thinking-line-glyph"] { flex-shrink: 0; }
244
+ [data-vibe-closed-build-line] [data-testid="thinking-line-verb"] {
245
+ min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
246
+ }
247
+ @media (prefers-reduced-motion: reduce) {
248
+ [data-vibe-closed-build-line] { transition: none; }
60
249
  }
61
250
  /* Composer submit button: a blue pulsating glow while the field holds unsent
62
251
  content, nudging the user to send it — the same "something's live, act on it"
@@ -126,14 +315,14 @@ const unifiedVibeCardDarkCss = `
126
315
  animation: none;
127
316
  }
128
317
  }
129
- /* Four shrink-proof nav circles share the footer with the 120px logo spacer.
318
+ /* Three shrink-proof nav circles (Home / Edit / Share) share the footer with the 120px logo spacer.
130
319
  Compact size/gap steps keep them clear of the logo on narrow phones without
131
320
  flex shrinking circles into ovals. */
132
321
  @media (max-width: 400px) {
133
- [data-unified-vibe-card] { --uvc-nav-size: 34px; --uvc-nav-gap: 4px; }
322
+ [data-unified-vibe-card], [data-vibe-switch-glass] { --uvc-nav-size: 34px; --uvc-nav-gap: 4px; }
134
323
  }
135
324
  @media (max-width: 360px) {
136
- [data-unified-vibe-card] { --uvc-nav-size: 30px; }
325
+ [data-unified-vibe-card], [data-vibe-switch-glass] { --uvc-nav-size: 30px; }
137
326
  }
138
327
  /* Composer input row — a FIELD, not a button (Storybook audition C2, #4578).
139
328
  It used to wear a hard 2px/3px offset shadow over the card's own grey, which
@@ -220,7 +409,7 @@ const unifiedVibeCardDarkCss = `
220
409
  }
221
410
  [data-vibe-card-editor-tabs] [role="tab"] {
222
411
  min-height: 36px;
223
- padding-inline: 4px !important;
412
+ padding-inline: 3px !important;
224
413
  font-size: 14px !important;
225
414
  }
226
415
  [data-unified-vibe-card] [data-testid="build-card"] {
@@ -253,8 +442,63 @@ const unifiedVibeCardDarkCss = `
253
442
  }`;
254
443
  const CARD_VIEWPORT_HEIGHT = "80%";
255
444
  const CARD_FULL_VIEWPORT_HEIGHT = "94%";
445
+ function ClosedBuildStatus({ visible, content }) {
446
+ const [retainedContent, setRetainedContent] = useState(visible ? content : undefined);
447
+ const [revealed, setRevealed] = useState(false);
448
+ const [reducedMotion, setReducedMotion] = useState(false);
449
+ useEffect(() => {
450
+ if (typeof window.matchMedia !== "function")
451
+ return;
452
+ const media = window.matchMedia("(prefers-reduced-motion: reduce)");
453
+ function updateMotion() {
454
+ setReducedMotion(media.matches);
455
+ }
456
+ updateMotion();
457
+ media.addEventListener("change", updateMotion);
458
+ return () => media.removeEventListener("change", updateMotion);
459
+ }, []);
460
+ useEffect(() => {
461
+ if (visible)
462
+ setRetainedContent(content);
463
+ }, [visible, content]);
464
+ useEffect(() => {
465
+ let firstFrame;
466
+ let secondFrame;
467
+ let exitTimer;
468
+ if (visible) {
469
+ if (reducedMotion)
470
+ setRevealed(true);
471
+ else
472
+ firstFrame = requestAnimationFrame(() => {
473
+ secondFrame = requestAnimationFrame(() => setRevealed(true));
474
+ });
475
+ }
476
+ else {
477
+ setRevealed(false);
478
+ if (reducedMotion)
479
+ setRetainedContent(undefined);
480
+ else
481
+ exitTimer = setTimeout(() => setRetainedContent(undefined), 240);
482
+ }
483
+ return () => {
484
+ if (firstFrame !== undefined)
485
+ cancelAnimationFrame(firstFrame);
486
+ if (secondFrame !== undefined)
487
+ cancelAnimationFrame(secondFrame);
488
+ clearTimeout(exitTimer);
489
+ };
490
+ }, [visible, reducedMotion]);
491
+ if (retainedContent === undefined || retainedContent === null)
492
+ return undefined;
493
+ return (React.createElement("div", { "data-vibe-closed-build-status": true, "data-testid": "closed-build-status", "aria-hidden": visible ? undefined : true, inert: !visible },
494
+ React.createElement("div", { "data-vibe-closed-build-line": true, "data-revealed": revealed }, retainedContent)));
495
+ }
256
496
  export function UnifiedVibeCard(props) {
257
497
  const { appTitle, appSlug, isTwinkling, isTeasing, open: controlledOpen, onOpenChange } = props;
498
+ const switchGlassColor = props.switchOvalColor ??
499
+ (props.versionLocked || props.publishState === "draft" || props.onViewDraft !== undefined
500
+ ? ovalColors.yellow
501
+ : ovalColors.neutral);
258
502
  const [internalOpen, setInternalOpen] = useState(false);
259
503
  const open = controlledOpen ?? internalOpen;
260
504
  const setOpen = (next) => {
@@ -413,10 +657,11 @@ export function UnifiedVibeCard(props) {
413
657
  setEverOpened(true);
414
658
  }, [open]);
415
659
  const retained = Boolean(props.keepMounted) && everOpened;
416
- const scrollRegionRef = useRef(null);
660
+ const internalScrollRegionRef = useRef(null);
661
+ const scrollRegionRef = props.scrollContainerRef ?? internalScrollRegionRef;
417
662
  const chipsRegionRef = useRef(null);
418
663
  const stickToBottomRef = useRef(true);
419
- const hasHistory = props.historyBody != null && props.body == null;
664
+ const hasHistory = (props.historyBody != null || props.streamBody != null) && props.body == null;
420
665
  const streamLive = props.streamBody != null && props.streamSettled !== true;
421
666
  const chipBuildLive = streamLive && props.activeChip !== undefined;
422
667
  const visibleChips = props.activeChip === undefined || props.chips?.slice(0, 3).includes(props.activeChip) === true
@@ -571,7 +816,8 @@ export function UnifiedVibeCard(props) {
571
816
  const resizeValueNow = Math.round(Math.min(Math.max(userHeight ?? cardRef.current?.offsetHeight ?? 0, RESIZE_MIN), resizeValueMax));
572
817
  return (React.createElement(React.Fragment, null,
573
818
  React.createElement("style", null, unifiedVibeCardDarkCss),
574
- !hidden && (mounted || retained) && (React.createElement("div", { ref: cardRef, role: "dialog", "aria-label": "Vibe menu", "data-unified-vibe-card": true, className: props.className, "aria-hidden": mounted ? undefined : true, inert: mounted ? undefined : true, style: {
819
+ React.createElement(ClosedBuildStatus, { visible: open === false && hidden === false && props.closedBuildStatus !== undefined && props.closedBuildStatus !== null, content: props.closedBuildStatus }),
820
+ !hidden && (mounted || retained) && (React.createElement("div", { ref: cardRef, role: "dialog", "aria-label": "Vibe menu", "data-unified-vibe-card": true, "data-vibe-agent-panel": agentPanelActive, className: props.className, "aria-hidden": mounted ? undefined : true, inert: mounted ? undefined : true, style: {
575
821
  position: "absolute",
576
822
  left: 2,
577
823
  right: 2,
@@ -580,12 +826,11 @@ export function UnifiedVibeCard(props) {
580
826
  borderRadius: 14,
581
827
  maxHeight: cardHeight,
582
828
  height: cardHeight,
583
- overflow: "hidden",
829
+ overflow: "visible",
584
830
  display: "flex",
585
831
  flexDirection: "column",
586
- background: "var(--color-light-background-00, #fff)",
587
- border: "1px solid var(--vibes-near-black, #1a1a1a)",
588
- boxShadow: "0 8px 40px var(--uvc-card-shadow, rgba(0,0,0,0.75))",
832
+ background: "transparent",
833
+ border: "1px solid transparent",
589
834
  transformOrigin: "bottom right",
590
835
  transform: grown ? "scale(1)" : "scale(0)",
591
836
  transition: dragging ? scaleTransition : `${scaleTransition}, max-height 0.24s ease, height 0.24s ease`,
@@ -593,6 +838,7 @@ export function UnifiedVibeCard(props) {
593
838
  ...(mounted ? null : { visibility: "hidden" }),
594
839
  } },
595
840
  React.createElement("div", { "data-vibe-card-content": true, style: {
841
+ position: "relative",
596
842
  display: "flex",
597
843
  flexDirection: "column",
598
844
  minHeight: 0,
@@ -600,216 +846,229 @@ export function UnifiedVibeCard(props) {
600
846
  opacity: grown ? 1 : 0,
601
847
  transition: grown ? "opacity 0.18s ease 0.14s" : "opacity 0.1s ease",
602
848
  }, className: "text-light-primary dark:text-dark-primary" },
603
- React.createElement("div", { style: {
849
+ React.createElement("div", { "data-vibe-card-body": true, style: {
604
850
  display: "flex",
605
- alignItems: "center",
606
- gap: 8,
607
- padding: "10px 10px 10px",
608
- flexShrink: 0,
609
- position: "relative",
610
- userSelect: "none",
851
+ flexDirection: "column",
852
+ flex: "1 1 auto",
853
+ minHeight: 0,
854
+ overflow: "hidden",
855
+ borderRadius: 13,
611
856
  } },
612
- React.createElement("div", { "data-testid": "vibe-card-resize-handle", role: "separator", "aria-orientation": "horizontal", "aria-label": "Resize chat height", "aria-valuemin": RESIZE_MIN, "aria-valuemax": resizeValueMax, "aria-valuenow": resizeValueNow, tabIndex: 0, onPointerDown: onHandlePointerDown, onPointerMove: onHandlePointerMove, onPointerUp: endHandleDrag, onPointerCancel: cancelHandleDrag, onKeyDown: onHandleKeyDown, onDoubleClick: () => clearForcedHeight(), style: {
613
- position: "absolute",
614
- top: 0,
615
- left: 0,
616
- right: 0,
617
- bottom: 0,
618
- zIndex: 0,
619
- cursor: "ns-resize",
620
- touchAction: "none",
621
- } },
622
- React.createElement("span", { "aria-hidden": true, style: {
623
- position: "absolute",
624
- top: 4,
625
- left: "50%",
626
- transform: "translateX(-50%)",
627
- width: 44,
628
- height: 4,
629
- borderRadius: 999,
630
- background: "var(--color-light-decorative-01, #ddd)",
631
- } })),
632
- React.createElement("div", { style: {
633
- position: "relative",
634
- width: 30,
635
- height: 30,
636
- borderRadius: 8,
857
+ React.createElement("div", { "data-vibe-card-header": true, style: {
858
+ display: "flex",
859
+ alignItems: "center",
860
+ gap: 8,
861
+ padding: "10px 10px 10px",
637
862
  flexShrink: 0,
638
- overflow: "hidden",
639
- background: "linear-gradient(160deg,#312e81,#4c1d95)",
640
- border: "1px solid rgba(0,0,0,0.15)",
641
- pointerEvents: "none",
863
+ position: "relative",
864
+ userSelect: "none",
642
865
  } },
643
- props.appIconUrl && (React.createElement("img", { src: props.appIconUrl, alt: "", style: { width: "100%", height: "100%", objectFit: "cover", transform: "scale(2)" } })),
644
- React.createElement(CodingIndicator, { active: props.coding === true })),
645
- React.createElement("div", { style: { lineHeight: 1.2, minWidth: 0, flex: "1 1 auto" } },
646
- React.createElement("strong", { style: {
647
- fontFamily: HOME_BRAND_FONT,
648
- display: "block",
649
- fontSize: 13,
650
- lineHeight: 1.3,
866
+ React.createElement("div", { "data-testid": "vibe-card-resize-handle", role: "separator", "aria-orientation": "horizontal", "aria-label": "Resize chat height", "aria-valuemin": RESIZE_MIN, "aria-valuemax": resizeValueMax, "aria-valuenow": resizeValueNow, tabIndex: 0, onPointerDown: onHandlePointerDown, onPointerMove: onHandlePointerMove, onPointerUp: endHandleDrag, onPointerCancel: cancelHandleDrag, onKeyDown: onHandleKeyDown, onDoubleClick: () => clearForcedHeight(), style: {
867
+ position: "absolute",
868
+ top: 0,
869
+ left: 0,
870
+ right: 0,
871
+ bottom: 0,
872
+ zIndex: 0,
873
+ cursor: "ns-resize",
874
+ touchAction: "none",
875
+ } },
876
+ React.createElement("span", { "aria-hidden": true, style: {
877
+ position: "absolute",
878
+ top: 4,
879
+ left: "50%",
880
+ transform: "translateX(-50%)",
881
+ width: "var(--uvc-notch-width, 44px)",
882
+ height: 4,
883
+ borderRadius: 999,
884
+ background: "var(--uvc-notch-fill)",
885
+ boxShadow: "var(--uvc-notch-shadow)",
886
+ opacity: "var(--uvc-notch-opacity)",
887
+ } })),
888
+ React.createElement("div", { style: {
889
+ position: "relative",
890
+ width: 30,
891
+ height: 30,
892
+ borderRadius: 8,
893
+ flexShrink: 0,
651
894
  overflow: "hidden",
652
- textOverflow: "ellipsis",
653
- whiteSpace: "nowrap",
654
- } }, appTitle),
655
- appSlug &&
656
- (props.onOpenApp === undefined && props.onCopyAppUrl === undefined ? (React.createElement("div", { className: "text-light-secondary dark:text-dark-secondary", style: { fontSize: 12, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, appSlug)) : (React.createElement("div", { "data-vibe-card-header-controls": "true", "data-testid": "vibe-card-slug-line", style: { position: "relative", zIndex: 1, display: "flex", alignItems: "center", gap: 4, minWidth: 0 } },
657
- props.onOpenApp === undefined ? (React.createElement("span", { className: "text-light-secondary dark:text-dark-secondary", style: { fontSize: 12, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, appSlug)) : (React.createElement("button", { type: "button", "data-testid": "vibe-card-slug-link", onClick: props.onOpenApp, className: "text-light-secondary dark:text-dark-secondary", style: {
658
- font: "inherit",
659
- fontSize: 12,
660
- lineHeight: "20px",
661
- overflow: "hidden",
662
- textOverflow: "ellipsis",
663
- whiteSpace: "nowrap",
664
- minWidth: 0,
665
- textDecoration: "underline",
666
- textUnderlineOffset: 2,
667
- background: "none",
668
- border: "none",
669
- padding: 0,
670
- cursor: "pointer",
671
- textAlign: "left",
672
- } }, appSlug)),
673
- props.onCopyAppUrl !== undefined && React.createElement(CopyLinkIconButton, { onCopy: props.onCopyAppUrl, color: "currentColor" }))))),
895
+ background: "var(--vibes-cream, #fffff0)",
896
+ border: "1px solid rgba(0,0,0,0.15)",
897
+ pointerEvents: "none",
898
+ } },
899
+ props.appIconUrl && (React.createElement("img", { src: props.appIconUrl, alt: "", style: { width: "100%", height: "100%", objectFit: "cover", transform: "scale(2)" } })),
900
+ React.createElement(CodingIndicator, { active: props.coding === true })),
901
+ React.createElement("div", { style: { lineHeight: 1.2, minWidth: 0, flex: "1 1 auto" } },
902
+ React.createElement("strong", { style: {
903
+ fontFamily: HOME_BRAND_FONT,
904
+ display: "block",
905
+ fontSize: 13,
906
+ lineHeight: 1.3,
907
+ overflow: "hidden",
908
+ textOverflow: "ellipsis",
909
+ whiteSpace: "nowrap",
910
+ } }, appTitle),
911
+ appSlug &&
912
+ (props.onOpenApp === undefined && props.onCopyAppUrl === undefined ? (React.createElement("div", { className: "text-light-secondary dark:text-dark-secondary", style: { fontSize: 12, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, appSlug)) : (React.createElement("div", { "data-vibe-card-header-controls": "true", "data-testid": "vibe-card-slug-line", style: { position: "relative", zIndex: 1, display: "flex", alignItems: "center", gap: 4, minWidth: 0 } },
913
+ props.onOpenApp === undefined ? (React.createElement("span", { className: "text-light-secondary dark:text-dark-secondary", style: {
914
+ fontSize: 12,
915
+ minWidth: 0,
916
+ overflow: "hidden",
917
+ textOverflow: "ellipsis",
918
+ whiteSpace: "nowrap",
919
+ } }, appSlug)) : (React.createElement("button", { type: "button", "data-testid": "vibe-card-slug-link", onClick: props.onOpenApp, className: "text-light-secondary dark:text-dark-secondary", style: {
920
+ font: "inherit",
921
+ fontSize: 12,
922
+ lineHeight: "20px",
923
+ overflow: "hidden",
924
+ textOverflow: "ellipsis",
925
+ whiteSpace: "nowrap",
926
+ minWidth: 0,
927
+ textDecoration: "underline",
928
+ textUnderlineOffset: 2,
929
+ background: "none",
930
+ border: "none",
931
+ padding: 0,
932
+ cursor: "pointer",
933
+ textAlign: "left",
934
+ } }, appSlug)),
935
+ props.onCopyAppUrl !== undefined && (React.createElement(CopyLinkIconButton, { onCopy: props.onCopyAppUrl, color: "currentColor" })))))),
936
+ (() => {
937
+ const badges = [
938
+ props.publishState === "old" ? React.createElement(DraftBadge, { key: "old", variant: "old" }) : null,
939
+ props.publishState === "draft" && !props.versionLocked ? React.createElement(DraftBadge, { key: "draft", variant: "draft" }) : null,
940
+ props.versionLocked ? React.createElement(LockedBadge, { key: "locked" }) : null,
941
+ !props.publishState && props.onViewDraft && !props.versionLocked ? (React.createElement(ViewDraftBadge, { key: "view-draft", onClick: props.onViewDraft })) : null,
942
+ ].filter(Boolean);
943
+ return badges.length === 0 ? null : (React.createElement("div", { "data-vibe-card-header-controls": "true", style: { position: "relative", zIndex: 1, display: "flex", alignItems: "center", gap: 10, flexShrink: 0 } }, badges));
944
+ })(),
945
+ React.createElement("div", { "data-vibe-card-header-controls": "true", style: {
946
+ position: "relative",
947
+ zIndex: 1,
948
+ marginLeft: "auto",
949
+ display: "flex",
950
+ alignItems: "center",
951
+ gap: 10,
952
+ flexShrink: 0,
953
+ maxWidth: "38%",
954
+ minWidth: 0,
955
+ } }, props.hideViewerTag ? null : props.handleSlug ? (React.createElement("div", { ref: pickerWrapRef, style: { position: "relative", minWidth: 0, maxWidth: "100%" } },
956
+ React.createElement(ViewerTagView, { slug: props.handleSlug, displayName: `@${props.handleSlug}`, avatarUrl: props.handleAvatarUrl, editable: Boolean(props.onPickAvatar), onPickFile: props.onPickAvatar, onTagClick: pickerInteractive ? () => setPickerOpen(!pickerOpen) : undefined, tagExpanded: pickerOpen, tagLabel: "Account and handles", style: {
957
+ background: "var(--color-light-background-01, #eee)",
958
+ border: "1px solid var(--color-light-decorative-01, #ddd)",
959
+ color: "var(--color-light-primary, #333)",
960
+ fontSize: 13,
961
+ padding: "3px 8px 3px 4px",
962
+ minWidth: 0,
963
+ maxWidth: "100%",
964
+ } }))) : props.viewerPending ? (React.createElement(ViewerTagView, { slug: "", pending: true })) : props.viewerNeedsHandle && !pickerInteractive ? (React.createElement(ViewerTagView, { slug: "", needsHandle: true, onChooseHandle: props.onChooseHandle })) : props.viewerNeedsHandle ? (React.createElement("div", { ref: pickerWrapRef },
965
+ React.createElement(ViewerTagView, { slug: "?", displayName: "Account", onTagClick: () => setPickerOpen(!pickerOpen), tagExpanded: pickerOpen, tagLabel: "Account and handles" }))) : pickerInteractive ? (React.createElement("div", { ref: pickerWrapRef },
966
+ React.createElement(ViewerTagView, { slug: "?", displayName: "Account", onTagClick: () => setPickerOpen(!pickerOpen), tagExpanded: pickerOpen, tagLabel: "Account and handles" }))) : (React.createElement(ViewerTagView, { slug: "?", anonymous: true, onSignIn: props.onSignIn })))),
674
967
  (() => {
675
- const badges = [
676
- props.publishState === "old" ? React.createElement(DraftBadge, { key: "old", variant: "old" }) : null,
677
- props.publishState === "draft" && !props.versionLocked ? React.createElement(DraftBadge, { key: "draft", variant: "draft" }) : null,
678
- props.versionLocked ? React.createElement(LockedBadge, { key: "locked" }) : null,
679
- !props.publishState && props.onViewDraft && !props.versionLocked ? (React.createElement(ViewDraftBadge, { key: "view-draft", onClick: props.onViewDraft })) : null,
680
- ].filter(Boolean);
681
- return badges.length === 0 ? null : (React.createElement("div", { "data-vibe-card-header-controls": "true", style: { position: "relative", zIndex: 1, display: "flex", alignItems: "center", gap: 10, flexShrink: 0 } }, badges));
968
+ const wizardBanner = (props.publishWizardActive === true || props.publishWizardError === true) && props.onPublish;
969
+ if (wizardBanner && props.selectedNav !== "share") {
970
+ return (React.createElement("div", { style: { padding: "0 14px", flexShrink: 0 } },
971
+ React.createElement(PublishBanner, { variant: props.publishState === "draft" || props.publishState === "old" ? props.publishState : undefined, onPublish: props.onPublish, publishing: props.publishing, wizardActive: props.publishWizardActive, wizardError: props.publishWizardError, escapeRevealed: props.publishEscapeRevealed, onPublishWithoutAccess: props.onPublishWithoutAccess, onRetryWizard: props.onRetryWizard })));
972
+ }
973
+ if (props.autoPublishError === true && props.selectedNav !== "share") {
974
+ return (React.createElement("div", { style: { padding: "0 14px", flexShrink: 0 } },
975
+ React.createElement(PublishBanner, { variant: undefined, autoPublishError: true, onRetryAutoPublish: props.onRetryAutoPublish })));
976
+ }
977
+ if (props.statusBanner && props.selectedNav !== "share") {
978
+ return React.createElement("div", { style: { padding: "0 14px", flexShrink: 0 } }, props.statusBanner);
979
+ }
980
+ if (props.versionLocked === true && props.selectedNav !== "share") {
981
+ return (React.createElement("div", { style: { padding: "0 14px", flexShrink: 0 } },
982
+ React.createElement(PublishBanner, { variant: undefined, locked: true, onUnlock: props.onUnlock })));
983
+ }
984
+ if (props.signedOutSavePrompt && props.selectedNav !== "share") {
985
+ return (React.createElement("div", { style: { padding: "0 14px", flexShrink: 0 } },
986
+ React.createElement(SignedOutSavePrompt, { onSignIn: props.onSignIn })));
987
+ }
988
+ if (props.selectedNav === "share")
989
+ return null;
990
+ const hasPendingVersion = props.publishState === "draft" || props.publishState === "old";
991
+ if (hasPendingVersion && Boolean(props.onPublish)) {
992
+ return (React.createElement("div", { style: { padding: "0 14px", flexShrink: 0 } },
993
+ React.createElement(PublishBanner, { variant: props.publishState === "draft" || props.publishState === "old" ? props.publishState : undefined, onPublish: props.onPublish, publishing: props.publishing })));
994
+ }
995
+ return null;
682
996
  })(),
683
- React.createElement("div", { "data-vibe-card-header-controls": "true", style: {
684
- position: "relative",
685
- zIndex: 1,
686
- marginLeft: "auto",
687
- display: "flex",
688
- alignItems: "center",
689
- gap: 10,
997
+ props.editorTab != null && props.selectedNav !== "share" && !agentPanelActive && (React.createElement("div", { "data-vibe-card-editor-tabs": true, style: { padding: "0 14px 6px", flexShrink: 0 } },
998
+ React.createElement(EditorTabStrip, { tab: props.editorTab, onTab: (t) => props.onEditorTab?.(t), standing: {
999
+ viewerMode: props.viewerMode,
1000
+ memberReadOnly: props.memberReadOnly,
1001
+ adminMode: props.adminMode,
1002
+ versionLocked: props.versionLocked,
1003
+ publishState: props.publishState,
1004
+ publishAccess: props.publishAccess,
1005
+ } }))),
1006
+ props.body == null && props.composerControls && !agentPanelActive && (React.createElement("div", { "data-testid": "vibe-card-composer-controls", "aria-hidden": streamLive ? true : undefined, inert: streamLive ? true : undefined, style: {
1007
+ padding: "0 14px",
1008
+ marginBottom: 6,
690
1009
  flexShrink: 0,
691
- maxWidth: "38%",
692
- minWidth: 0,
693
- } }, props.hideViewerTag ? null : props.handleSlug ? (React.createElement("div", { ref: pickerWrapRef, style: { position: "relative", minWidth: 0, maxWidth: "100%" } },
694
- React.createElement(ViewerTagView, { slug: props.handleSlug, displayName: `@${props.handleSlug}`, avatarUrl: props.handleAvatarUrl, editable: Boolean(props.onPickAvatar), onPickFile: props.onPickAvatar, onTagClick: pickerInteractive ? () => setPickerOpen(!pickerOpen) : undefined, tagExpanded: pickerOpen, tagLabel: "Account and handles", style: {
695
- background: "var(--color-light-background-01, #eee)",
696
- border: "1px solid var(--color-light-decorative-01, #ddd)",
697
- color: "var(--color-light-primary, #333)",
698
- fontSize: 13,
699
- padding: "3px 8px 3px 4px",
700
- minWidth: 0,
701
- maxWidth: "100%",
702
- } }))) : props.viewerPending ? (React.createElement(ViewerTagView, { slug: "", pending: true })) : props.viewerNeedsHandle && !pickerInteractive ? (React.createElement(ViewerTagView, { slug: "", needsHandle: true, onChooseHandle: props.onChooseHandle })) : props.viewerNeedsHandle ? (React.createElement("div", { ref: pickerWrapRef },
703
- React.createElement(ViewerTagView, { slug: "?", displayName: "Account", onTagClick: () => setPickerOpen(!pickerOpen), tagExpanded: pickerOpen, tagLabel: "Account and handles" }))) : pickerInteractive ? (React.createElement("div", { ref: pickerWrapRef },
704
- React.createElement(ViewerTagView, { slug: "?", displayName: "Account", onTagClick: () => setPickerOpen(!pickerOpen), tagExpanded: pickerOpen, tagLabel: "Account and handles" }))) : (React.createElement(ViewerTagView, { slug: "?", anonymous: true, onSignIn: props.onSignIn })))),
705
- (() => {
706
- const wizardBanner = (props.publishWizardActive === true || props.publishWizardError === true) && props.onPublish;
707
- if (wizardBanner && props.selectedNav !== "share") {
708
- return (React.createElement("div", { style: { padding: "0 14px", flexShrink: 0 } },
709
- React.createElement(PublishBanner, { variant: props.publishState === "draft" || props.publishState === "old" ? props.publishState : undefined, onPublish: props.onPublish, publishing: props.publishing, wizardActive: props.publishWizardActive, wizardError: props.publishWizardError, escapeRevealed: props.publishEscapeRevealed, onPublishWithoutAccess: props.onPublishWithoutAccess, onRetryWizard: props.onRetryWizard })));
710
- }
711
- if (props.autoPublishError === true && props.selectedNav !== "share") {
712
- return (React.createElement("div", { style: { padding: "0 14px", flexShrink: 0 } },
713
- React.createElement(PublishBanner, { variant: undefined, autoPublishError: true, onRetryAutoPublish: props.onRetryAutoPublish })));
714
- }
715
- if (props.statusBanner && props.selectedNav !== "share") {
716
- return React.createElement("div", { style: { padding: "0 14px", flexShrink: 0 } }, props.statusBanner);
717
- }
718
- if (props.versionLocked === true && props.selectedNav !== "share") {
719
- return (React.createElement("div", { style: { padding: "0 14px", flexShrink: 0 } },
720
- React.createElement(PublishBanner, { variant: undefined, locked: true, onUnlock: props.onUnlock })));
721
- }
722
- if (props.signedOutSavePrompt && props.selectedNav !== "share") {
723
- return (React.createElement("div", { style: { padding: "0 14px", flexShrink: 0 } },
724
- React.createElement(SignedOutSavePrompt, { onSignIn: props.onSignIn })));
725
- }
726
- if (props.selectedNav === "share")
727
- return null;
728
- const hasPendingVersion = props.publishState === "draft" || props.publishState === "old";
729
- if (hasPendingVersion && Boolean(props.onPublish)) {
730
- return (React.createElement("div", { style: { padding: "0 14px", flexShrink: 0 } },
731
- React.createElement(PublishBanner, { variant: props.publishState === "draft" || props.publishState === "old" ? props.publishState : undefined, onPublish: props.onPublish, publishing: props.publishing })));
732
- }
733
- return null;
734
- })(),
735
- props.editorTab != null && props.editorTab !== "edit-chat" && props.selectedNav !== "share" && !agentPanelActive && (React.createElement("div", { "data-vibe-card-editor-tabs": true, style: { padding: "0 14px 6px", flexShrink: 0 } },
736
- React.createElement(EditorTabStrip, { tab: props.editorTab, onTab: (t) => props.onEditorTab?.(t), standing: {
737
- viewerMode: props.viewerMode,
738
- memberReadOnly: props.memberReadOnly,
739
- adminMode: props.adminMode,
740
- versionLocked: props.versionLocked,
741
- publishState: props.publishState,
742
- publishAccess: props.publishAccess,
743
- } }))),
744
- props.body == null && props.composerControls && !agentPanelActive && (React.createElement("div", { "data-testid": "vibe-card-composer-controls", "aria-hidden": streamLive ? true : undefined, inert: streamLive ? true : undefined, style: {
745
- padding: "0 14px",
746
- marginBottom: 6,
747
- flexShrink: 0,
748
- ...(streamLive ? { visibility: "hidden" } : null),
749
- } }, props.composerControls)),
750
- React.createElement("div", { ref: scrollRegionRef, "data-testid": "vibe-card-scroll", style: {
751
- padding: "0 14px 4px",
752
- overflowY: "auto",
753
- overflowX: "hidden",
754
- flex: "1 1 auto",
755
- minHeight: 0,
756
- ...((props.bodyFill && props.body) || agentPanelActive || props.body == null
757
- ? { display: "flex", flexDirection: "column" }
758
- : null),
759
- } },
760
- props.retainedEditor != null && (React.createElement("div", { hidden: !props.editorVisible || agentPanelActive, inert: !props.editorVisible || agentPanelActive ? true : undefined, style: props.editorVisible && !agentPanelActive ? { flex: "1 1 auto", minHeight: 0 } : { display: "none" } }, props.retainedEditor)),
761
- agentPanelActive ? (React.createElement("div", { style: { flex: "1 1 auto", minHeight: 0 } }, props.agentPanel)) : props.editorVisible && props.retainedEditor != null ? null : props.editorTab === "standing" ? (React.createElement(ViewerStandingView, { viewerMode: props.viewerMode, memberReadOnly: props.memberReadOnly, adminMode: props.adminMode, versionLocked: props.versionLocked, publishState: props.publishState, publishAccess: props.publishAccess, myGrant: props.myGrant, onToggleAdmin: props.onToggleAdmin, onUnlock: props.onUnlock })) : props.body != null ? (props.bodyFill ? (React.createElement("div", { style: { flex: "1 1 auto", minHeight: 0 } }, props.body)) : (props.body)) : (React.createElement(React.Fragment, null,
762
- React.createElement("div", { ref: chipsRegionRef, style: streamFillActive
763
- ? { flex: "1 1 auto", minHeight: 0, display: "flex", flexDirection: "column" }
764
- : { flex: "1 0 auto", minHeight: 0 } },
765
- props.historyBody && (React.createElement("div", { "data-testid": "vibe-card-history", "aria-hidden": props.streamBody ? true : undefined, inert: props.streamBody ? true : undefined, style: { marginBottom: 8, ...(props.streamBody ? { display: "none" } : null) } }, props.historyBody)),
766
- React.createElement("div", { "data-testid": "vibe-card-stream-region", style: {
767
- position: "relative",
768
- ...(chipBuildLive ? { display: "flex", flexDirection: "column" } : {}),
769
- ...(streamFillActive ? { flex: "1 1 auto", minHeight: 0 } : null),
770
- } },
771
- props.streamBody && (React.createElement("div", { "data-testid": streamLive ? undefined : "vibe-card-stream-settled", style: streamLive
772
- ? {
773
- ...(chipBuildLive ? { flex: "1 1 auto" } : { position: "absolute", inset: 0 }),
774
- display: "flex",
775
- flexDirection: "column",
776
- minHeight: 0,
777
- }
778
- : { marginBottom: 8 } },
779
- React.createElement("div", { style: streamLive ? { flex: "1 1 auto", minHeight: 0, overflowY: "auto" } : undefined }, props.streamBody),
780
- props.liveDuringBuild === true && React.createElement(PendingMessages, { messages: queued }))),
781
- React.createElement("div", { "aria-hidden": streamLive && !chipBuildLive ? true : undefined, inert: streamLive && !chipBuildLive ? true : undefined, style: streamLive && !chipBuildLive ? { visibility: "hidden" } : { flexShrink: 0 } }, (props.seedOffer || props.upgradeChips || (visibleChips && visibleChips.length > 0)) && (React.createElement("div", { className: "mx-auto mt-2 w-full max-w-5xl" },
782
- React.createElement(ChatMessageRow, { role: "ai", rowAttrs: { "data-message-role": "suggestions" } },
783
- React.createElement("div", { className: "vibes-chip-well flex flex-col", "data-testid": "vibe-chip-set" },
784
- (props.seedOffer || props.upgradeChips) && (React.createElement("div", { className: "flex flex-col gap-[6px]", inert: props.activeChip !== undefined },
785
- props.seedOffer,
786
- props.upgradeChips)),
787
- visibleChips && visibleChips.length > 0 && (React.createElement(OptionButtons, { options: visibleChips, activeOption: props.activeChip, inSet: true, isFirst: props.activeChip === undefined, firstMessage: props.isOwner
788
- ? "Suggested requests:"
789
- : "Describe a change to remix your own copy of this app:", onSelect: (o) => {
790
- const r = props.onSelectChip?.(o);
791
- return r && typeof r === "object" && "then" in r ? r.then((ok) => ok ?? false) : false;
792
- }, decorate: (option) => decorateChip(option, props) }))))))),
793
- props.streamFooter !== undefined && React.createElement("div", { className: "mx-auto w-full max-w-5xl" }, props.streamFooter)))))),
794
- props.body == null && !agentPanelActive && (React.createElement("div", { style: { flexShrink: 0, padding: "0 14px 10px" } },
795
- React.createElement(OtherRow, { value: draft, onValueChange: setDraft, queued: queued, onQueuedChange: setQueued, onSubmitOther: props.onSubmitOther, generating: props.generating, liveDuringBuild: props.liveDuringBuild, canSendNow: props.canSendNow, onStop: props.onStop, stopDisabled: props.stopDisabled, attachmentsCtl: attachmentsCtl }))),
1010
+ ...(streamLive ? { visibility: "hidden" } : null),
1011
+ } }, props.composerControls)),
1012
+ React.createElement("div", { ref: scrollRegionRef, "data-testid": "vibe-card-scroll", style: {
1013
+ padding: "0 14px 4px",
1014
+ overflowY: "auto",
1015
+ overflowX: "hidden",
1016
+ flex: "1 1 auto",
1017
+ minHeight: 0,
1018
+ ...((props.bodyFill && props.body) || agentPanelActive || props.body == null
1019
+ ? { display: "flex", flexDirection: "column" }
1020
+ : null),
1021
+ } },
1022
+ props.retainedEditor != null && (React.createElement("div", { hidden: !props.editorVisible || agentPanelActive, inert: !props.editorVisible || agentPanelActive ? true : undefined, style: props.editorVisible && !agentPanelActive ? { flex: "1 1 auto", minHeight: 0 } : { display: "none" } }, props.retainedEditor)),
1023
+ agentPanelActive ? (React.createElement("div", { style: { flex: "1 1 auto", minHeight: 0 } }, props.agentPanel)) : props.editorVisible && props.retainedEditor != null ? null : props.editorTab === "standing" ? (React.createElement(ViewerStandingView, { viewerMode: props.viewerMode, memberReadOnly: props.memberReadOnly, adminMode: props.adminMode, versionLocked: props.versionLocked, publishState: props.publishState, publishAccess: props.publishAccess, myGrant: props.myGrant, onToggleAdmin: props.onToggleAdmin, onUnlock: props.onUnlock })) : props.body != null ? (props.bodyFill ? (React.createElement("div", { style: { flex: "1 1 auto", minHeight: 0 } }, props.body)) : (props.body)) : (React.createElement(React.Fragment, null,
1024
+ React.createElement("div", { ref: chipsRegionRef, style: streamFillActive
1025
+ ? { flex: "1 1 auto", minHeight: 0, display: "flex", flexDirection: "column" }
1026
+ : { flex: "1 0 auto", minHeight: 0 } },
1027
+ props.historyBody && (React.createElement("div", { "data-testid": "vibe-card-history", "aria-hidden": props.streamBody ? true : undefined, inert: props.streamBody ? true : undefined, style: { marginBottom: 8, ...(props.streamBody ? { display: "none" } : null) } }, props.historyBody)),
1028
+ React.createElement("div", { "data-testid": "vibe-card-stream-region", style: {
1029
+ position: "relative",
1030
+ ...(chipBuildLive ? { display: "flex", flexDirection: "column" } : {}),
1031
+ ...(streamFillActive ? { flex: "1 1 auto", minHeight: 0 } : null),
1032
+ } },
1033
+ props.streamBody && (React.createElement("div", { "data-testid": streamLive ? undefined : "vibe-card-stream-settled", style: streamLive
1034
+ ? {
1035
+ ...(chipBuildLive ? { flex: "1 1 auto" } : { position: "absolute", inset: 0 }),
1036
+ display: "flex",
1037
+ flexDirection: "column",
1038
+ minHeight: 0,
1039
+ }
1040
+ : { marginBottom: 8 } },
1041
+ React.createElement("div", { style: streamLive ? { flex: "1 1 auto", minHeight: 0, overflowY: "auto" } : undefined }, props.streamBody),
1042
+ props.liveDuringBuild === true && React.createElement(PendingMessages, { messages: queued }))),
1043
+ React.createElement("div", { "aria-hidden": streamLive && !chipBuildLive ? true : undefined, inert: streamLive && !chipBuildLive ? true : undefined, style: streamLive && !chipBuildLive ? { visibility: "hidden" } : { flexShrink: 0 } }, (props.seedOffer || props.upgradeChips || (visibleChips && visibleChips.length > 0)) && (React.createElement("div", { className: "mx-auto mt-2 w-full max-w-5xl" },
1044
+ React.createElement(ChatMessageRow, { role: "ai", rowAttrs: { "data-message-role": "suggestions" } },
1045
+ React.createElement("div", { className: "vibes-chip-well flex flex-col", "data-testid": "vibe-chip-set" },
1046
+ (props.seedOffer || props.upgradeChips) && (React.createElement("div", { className: "flex flex-col gap-[6px]", inert: props.activeChip !== undefined },
1047
+ props.seedOffer,
1048
+ props.upgradeChips)),
1049
+ visibleChips && visibleChips.length > 0 && (React.createElement(OptionButtons, { options: visibleChips, activeOption: props.activeChip, inSet: true, isFirst: props.activeChip === undefined, firstMessage: props.isOwner
1050
+ ? "Suggested requests:"
1051
+ : "Describe a change to remix your own copy of this app:", onSelect: (o) => {
1052
+ const r = props.onSelectChip?.(o);
1053
+ return r && typeof r === "object" && "then" in r ? r.then((ok) => ok ?? false) : false;
1054
+ }, decorate: (option) => decorateChip(option, props) }))))))),
1055
+ props.streamFooter !== undefined && React.createElement("div", { className: "mx-auto w-full max-w-5xl" }, props.streamFooter)))))),
1056
+ props.body == null && !agentPanelActive && (React.createElement("div", { style: { flexShrink: 0, padding: "0 14px 10px" } },
1057
+ React.createElement(OtherRow, { value: draft, onValueChange: setDraft, queued: queued, onQueuedChange: setQueued, onSubmitOther: props.onSubmitOther, generating: props.generating, liveDuringBuild: props.liveDuringBuild, canSendNow: props.canSendNow, onStop: props.onStop, stopDisabled: props.stopDisabled, attachmentsCtl: attachmentsCtl })))),
796
1058
  agentPanelActive ? (React.createElement("div", { "aria-hidden": true, style: { display: "flex", flexShrink: 0, padding: "0 8px" } },
797
- React.createElement("div", { style: { width: 120, height: 50, marginLeft: "auto" } }))) : (React.createElement("div", { style: {
1059
+ React.createElement("div", { style: { width: 120, height: 50, marginLeft: "auto" } }))) : (React.createElement("div", { "data-vibe-card-nav": true, style: {
798
1060
  display: "flex",
799
1061
  alignItems: "center",
800
1062
  gap: "var(--uvc-nav-gap, 6px)",
801
1063
  padding: "0 8px",
802
- borderTop: "1px solid var(--color-light-decorative-00, #e5e5e5)",
1064
+ borderTop: "1px solid transparent",
803
1065
  flexShrink: 0,
804
1066
  } },
805
- React.createElement(NavIcon, { label: "Home", color: "#2563eb", onClick: props.onHome },
1067
+ React.createElement(NavIcon, { label: "Home", color: "var(--uvc-nav-home, #2563eb)", onClick: props.onHome },
806
1068
  React.createElement("svg", { width: "17", height: "17", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" },
807
1069
  React.createElement("path", { d: "M3 12l9-9 9 9" }),
808
1070
  React.createElement("path", { d: "M5 10v10a1 1 0 001 1h4v-6h4v6h4a1 1 0 001-1V10" }))),
809
- React.createElement(NavIcon, { label: "Chat", color: "var(--uvc-nav-chat, #7c3aed)", selected: props.selectedNav === "edit-chat", onClick: props.onChat },
810
- React.createElement("svg", { width: "17", height: "17", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, "aria-hidden": "true" },
811
- React.createElement("path", { d: "M21 11a8 8 0 01-8 8H7l-4 3V11a9 9 0 0118 0Z" }))),
812
- React.createElement(NavIcon, { label: "Edit", color: "var(--uvc-nav-edit, #c2410c)", selected: ["edit", "standing", "code", "files", "data", "style", "settings", "faq"].includes(props.selectedNav ?? "edit"), onClick: props.onEdit },
1071
+ React.createElement(NavIcon, { label: "Edit", color: "var(--uvc-nav-edit, #c2410c)", selected: ["edit", "edit-chat", "standing", "code", "files", "data", "style", "settings", "faq"].includes(props.selectedNav ?? "edit"), onClick: props.onEdit ?? props.onChat },
813
1072
  React.createElement("svg", { width: "17", height: "17", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" },
814
1073
  React.createElement("path", { d: "M12 20h9" }),
815
1074
  React.createElement("path", { d: "M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z" }))),
@@ -917,7 +1176,7 @@ export function UnifiedVibeCard(props) {
917
1176
  } },
918
1177
  React.createElement("span", { "aria-hidden": true, style: { width: 6, height: 6, borderRadius: "50%", background: "#f59e0b" } }),
919
1178
  "draft")),
920
- React.createElement("button", { type: "button", "aria-label": open
1179
+ React.createElement("button", { type: "button", "data-vibe-switch-glass": true, "aria-label": open
921
1180
  ? "Close vibe menu"
922
1181
  : (props.pendingRequestCount ?? 0) > 0
923
1182
  ?
@@ -933,10 +1192,10 @@ export function UnifiedVibeCard(props) {
933
1192
  position: "relative",
934
1193
  overflow: "visible",
935
1194
  } },
936
- React.createElement(VibesSwitch, { size: 60, style: { position: "absolute", right: 0, bottom: 0, pointerEvents: "none" }, isActive: !open, isTwinkling: !open && isTwinkling, isTeasing: !open && isTeasing, ovalColor: props.switchOvalColor ??
937
- (props.versionLocked || props.publishState === "draft" || props.onViewDraft !== undefined
938
- ? ovalColors.yellow
939
- : undefined), ovalStatusKey: props.switchOvalStatusKey }),
1195
+ switchGlassColor !== ovalColors.neutral && (React.createElement(React.Fragment, { key: `${switchGlassColor}|${props.switchOvalStatusKey ?? ""}` },
1196
+ React.createElement("span", { "data-vibe-switch-rest": true, "aria-hidden": "true" }),
1197
+ React.createElement("span", { "data-vibe-switch-highlight": true, "data-testid": "vibe-switch-highlight", "data-arrival-amber": switchGlassColor === ovalColors.yellow, "aria-hidden": "true", style: { color: switchGlassColor } }))),
1198
+ React.createElement(VibesSwitch, { size: 60, style: { position: "absolute", right: 0, bottom: 0, pointerEvents: "none" }, isActive: !open, isTwinkling: !open && isTwinkling, isTeasing: !open && isTeasing }),
940
1199
  !open && (props.pendingRequestCount ?? 0) > 0 && (React.createElement("span", { "data-testid": "vibe-switch-pending-badge", "aria-hidden": "true", style: {
941
1200
  position: "absolute",
942
1201
  top: -4,
@@ -1218,22 +1477,24 @@ function FastPathToggle({ blessed, onClick }) {
1218
1477
  function NavIcon({ label, color, selected, onClick, buttonRef, badgeCount, children, }) {
1219
1478
  const showBadge = typeof badgeCount === "number" && badgeCount > 0;
1220
1479
  const badgeText = showBadge ? (badgeCount > 99 ? "99+" : String(badgeCount)) : "";
1221
- return (React.createElement("button", { ref: buttonRef, type: "button", "aria-label": showBadge ? `${label} (${badgeCount})` : label, title: label, onClick: onClick, style: {
1480
+ return (React.createElement("button", { ref: buttonRef, type: "button", "aria-label": showBadge ? `${label} (${badgeCount})` : label, title: label, "data-vibe-nav-selected": selected === true, onClick: onClick, style: {
1222
1481
  position: "relative",
1223
1482
  width: "var(--uvc-nav-size, 38px)",
1224
1483
  height: "var(--uvc-nav-size, 38px)",
1225
1484
  flexShrink: 0,
1226
1485
  boxSizing: "border-box",
1227
1486
  borderRadius: "50%",
1228
- background: "transparent",
1487
+ background: "rgba(0,0,0,.5)",
1229
1488
  display: "flex",
1230
1489
  alignItems: "center",
1231
1490
  justifyContent: "center",
1232
1491
  color,
1233
1492
  fontSize: 16,
1234
1493
  cursor: "pointer",
1235
- border: `1px solid ${color}`,
1236
- boxShadow: selected ? `0 0 0 3px ${color}` : "none",
1494
+ border: `${selected ? 3 : 1}px solid ${color}`,
1495
+ boxShadow: selected
1496
+ ? "inset 0 4px 8px 1px rgba(0,0,0,.85), inset 0 1px 3px rgba(0,0,0,.65), inset 0 -1px 0 color-mix(in srgb,currentColor 45%,transparent)"
1497
+ : "none",
1237
1498
  } },
1238
1499
  children,
1239
1500
  showBadge ? (React.createElement("span", { "aria-hidden": "true", style: {
@@ -1334,9 +1595,10 @@ function OtherRow({ value, onValueChange, queued, onQueuedChange, onSubmitOther,
1334
1595
  const submitDisabled = attachmentsCtl.busy || (Boolean(generating) && attachmentsCtl.readyAttachments.length > 0);
1335
1596
  const sendNow = liveDuringBuild === true && canSendNow === true && queued.length === 0 && attachmentsCtl.readyAttachments.length === 0;
1336
1597
  const hasUnsentContent = value.trim() !== "" || attachmentsCtl.readyAttachments.length > 0;
1598
+ const composerDrop = composerFileDrop({ onFiles: attachmentsCtl.add });
1337
1599
  return (React.createElement(React.Fragment, null,
1338
1600
  React.createElement(AttachmentChips, { chips: attachmentsCtl.attachments, onRemove: attachmentsCtl.remove }),
1339
- React.createElement("form", { onSubmit: (e) => {
1601
+ React.createElement("form", { onDragOver: composerDrop.onDragOver, onDrop: composerDrop.onDrop, onSubmit: (e) => {
1340
1602
  e.preventDefault();
1341
1603
  const text = value.trim();
1342
1604
  const files = attachmentsCtl.readyAttachments;
@@ -1391,7 +1653,9 @@ function OtherRow({ value, onValueChange, queued, onQueuedChange, onSubmitOther,
1391
1653
  ? "Sends when the current change finishes"
1392
1654
  : undefined, style: {
1393
1655
  ...OTHER_ROW_BUTTON_STYLE,
1394
- background: "var(--vibes-blue, #3b82f6)",
1656
+ background: "var(--uvc-submit-fill, rgba(64,89,114,.2))",
1657
+ color: "var(--uvc-submit-arrow, var(--vibes-cream, #fffff0))",
1658
+ boxShadow: "none",
1395
1659
  opacity: submitDisabled ? 0.5 : 1,
1396
1660
  cursor: submitDisabled ? "default" : "pointer",
1397
1661
  } },