@vibes.diy/base 14.3.0 → 14.3.2

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,216 @@ 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-menu-strip: rgba(255,255,255,.18);
67
+ --uvc-menu-tab: rgba(255,255,255,.45);
68
+ --uvc-share-muted: #424954;
69
+ --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);
70
+ --color-light-background-00: rgba(255,255,255,.24);
71
+ --color-light-background-01: rgba(255,255,255,.2);
72
+ --uvc-suggestion-bg: rgba(255,255,255,.28);
73
+ --vibes-field-bg: rgba(255,255,255,.2);
74
+ --uvc-nav-home: #2563eb;`;
75
+ const UNIFIED_VIBE_CARD_GLASS_DARK_VARS = `
76
+ --uvc-submit-arrow:#aaa;
77
+ --uvc-submit-fill:#000;
78
+ --uvc-notch-opacity: .25;
79
+ --uvc-notch-width: 52px;
80
+ --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));
81
+ --uvc-notch-shadow: inset 0 0 0 1px rgba(230,255,245,.2),0 1px 2px rgba(0,0,0,.3);
82
+ --uvc-composer-border: rgba(210,239,228,.28);
83
+ --uvc-glass-fill: rgba(16,26,24,.72);
84
+ --uvc-glass-edge: rgba(210,239,228,.28);
85
+ --uvc-glass-highlight: rgba(230,255,245,.32);
86
+ --uvc-menu-strip: rgba(16,26,24,.18);
87
+ --uvc-menu-tab: rgba(230,255,245,.12);
88
+ --uvc-share-muted: #b7becb;
89
+ --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);
90
+ --color-light-background-00: rgba(16,26,24,.28);
91
+ --color-light-background-01: rgba(16,26,24,.24);
92
+ --uvc-suggestion-bg: rgba(16,26,24,.32);
93
+ --vibes-field-bg: rgba(16,26,24,.28);
94
+ --uvc-nav-home: #93b4ff;`;
95
+ const UNIFIED_VIBE_CARD_OBSIDIAN_VARS = `
96
+ --uvc-glass-fill: rgba(6,9,10,.94);
97
+ --uvc-glass-edge: rgba(255,255,255,.12);
98
+ --uvc-glass-highlight: rgba(255,255,255,.25);
99
+ --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);
100
+ --uvc-nav-home: #93b4ff;`;
101
+ const UNIFIED_VIBE_CARD_NAV_LIGHT_VARS = `
102
+ --uvc-glass-fill:rgba(255,255,255,.08);
103
+ --uvc-glass-edge:rgba(255,255,255,.3);
104
+ --uvc-glass-highlight:rgba(255,255,255,.8);
105
+ --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);
106
+ --uvc-nav-home:#2d5bd1; --uvc-nav-edit:#a34b0a; --uvc-nav-share:#18713b;
107
+ --uvc-nav-fill:rgba(255,255,240,.92);
108
+ --uvc-nav-border:color-mix(in srgb,currentColor 65%,transparent);
109
+ --uvc-nav-selected-width:1px;
110
+ --uvc-nav-selected-shadow:inset 0 0 5px 1px color-mix(in srgb,currentColor 45%,transparent),inset 0 0 2px color-mix(in srgb,currentColor 25%,transparent);`;
111
+ const UNIFIED_VIBE_CARD_NAV_DARK_VARS = `${UNIFIED_VIBE_CARD_OBSIDIAN_VARS}
112
+ --uvc-nav-edit:#fb923c; --uvc-nav-share:#4ade80;
113
+ --uvc-nav-fill:rgba(0,0,0,.5);
114
+ --uvc-nav-border:currentColor;
115
+ --uvc-nav-selected-width:3px;
116
+ --uvc-nav-selected-shadow: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);`;
54
117
  const unifiedVibeCardDarkCss = `
118
+ [data-unified-vibe-card] {${UNIFIED_VIBE_CARD_GLASS_LIGHT_VARS}
119
+ }
55
120
  @media (prefers-color-scheme: dark) {
56
- [data-unified-vibe-card], [data-vibe-handle-menu], [data-vibe-version-menu] {${UNIFIED_VIBE_CARD_DARK_VARS}
121
+ [data-unified-vibe-card], [data-vibe-handle-menu], [data-vibe-version-menu], [data-vibe-closed-build-status] {${UNIFIED_VIBE_CARD_DARK_VARS}
122
+ }
123
+ [data-unified-vibe-card] {${UNIFIED_VIBE_CARD_GLASS_DARK_VARS}
57
124
  }
58
125
  }
59
- .dark [data-unified-vibe-card], .dark [data-vibe-handle-menu], .dark [data-vibe-version-menu] {${UNIFIED_VIBE_CARD_DARK_VARS}
126
+ .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}
127
+ }
128
+ .dark [data-unified-vibe-card] {${UNIFIED_VIBE_CARD_GLASS_DARK_VARS}
129
+ }
130
+ /* The detached row owns its theme independently of the card content. */
131
+ [data-vibe-card-nav] {${UNIFIED_VIBE_CARD_DARK_VARS}${UNIFIED_VIBE_CARD_NAV_LIGHT_VARS}
132
+ }
133
+ [data-vibe-switch-glass] {${UNIFIED_VIBE_CARD_OBSIDIAN_VARS}
134
+ }
135
+ /* The card's layout still includes its footer. Only its glass surface ends
136
+ at the former divider: retain the outer 1px border and footer's 1px top
137
+ border as transparent spacing so the circles and switch never move. */
138
+ [data-unified-vibe-card]::before {
139
+ content: ""; position: absolute; inset: -1px -1px 50px; border-radius: 14px;
140
+ }
141
+ /* The agent panel retains its existing 50px switch reservation, without the
142
+ ordinary nav row's extra divider pixel. Its body ends one pixel lower. */
143
+ [data-unified-vibe-card][data-vibe-agent-panel="true"]::before { bottom: 49px; }
144
+ [data-vibe-card-nav] { position: relative; isolation: isolate; }
145
+ [data-vibe-card-nav]::before {
146
+ content: ""; position: absolute; left: 2px; top: 50%; transform: translateY(-50%); z-index: -1;
147
+ width: calc(3 * var(--uvc-nav-size, 38px) + 2 * var(--uvc-nav-gap, 6px) + 12px);
148
+ height: calc(var(--uvc-nav-size, 38px) + 10px); border-radius: 999px;
149
+ }
150
+ [data-unified-vibe-card]::before, [data-vibe-card-nav]::before, [data-vibe-switch-glass]::before {
151
+ box-sizing: border-box; border: 1px solid transparent; pointer-events: none;
152
+ }
153
+ [data-unified-vibe-card]::before, [data-vibe-card-nav]::before, [data-vibe-switch-glass]::before {
154
+ background: linear-gradient(to right, transparent, var(--uvc-glass-highlight), transparent) top / 100% 1px no-repeat,
155
+ linear-gradient(to bottom, var(--uvc-glass-highlight), transparent, var(--uvc-glass-edge)) left / 1px 100% no-repeat,
156
+ var(--uvc-glass-fill);
157
+ backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px);
158
+ box-shadow: var(--uvc-glass-shadow);
159
+ }
160
+ [data-vibe-card-nav]::before { backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px); }
161
+ @media (prefers-color-scheme: dark) {
162
+ [data-vibe-card-nav] {${UNIFIED_VIBE_CARD_NAV_DARK_VARS}}
163
+ [data-vibe-card-nav]::before { backdrop-filter:blur(24px); -webkit-backdrop-filter:blur(24px); }
164
+ }
165
+ .dark [data-vibe-card-nav] {${UNIFIED_VIBE_CARD_NAV_DARK_VARS}}
166
+ .dark [data-vibe-card-nav]::before { backdrop-filter:blur(24px); -webkit-backdrop-filter:blur(24px); }
167
+ .light [data-vibe-card-nav] {${UNIFIED_VIBE_CARD_NAV_LIGHT_VARS}}
168
+ .light [data-vibe-card-nav]::before { backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px); }
169
+ [data-vibe-card-nav] [data-vibe-nav-selected="true"] svg { stroke-width:3; }
170
+ /* The arrival uses the fitted glass surface, leaving the brand artwork untouched.
171
+ Keying the layer by status replays same-color arrivals without a JS timer. */
172
+ [data-vibe-switch-glass]:has([data-vibe-switch-highlight])::before { visibility:hidden; }
173
+ [data-vibe-switch-rest] {
174
+ background:linear-gradient(to right,transparent,var(--uvc-glass-highlight),transparent) top/100% 1px no-repeat,
175
+ linear-gradient(to bottom,var(--uvc-glass-highlight),transparent,var(--uvc-glass-edge)) left/1px 100% no-repeat,var(--uvc-glass-fill);
176
+ box-shadow:var(--uvc-glass-shadow);
177
+ animation:uvc-notification-rest 7s forwards;
178
+ }
179
+ [data-vibe-switch-highlight] {
180
+ --uvc-arrival-fill:color-mix(in srgb,currentColor 80%,transparent);
181
+ --uvc-arrival-edge:color-mix(in srgb,color-mix(in srgb,currentColor 50%,white) 60%,transparent);
182
+ --uvc-arrival-highlight:color-mix(in srgb,color-mix(in srgb,currentColor 20%,white) 90%,transparent);
183
+ background:linear-gradient(to right,transparent,var(--uvc-arrival-highlight),transparent) top/100% 1px no-repeat,
184
+ linear-gradient(to bottom,var(--uvc-arrival-highlight),transparent,var(--uvc-arrival-edge)) left/1px 100% no-repeat,var(--uvc-arrival-fill);
185
+ 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);
186
+ animation:uvc-notification-glass 7s forwards;
187
+ }
188
+ /* Preserve the original Amber material for the yellow/pending status. */
189
+ [data-vibe-switch-highlight][data-arrival-amber="true"] {
190
+ --uvc-arrival-fill:rgba(191,142,66,.8);
191
+ --uvc-arrival-edge:rgba(255,229,167,.6);
192
+ --uvc-arrival-highlight:rgba(255,246,218,.9);
193
+ }
194
+ [data-vibe-switch-highlight], [data-vibe-switch-rest] {
195
+ box-sizing:border-box; border:1px solid transparent; pointer-events:none;
196
+ backdrop-filter:blur(24px); -webkit-backdrop-filter:blur(24px);
197
+ }
198
+ @keyframes uvc-notification-rest {
199
+ 0%,100% { opacity:1; }
200
+ 14.3%,71.4% { opacity:0; }
201
+ }
202
+ @keyframes uvc-notification-glass {
203
+ 0% { opacity:0; }
204
+ 14.3%,71.4% { opacity:1; }
205
+ 100% { opacity:0; }
206
+ }
207
+ @media (prefers-reduced-motion:reduce) {
208
+ [data-vibe-switch-highlight] { animation-name:uvc-notification-glass-reduced; }
209
+ [data-vibe-switch-rest] { animation-name:uvc-notification-rest-reduced; }
210
+ }
211
+ @keyframes uvc-notification-rest-reduced {
212
+ 0%,71.4% { opacity:0; }
213
+ 100% { opacity:1; }
214
+ }
215
+ @keyframes uvc-notification-glass-reduced {
216
+ 0%,71.4% { opacity:1; }
217
+ 100% { opacity:0; }
218
+ }
219
+ /* Fit the painted 102px logo, excluding the SVG's transparent side margins.
220
+ Match the nav oval's height without moving the existing 120×44 hit target. */
221
+ [data-vibe-switch-glass] { isolation: isolate; }
222
+ [data-vibe-switch-glass]::before, [data-vibe-switch-highlight], [data-vibe-switch-rest] {
223
+ content: ""; position: absolute; left: 6px; right: 6px; top: calc(50% + 1px);
224
+ height: calc(var(--uvc-nav-size, 38px) + 10px); transform: translateY(-50%);
225
+ border-radius: 999px; z-index: -1;
226
+ }
227
+ [data-unified-vibe-card] form.vibes-edit-field {
228
+ --vibes-card-border: var(--uvc-composer-border);
229
+ }
230
+ /* The selected Frost header rule fades at both ends at half opacity,
231
+ without consuming any of the card's content height. */
232
+ [data-vibe-card-header]::after {
233
+ content: ""; position: absolute; bottom: 0; left: 0; right: 0; height: 1px;
234
+ background: linear-gradient(90deg, transparent, var(--uvc-glass-edge) 22%, var(--uvc-glass-edge) 78%, transparent);
235
+ opacity: .5; pointer-events: none;
236
+ }
237
+ /* A · Line: the clip edge sits under the painted switch, inside the 120px
238
+ hit box (whose SVG has transparent air at its left). 22px padding leaves
239
+ the resting strip at right:132px without changing the switch geometry. */
240
+ [data-vibe-closed-build-status] {
241
+ position: absolute; left: 12px; right: 110px; bottom: 17px;
242
+ z-index: 2; display: flex; justify-content: flex-end; padding-right: 22px;
243
+ overflow: hidden; pointer-events: none; box-sizing: border-box;
244
+ }
245
+ [data-vibe-closed-build-line] {
246
+ min-width: 0; max-width: 100%; box-sizing: border-box;
247
+ padding: 3px 5px; border-radius: 3px;
248
+ background: var(--uvc-closed-build-bg, #fff);
249
+ color: var(--uvc-closed-build-text, #6d6973);
250
+ font: 12px/18px Inter, system-ui, sans-serif;
251
+ transform: translateX(calc(100% + 22px));
252
+ transition: transform 240ms cubic-bezier(.22,.68,0,1);
253
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
254
+ }
255
+ [data-vibe-closed-build-line][data-revealed="true"] { transform: translateX(0); }
256
+ [data-vibe-closed-build-line] [data-testid="thinking-line"] {
257
+ display: flex; align-items: baseline; gap: 6px; min-width: 0;
258
+ }
259
+ [data-vibe-closed-build-line] [data-testid="thinking-line-glyph"] { flex-shrink: 0; }
260
+ [data-vibe-closed-build-line] [data-testid="thinking-line-verb"] {
261
+ min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
262
+ }
263
+ @media (prefers-reduced-motion: reduce) {
264
+ [data-vibe-closed-build-line] { transition: none; }
60
265
  }
61
266
  /* Composer submit button: a blue pulsating glow while the field holds unsent
62
267
  content, nudging the user to send it — the same "something's live, act on it"
@@ -126,14 +331,14 @@ const unifiedVibeCardDarkCss = `
126
331
  animation: none;
127
332
  }
128
333
  }
129
- /* Four shrink-proof nav circles share the footer with the 120px logo spacer.
334
+ /* Three shrink-proof nav circles (Home / Edit / Share) share the footer with the 120px logo spacer.
130
335
  Compact size/gap steps keep them clear of the logo on narrow phones without
131
336
  flex shrinking circles into ovals. */
132
337
  @media (max-width: 400px) {
133
- [data-unified-vibe-card] { --uvc-nav-size: 34px; --uvc-nav-gap: 4px; }
338
+ [data-unified-vibe-card], [data-vibe-switch-glass] { --uvc-nav-size: 34px; --uvc-nav-gap: 4px; }
134
339
  }
135
340
  @media (max-width: 360px) {
136
- [data-unified-vibe-card] { --uvc-nav-size: 30px; }
341
+ [data-unified-vibe-card], [data-vibe-switch-glass] { --uvc-nav-size: 30px; }
137
342
  }
138
343
  /* Composer input row — a FIELD, not a button (Storybook audition C2, #4578).
139
344
  It used to wear a hard 2px/3px offset shadow over the card's own grey, which
@@ -220,9 +425,25 @@ const unifiedVibeCardDarkCss = `
220
425
  }
221
426
  [data-vibe-card-editor-tabs] [role="tab"] {
222
427
  min-height: 36px;
223
- padding-inline: 4px !important;
428
+ padding-inline: 3px !important;
224
429
  font-size: 14px !important;
225
430
  }
431
+ /* Share and Chat/editor strips are pinned outside their content scrollers.
432
+ Continue the card glass across both menus; standalone strips retain their
433
+ opaque surfaces so scrolling text cannot show through them. */
434
+ [data-unified-vibe-card] [data-share-panel] > [role="tablist"],
435
+ [data-unified-vibe-card] [data-vibe-card-editor-tabs] [role="tablist"] {
436
+ background: var(--uvc-menu-strip);
437
+ border-color: var(--uvc-glass-edge);
438
+ }
439
+ [data-unified-vibe-card] [data-share-panel] > [role="tablist"] [role="tab"][aria-selected="true"],
440
+ [data-unified-vibe-card] [data-vibe-card-editor-tabs] [role="tab"][aria-selected="true"] {
441
+ background: var(--uvc-menu-tab);
442
+ box-shadow: inset 0 1px 0 var(--uvc-glass-edge);
443
+ }
444
+ [data-unified-vibe-card] [data-share-secondary] {
445
+ color: var(--uvc-share-muted);
446
+ }
226
447
  [data-unified-vibe-card] [data-testid="build-card"] {
227
448
  --text-secondary: var(--uvc-build-text, #526071);
228
449
  }
@@ -253,8 +474,67 @@ const unifiedVibeCardDarkCss = `
253
474
  }`;
254
475
  const CARD_VIEWPORT_HEIGHT = "80%";
255
476
  const CARD_FULL_VIEWPORT_HEIGHT = "94%";
477
+ function ClosedBuildStatus({ visible, content }) {
478
+ const [retainedContent, setRetainedContent] = useState(visible ? content : undefined);
479
+ const [revealed, setRevealed] = useState(false);
480
+ const [reducedMotion, setReducedMotion] = useState(false);
481
+ useEffect(() => {
482
+ if (typeof window.matchMedia !== "function")
483
+ return;
484
+ const media = window.matchMedia("(prefers-reduced-motion: reduce)");
485
+ function updateMotion() {
486
+ setReducedMotion(media.matches);
487
+ }
488
+ updateMotion();
489
+ media.addEventListener("change", updateMotion);
490
+ return () => media.removeEventListener("change", updateMotion);
491
+ }, []);
492
+ useEffect(() => {
493
+ if (visible)
494
+ setRetainedContent(content);
495
+ }, [visible, content]);
496
+ useEffect(() => {
497
+ let firstFrame;
498
+ let secondFrame;
499
+ let exitTimer;
500
+ if (visible) {
501
+ if (reducedMotion)
502
+ setRevealed(true);
503
+ else
504
+ firstFrame = requestAnimationFrame(() => {
505
+ secondFrame = requestAnimationFrame(() => setRevealed(true));
506
+ });
507
+ }
508
+ else {
509
+ setRevealed(false);
510
+ if (reducedMotion)
511
+ setRetainedContent(undefined);
512
+ else
513
+ exitTimer = setTimeout(() => setRetainedContent(undefined), 240);
514
+ }
515
+ return () => {
516
+ if (firstFrame !== undefined)
517
+ cancelAnimationFrame(firstFrame);
518
+ if (secondFrame !== undefined)
519
+ cancelAnimationFrame(secondFrame);
520
+ clearTimeout(exitTimer);
521
+ };
522
+ }, [visible, reducedMotion]);
523
+ if (retainedContent === undefined || retainedContent === null)
524
+ return undefined;
525
+ return (React.createElement("div", { "data-vibe-closed-build-status": true, "data-testid": "closed-build-status", "aria-hidden": visible ? undefined : true, inert: !visible },
526
+ React.createElement("div", { "data-vibe-closed-build-line": true, "data-revealed": revealed }, retainedContent)));
527
+ }
256
528
  export function UnifiedVibeCard(props) {
257
529
  const { appTitle, appSlug, isTwinkling, isTeasing, open: controlledOpen, onOpenChange } = props;
530
+ const editorTabDisabled = props.editorTab === undefined ? false : props.disabledEditorTabs?.includes(props.editorTab) === true;
531
+ const editorTabForRender = editorTabDisabled ? "edit-chat" : props.editorTab;
532
+ const retainedEditorVisible = props.editorVisible === true && editorTabDisabled === false;
533
+ const retainedEditorMounted = props.retainedEditor != null && editorTabDisabled === false;
534
+ const switchGlassColor = props.switchOvalColor ??
535
+ (props.versionLocked || props.publishState === "draft" || props.onViewDraft !== undefined
536
+ ? ovalColors.yellow
537
+ : ovalColors.neutral);
258
538
  const [internalOpen, setInternalOpen] = useState(false);
259
539
  const open = controlledOpen ?? internalOpen;
260
540
  const setOpen = (next) => {
@@ -413,10 +693,11 @@ export function UnifiedVibeCard(props) {
413
693
  setEverOpened(true);
414
694
  }, [open]);
415
695
  const retained = Boolean(props.keepMounted) && everOpened;
416
- const scrollRegionRef = useRef(null);
696
+ const internalScrollRegionRef = useRef(null);
697
+ const scrollRegionRef = props.scrollContainerRef ?? internalScrollRegionRef;
417
698
  const chipsRegionRef = useRef(null);
418
699
  const stickToBottomRef = useRef(true);
419
- const hasHistory = props.historyBody != null && props.body == null;
700
+ const hasHistory = (props.historyBody != null || props.streamBody != null) && props.body == null;
420
701
  const streamLive = props.streamBody != null && props.streamSettled !== true;
421
702
  const chipBuildLive = streamLive && props.activeChip !== undefined;
422
703
  const visibleChips = props.activeChip === undefined || props.chips?.slice(0, 3).includes(props.activeChip) === true
@@ -571,7 +852,8 @@ export function UnifiedVibeCard(props) {
571
852
  const resizeValueNow = Math.round(Math.min(Math.max(userHeight ?? cardRef.current?.offsetHeight ?? 0, RESIZE_MIN), resizeValueMax));
572
853
  return (React.createElement(React.Fragment, null,
573
854
  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: {
855
+ React.createElement(ClosedBuildStatus, { visible: open === false && hidden === false && props.closedBuildStatus !== undefined && props.closedBuildStatus !== null, content: props.closedBuildStatus }),
856
+ !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
857
  position: "absolute",
576
858
  left: 2,
577
859
  right: 2,
@@ -580,12 +862,11 @@ export function UnifiedVibeCard(props) {
580
862
  borderRadius: 14,
581
863
  maxHeight: cardHeight,
582
864
  height: cardHeight,
583
- overflow: "hidden",
865
+ overflow: "visible",
584
866
  display: "flex",
585
867
  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))",
868
+ background: "transparent",
869
+ border: "1px solid transparent",
589
870
  transformOrigin: "bottom right",
590
871
  transform: grown ? "scale(1)" : "scale(0)",
591
872
  transition: dragging ? scaleTransition : `${scaleTransition}, max-height 0.24s ease, height 0.24s ease`,
@@ -593,6 +874,7 @@ export function UnifiedVibeCard(props) {
593
874
  ...(mounted ? null : { visibility: "hidden" }),
594
875
  } },
595
876
  React.createElement("div", { "data-vibe-card-content": true, style: {
877
+ position: "relative",
596
878
  display: "flex",
597
879
  flexDirection: "column",
598
880
  minHeight: 0,
@@ -600,216 +882,233 @@ export function UnifiedVibeCard(props) {
600
882
  opacity: grown ? 1 : 0,
601
883
  transition: grown ? "opacity 0.18s ease 0.14s" : "opacity 0.1s ease",
602
884
  }, className: "text-light-primary dark:text-dark-primary" },
603
- React.createElement("div", { style: {
885
+ React.createElement("div", { "data-vibe-card-body": true, style: {
604
886
  display: "flex",
605
- alignItems: "center",
606
- gap: 8,
607
- padding: "10px 10px 10px",
608
- flexShrink: 0,
609
- position: "relative",
610
- userSelect: "none",
887
+ flexDirection: "column",
888
+ flex: "1 1 auto",
889
+ minHeight: 0,
890
+ overflow: "hidden",
891
+ borderRadius: 13,
611
892
  } },
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,
893
+ React.createElement("div", { "data-vibe-card-header": true, style: {
894
+ display: "flex",
895
+ alignItems: "center",
896
+ gap: 8,
897
+ padding: "10px 10px 10px",
637
898
  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",
899
+ position: "relative",
900
+ userSelect: "none",
642
901
  } },
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,
902
+ 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: {
903
+ position: "absolute",
904
+ top: 0,
905
+ left: 0,
906
+ right: 0,
907
+ bottom: 0,
908
+ zIndex: 0,
909
+ cursor: "ns-resize",
910
+ touchAction: "none",
911
+ } },
912
+ React.createElement("span", { "aria-hidden": true, style: {
913
+ position: "absolute",
914
+ top: 4,
915
+ left: "50%",
916
+ transform: "translateX(-50%)",
917
+ width: "var(--uvc-notch-width, 44px)",
918
+ height: 4,
919
+ borderRadius: 999,
920
+ background: "var(--uvc-notch-fill)",
921
+ boxShadow: "var(--uvc-notch-shadow)",
922
+ opacity: "var(--uvc-notch-opacity)",
923
+ } })),
924
+ React.createElement("div", { style: {
925
+ position: "relative",
926
+ width: 30,
927
+ height: 30,
928
+ borderRadius: 8,
929
+ flexShrink: 0,
651
930
  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" }))))),
931
+ background: "var(--vibes-cream, #fffff0)",
932
+ border: "1px solid rgba(0,0,0,0.15)",
933
+ pointerEvents: "none",
934
+ } },
935
+ props.appIconUrl && (React.createElement("img", { src: props.appIconUrl, alt: "", style: { width: "100%", height: "100%", objectFit: "cover", transform: "scale(2)" } })),
936
+ React.createElement(CodingIndicator, { active: props.coding === true })),
937
+ React.createElement("div", { style: { lineHeight: 1.2, minWidth: 0, flex: "1 1 auto" } },
938
+ React.createElement("strong", { style: {
939
+ fontFamily: HOME_BRAND_FONT,
940
+ display: "block",
941
+ fontSize: 13,
942
+ lineHeight: 1.3,
943
+ overflow: "hidden",
944
+ textOverflow: "ellipsis",
945
+ whiteSpace: "nowrap",
946
+ } }, appTitle),
947
+ appSlug &&
948
+ (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 } },
949
+ props.onOpenApp === undefined ? (React.createElement("span", { className: "text-light-secondary dark:text-dark-secondary", style: {
950
+ fontSize: 12,
951
+ minWidth: 0,
952
+ overflow: "hidden",
953
+ textOverflow: "ellipsis",
954
+ whiteSpace: "nowrap",
955
+ } }, appSlug)) : (React.createElement("button", { type: "button", "data-testid": "vibe-card-slug-link", onClick: props.onOpenApp, className: "text-light-secondary dark:text-dark-secondary", style: {
956
+ font: "inherit",
957
+ fontSize: 12,
958
+ lineHeight: "20px",
959
+ overflow: "hidden",
960
+ textOverflow: "ellipsis",
961
+ whiteSpace: "nowrap",
962
+ minWidth: 0,
963
+ textDecoration: "underline",
964
+ textUnderlineOffset: 2,
965
+ background: "none",
966
+ border: "none",
967
+ padding: 0,
968
+ cursor: "pointer",
969
+ textAlign: "left",
970
+ } }, appSlug)),
971
+ props.onCopyAppUrl !== undefined && (React.createElement(CopyLinkIconButton, { onCopy: props.onCopyAppUrl, color: "currentColor" })))))),
972
+ (() => {
973
+ const badges = [
974
+ props.publishState === "old" ? React.createElement(DraftBadge, { key: "old", variant: "old" }) : null,
975
+ props.publishState === "draft" && !props.versionLocked ? React.createElement(DraftBadge, { key: "draft", variant: "draft" }) : null,
976
+ props.versionLocked ? React.createElement(LockedBadge, { key: "locked" }) : null,
977
+ !props.publishState && props.onViewDraft && !props.versionLocked ? (React.createElement(ViewDraftBadge, { key: "view-draft", onClick: props.onViewDraft })) : null,
978
+ ].filter(Boolean);
979
+ 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));
980
+ })(),
981
+ React.createElement("div", { "data-vibe-card-header-controls": "true", style: {
982
+ position: "relative",
983
+ zIndex: 1,
984
+ marginLeft: "auto",
985
+ display: "flex",
986
+ alignItems: "center",
987
+ gap: 10,
988
+ flexShrink: 0,
989
+ maxWidth: "38%",
990
+ minWidth: 0,
991
+ } }, props.hideViewerTag ? null : props.handleSlug ? (React.createElement("div", { ref: pickerWrapRef, style: { position: "relative", minWidth: 0, maxWidth: "100%" } },
992
+ 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: {
993
+ background: "var(--color-light-background-01, #eee)",
994
+ border: "1px solid var(--color-light-decorative-01, #ddd)",
995
+ color: "var(--color-light-primary, #333)",
996
+ fontSize: 13,
997
+ padding: "3px 8px 3px 4px",
998
+ minWidth: 0,
999
+ maxWidth: "100%",
1000
+ } }))) : 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 },
1001
+ React.createElement(ViewerTagView, { slug: "?", displayName: "Account", onTagClick: () => setPickerOpen(!pickerOpen), tagExpanded: pickerOpen, tagLabel: "Account and handles" }))) : pickerInteractive ? (React.createElement("div", { ref: pickerWrapRef },
1002
+ 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
1003
  (() => {
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));
1004
+ const wizardBanner = (props.publishWizardActive === true || props.publishWizardError === true) && props.onPublish;
1005
+ if (wizardBanner && props.selectedNav !== "share") {
1006
+ return (React.createElement("div", { style: { padding: "0 14px", flexShrink: 0 } },
1007
+ 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 })));
1008
+ }
1009
+ if (props.autoPublishError === true && props.selectedNav !== "share") {
1010
+ return (React.createElement("div", { style: { padding: "0 14px", flexShrink: 0 } },
1011
+ React.createElement(PublishBanner, { variant: undefined, autoPublishError: true, onRetryAutoPublish: props.onRetryAutoPublish })));
1012
+ }
1013
+ if (props.statusBanner && props.selectedNav !== "share") {
1014
+ return React.createElement("div", { style: { padding: "0 14px", flexShrink: 0 } }, props.statusBanner);
1015
+ }
1016
+ if (props.versionLocked === true && props.selectedNav !== "share") {
1017
+ return (React.createElement("div", { style: { padding: "0 14px", flexShrink: 0 } },
1018
+ React.createElement(PublishBanner, { variant: undefined, locked: true, onUnlock: props.onUnlock })));
1019
+ }
1020
+ if (props.signedOutSavePrompt && props.selectedNav !== "share") {
1021
+ return (React.createElement("div", { style: { padding: "0 14px", flexShrink: 0 } },
1022
+ React.createElement(SignedOutSavePrompt, { onSignIn: props.onSignIn })));
1023
+ }
1024
+ if (props.selectedNav === "share")
1025
+ return null;
1026
+ const hasPendingVersion = props.publishState === "draft" || props.publishState === "old";
1027
+ if (hasPendingVersion && Boolean(props.onPublish)) {
1028
+ return (React.createElement("div", { style: { padding: "0 14px", flexShrink: 0 } },
1029
+ React.createElement(PublishBanner, { variant: props.publishState === "draft" || props.publishState === "old" ? props.publishState : undefined, onPublish: props.onPublish, publishing: props.publishing })));
1030
+ }
1031
+ return null;
682
1032
  })(),
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,
1033
+ editorTabForRender != null && props.selectedNav !== "share" && !agentPanelActive && (React.createElement("div", { "data-vibe-card-editor-tabs": true, style: { padding: "0 14px 6px", flexShrink: 0 } },
1034
+ React.createElement(EditorTabStrip, { tab: editorTabForRender, disabledTabs: props.disabledEditorTabs, onTab: (t) => {
1035
+ if (props.disabledEditorTabs?.includes(t) === true)
1036
+ return;
1037
+ props.onEditorTab?.(t);
1038
+ }, standing: {
1039
+ viewerMode: props.viewerMode,
1040
+ memberReadOnly: props.memberReadOnly,
1041
+ adminMode: props.adminMode,
1042
+ versionLocked: props.versionLocked,
1043
+ publishState: props.publishState,
1044
+ publishAccess: props.publishAccess,
1045
+ } }))),
1046
+ (props.body == null || editorTabDisabled) && props.composerControls && !agentPanelActive && (React.createElement("div", { "data-testid": "vibe-card-composer-controls", "aria-hidden": streamLive ? true : undefined, inert: streamLive ? true : undefined, style: {
1047
+ padding: "0 14px",
1048
+ marginBottom: 6,
690
1049
  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 }))),
1050
+ ...(streamLive ? { visibility: "hidden" } : null),
1051
+ } }, props.composerControls)),
1052
+ React.createElement("div", { ref: scrollRegionRef, "data-testid": "vibe-card-scroll", style: {
1053
+ padding: "0 14px 4px",
1054
+ overflowY: "auto",
1055
+ overflowX: "hidden",
1056
+ flex: "1 1 auto",
1057
+ minHeight: 0,
1058
+ ...((props.bodyFill && props.body) || agentPanelActive || props.body == null
1059
+ ? { display: "flex", flexDirection: "column" }
1060
+ : null),
1061
+ } },
1062
+ retainedEditorMounted && (React.createElement("div", { hidden: !retainedEditorVisible || agentPanelActive, inert: !retainedEditorVisible || agentPanelActive ? true : undefined, style: retainedEditorVisible && !agentPanelActive ? { flex: "1 1 auto", minHeight: 0 } : { display: "none" } }, props.retainedEditor)),
1063
+ agentPanelActive ? (React.createElement("div", { style: { flex: "1 1 auto", minHeight: 0 } }, props.agentPanel)) : retainedEditorVisible && props.retainedEditor != null ? null : editorTabForRender === "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 && editorTabDisabled === false ? (props.bodyFill ? (React.createElement("div", { style: { flex: "1 1 auto", minHeight: 0 } }, props.body)) : (props.body)) : (React.createElement(React.Fragment, null,
1064
+ React.createElement("div", { ref: chipsRegionRef, style: streamFillActive
1065
+ ? { flex: "1 1 auto", minHeight: 0, display: "flex", flexDirection: "column" }
1066
+ : { flex: "1 0 auto", minHeight: 0 } },
1067
+ 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)),
1068
+ React.createElement("div", { "data-testid": "vibe-card-stream-region", style: {
1069
+ position: "relative",
1070
+ ...(chipBuildLive ? { display: "flex", flexDirection: "column" } : {}),
1071
+ ...(streamFillActive ? { flex: "1 1 auto", minHeight: 0 } : null),
1072
+ } },
1073
+ props.streamBody && (React.createElement("div", { "data-testid": streamLive ? undefined : "vibe-card-stream-settled", style: streamLive
1074
+ ? {
1075
+ ...(chipBuildLive ? { flex: "1 1 auto" } : { position: "absolute", inset: 0 }),
1076
+ display: "flex",
1077
+ flexDirection: "column",
1078
+ minHeight: 0,
1079
+ }
1080
+ : { marginBottom: 8 } },
1081
+ React.createElement("div", { style: streamLive ? { flex: "1 1 auto", minHeight: 0, overflowY: "auto" } : undefined }, props.streamBody),
1082
+ props.liveDuringBuild === true && React.createElement(PendingMessages, { messages: queued }))),
1083
+ 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" },
1084
+ React.createElement(ChatMessageRow, { role: "ai", rowAttrs: { "data-message-role": "suggestions" } },
1085
+ React.createElement("div", { className: "vibes-chip-well flex flex-col", "data-testid": "vibe-chip-set" },
1086
+ (props.seedOffer || props.upgradeChips) && (React.createElement("div", { className: "flex flex-col gap-[6px]", inert: props.activeChip !== undefined },
1087
+ props.seedOffer,
1088
+ props.upgradeChips)),
1089
+ visibleChips && visibleChips.length > 0 && (React.createElement(OptionButtons, { options: visibleChips, activeOption: props.activeChip, inSet: true, isFirst: props.activeChip === undefined, firstMessage: props.isOwner
1090
+ ? "Suggested requests:"
1091
+ : "Describe a change to remix your own copy of this app:", onSelect: (o) => {
1092
+ const r = props.onSelectChip?.(o);
1093
+ return r && typeof r === "object" && "then" in r ? r.then((ok) => ok ?? false) : false;
1094
+ }, decorate: (option) => decorateChip(option, props) }))))))),
1095
+ props.streamFooter !== undefined && React.createElement("div", { className: "mx-auto w-full max-w-5xl" }, props.streamFooter)))))),
1096
+ (props.body == null || editorTabDisabled) && !agentPanelActive && (React.createElement("div", { style: { flexShrink: 0, padding: "0 14px 10px" } },
1097
+ 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
1098
  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: {
1099
+ React.createElement("div", { style: { width: 120, height: 50, marginLeft: "auto" } }))) : (React.createElement("div", { "data-vibe-card-nav": true, style: {
798
1100
  display: "flex",
799
1101
  alignItems: "center",
800
1102
  gap: "var(--uvc-nav-gap, 6px)",
801
1103
  padding: "0 8px",
802
- borderTop: "1px solid var(--color-light-decorative-00, #e5e5e5)",
1104
+ borderTop: "1px solid transparent",
803
1105
  flexShrink: 0,
804
1106
  } },
805
- React.createElement(NavIcon, { label: "Home", color: "#2563eb", onClick: props.onHome },
1107
+ React.createElement(NavIcon, { label: "Home", color: "var(--uvc-nav-home, #2563eb)", onClick: props.onHome },
806
1108
  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
1109
  React.createElement("path", { d: "M3 12l9-9 9 9" }),
808
1110
  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 },
1111
+ 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
1112
  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
1113
  React.createElement("path", { d: "M12 20h9" }),
815
1114
  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 +1216,7 @@ export function UnifiedVibeCard(props) {
917
1216
  } },
918
1217
  React.createElement("span", { "aria-hidden": true, style: { width: 6, height: 6, borderRadius: "50%", background: "#f59e0b" } }),
919
1218
  "draft")),
920
- React.createElement("button", { type: "button", "aria-label": open
1219
+ React.createElement("button", { type: "button", "data-vibe-switch-glass": true, "aria-label": open
921
1220
  ? "Close vibe menu"
922
1221
  : (props.pendingRequestCount ?? 0) > 0
923
1222
  ?
@@ -933,10 +1232,10 @@ export function UnifiedVibeCard(props) {
933
1232
  position: "relative",
934
1233
  overflow: "visible",
935
1234
  } },
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 }),
1235
+ switchGlassColor !== ovalColors.neutral && (React.createElement(React.Fragment, { key: `${switchGlassColor}|${props.switchOvalStatusKey ?? ""}` },
1236
+ React.createElement("span", { "data-vibe-switch-rest": true, "aria-hidden": "true" }),
1237
+ 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 } }))),
1238
+ React.createElement(VibesSwitch, { size: 60, style: { position: "absolute", right: 0, bottom: 0, pointerEvents: "none" }, isActive: !open, isTwinkling: !open && isTwinkling, isTeasing: !open && isTeasing }),
940
1239
  !open && (props.pendingRequestCount ?? 0) > 0 && (React.createElement("span", { "data-testid": "vibe-switch-pending-badge", "aria-hidden": "true", style: {
941
1240
  position: "absolute",
942
1241
  top: -4,
@@ -1218,22 +1517,22 @@ function FastPathToggle({ blessed, onClick }) {
1218
1517
  function NavIcon({ label, color, selected, onClick, buttonRef, badgeCount, children, }) {
1219
1518
  const showBadge = typeof badgeCount === "number" && badgeCount > 0;
1220
1519
  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: {
1520
+ 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
1521
  position: "relative",
1223
1522
  width: "var(--uvc-nav-size, 38px)",
1224
1523
  height: "var(--uvc-nav-size, 38px)",
1225
1524
  flexShrink: 0,
1226
1525
  boxSizing: "border-box",
1227
1526
  borderRadius: "50%",
1228
- background: "transparent",
1527
+ background: "var(--uvc-nav-fill)",
1229
1528
  display: "flex",
1230
1529
  alignItems: "center",
1231
1530
  justifyContent: "center",
1232
1531
  color,
1233
1532
  fontSize: 16,
1234
1533
  cursor: "pointer",
1235
- border: `1px solid ${color}`,
1236
- boxShadow: selected ? `0 0 0 3px ${color}` : "none",
1534
+ border: selected ? "var(--uvc-nav-selected-width) solid currentColor" : "1px solid var(--uvc-nav-border)",
1535
+ boxShadow: selected ? "var(--uvc-nav-selected-shadow)" : "none",
1237
1536
  } },
1238
1537
  children,
1239
1538
  showBadge ? (React.createElement("span", { "aria-hidden": "true", style: {
@@ -1334,9 +1633,10 @@ function OtherRow({ value, onValueChange, queued, onQueuedChange, onSubmitOther,
1334
1633
  const submitDisabled = attachmentsCtl.busy || (Boolean(generating) && attachmentsCtl.readyAttachments.length > 0);
1335
1634
  const sendNow = liveDuringBuild === true && canSendNow === true && queued.length === 0 && attachmentsCtl.readyAttachments.length === 0;
1336
1635
  const hasUnsentContent = value.trim() !== "" || attachmentsCtl.readyAttachments.length > 0;
1636
+ const composerDrop = composerFileDrop({ onFiles: attachmentsCtl.add });
1337
1637
  return (React.createElement(React.Fragment, null,
1338
1638
  React.createElement(AttachmentChips, { chips: attachmentsCtl.attachments, onRemove: attachmentsCtl.remove }),
1339
- React.createElement("form", { onSubmit: (e) => {
1639
+ React.createElement("form", { onDragOver: composerDrop.onDragOver, onDrop: composerDrop.onDrop, onSubmit: (e) => {
1340
1640
  e.preventDefault();
1341
1641
  const text = value.trim();
1342
1642
  const files = attachmentsCtl.readyAttachments;
@@ -1391,7 +1691,9 @@ function OtherRow({ value, onValueChange, queued, onQueuedChange, onSubmitOther,
1391
1691
  ? "Sends when the current change finishes"
1392
1692
  : undefined, style: {
1393
1693
  ...OTHER_ROW_BUTTON_STYLE,
1394
- background: "var(--vibes-blue, #3b82f6)",
1694
+ background: "var(--uvc-submit-fill, rgba(64,89,114,.2))",
1695
+ color: "var(--uvc-submit-arrow, var(--vibes-cream, #fffff0))",
1696
+ boxShadow: "none",
1395
1697
  opacity: submitDisabled ? 0.5 : 1,
1396
1698
  cursor: submitDisabled ? "default" : "pointer",
1397
1699
  } },