@vibes.diy/base 14.3.21 → 14.3.22

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.
Files changed (222) hide show
  1. package/components/StyleControls.js +27 -3
  2. package/components/StyleControls.js.map +1 -1
  3. package/components/VibesSwitch.d.ts +1 -2
  4. package/components/VibesSwitch.js +4 -59
  5. package/components/VibesSwitch.js.map +1 -1
  6. package/components/chip-fast-path.d.ts +6 -0
  7. package/components/chip-fast-path.js +2 -0
  8. package/components/chip-fast-path.js.map +1 -0
  9. package/components/editor-tabs.d.ts +3 -0
  10. package/components/editor-tabs.js +1 -0
  11. package/components/editor-tabs.js.map +1 -1
  12. package/components/index.d.ts +2 -17
  13. package/components/index.js +2 -17
  14. package/components/index.js.map +1 -1
  15. package/components/vibe-menu/VibeMenu.d.ts +16 -0
  16. package/components/vibe-menu/VibeMenu.js +99 -0
  17. package/components/vibe-menu/VibeMenu.js.map +1 -0
  18. package/components/vibe-menu/cards/AccountCard.d.ts +40 -0
  19. package/components/vibe-menu/cards/AccountCard.js +234 -0
  20. package/components/vibe-menu/cards/AccountCard.js.map +1 -0
  21. package/components/vibe-menu/cards/CodeCard.d.ts +20 -0
  22. package/components/vibe-menu/cards/CodeCard.js +109 -0
  23. package/components/vibe-menu/cards/CodeCard.js.map +1 -0
  24. package/components/vibe-menu/cards/CodeModeWorkspace.d.ts +31 -0
  25. package/components/vibe-menu/cards/CodeModeWorkspace.js +107 -0
  26. package/components/vibe-menu/cards/CodeModeWorkspace.js.map +1 -0
  27. package/components/vibe-menu/cards/HomeCard.d.ts +22 -0
  28. package/components/vibe-menu/cards/HomeCard.js +64 -0
  29. package/components/vibe-menu/cards/HomeCard.js.map +1 -0
  30. package/components/vibe-menu/cards/ShareCard.d.ts +9 -0
  31. package/components/vibe-menu/cards/ShareCard.js +84 -0
  32. package/components/vibe-menu/cards/ShareCard.js.map +1 -0
  33. package/components/vibe-menu/cards/ShareDrills.d.ts +35 -0
  34. package/components/vibe-menu/cards/ShareDrills.js +146 -0
  35. package/components/vibe-menu/cards/ShareDrills.js.map +1 -0
  36. package/components/vibe-menu/cards/ShareLinkRows.d.ts +6 -0
  37. package/components/vibe-menu/cards/ShareLinkRows.js +27 -0
  38. package/components/vibe-menu/cards/ShareLinkRows.js.map +1 -0
  39. package/components/vibe-menu/cards/share-actions.d.ts +13 -0
  40. package/components/vibe-menu/cards/share-actions.js +37 -0
  41. package/components/vibe-menu/cards/share-actions.js.map +1 -0
  42. package/components/vibe-menu/cards/share-card.styles.d.ts +1 -0
  43. package/components/vibe-menu/cards/share-card.styles.js +90 -0
  44. package/components/vibe-menu/cards/share-card.styles.js.map +1 -0
  45. package/components/vibe-menu/cards/share-copy.d.ts +24 -0
  46. package/components/vibe-menu/cards/share-copy.js +64 -0
  47. package/components/vibe-menu/cards/share-copy.js.map +1 -0
  48. package/components/vibe-menu/cards/share-parts.d.ts +46 -0
  49. package/components/vibe-menu/cards/share-parts.js +87 -0
  50. package/components/vibe-menu/cards/share-parts.js.map +1 -0
  51. package/components/vibe-menu/code-tabs.d.ts +11 -0
  52. package/components/vibe-menu/code-tabs.js +32 -0
  53. package/components/vibe-menu/code-tabs.js.map +1 -0
  54. package/components/vibe-menu/copy.d.ts +7 -0
  55. package/components/vibe-menu/copy.js +13 -0
  56. package/components/vibe-menu/copy.js.map +1 -0
  57. package/components/vibe-menu/fixtures.d.ts +42 -0
  58. package/components/vibe-menu/fixtures.js +184 -0
  59. package/components/vibe-menu/fixtures.js.map +1 -0
  60. package/components/vibe-menu/geometry.d.ts +24 -0
  61. package/components/vibe-menu/geometry.js +28 -0
  62. package/components/vibe-menu/geometry.js.map +1 -0
  63. package/components/vibe-menu/index.d.ts +42 -0
  64. package/components/vibe-menu/index.js +43 -0
  65. package/components/vibe-menu/index.js.map +1 -0
  66. package/components/vibe-menu/menu-state.d.ts +42 -0
  67. package/components/vibe-menu/menu-state.js +82 -0
  68. package/components/vibe-menu/menu-state.js.map +1 -0
  69. package/components/vibe-menu/moments.d.ts +13 -0
  70. package/components/vibe-menu/moments.js +39 -0
  71. package/components/vibe-menu/moments.js.map +1 -0
  72. package/components/vibe-menu/motion.d.ts +10 -0
  73. package/components/vibe-menu/motion.js +10 -0
  74. package/components/vibe-menu/motion.js.map +1 -0
  75. package/components/vibe-menu/open-resolver.d.ts +46 -0
  76. package/components/vibe-menu/open-resolver.js +91 -0
  77. package/components/vibe-menu/open-resolver.js.map +1 -0
  78. package/components/vibe-menu/parts/AccessToggle.d.ts +13 -0
  79. package/components/vibe-menu/parts/AccessToggle.js +58 -0
  80. package/components/vibe-menu/parts/AccessToggle.js.map +1 -0
  81. package/components/vibe-menu/parts/ActionPill.d.ts +18 -0
  82. package/components/vibe-menu/parts/ActionPill.js +21 -0
  83. package/components/vibe-menu/parts/ActionPill.js.map +1 -0
  84. package/components/vibe-menu/parts/BackRow.d.ts +8 -0
  85. package/components/vibe-menu/parts/BackRow.js +10 -0
  86. package/components/vibe-menu/parts/BackRow.js.map +1 -0
  87. package/components/vibe-menu/parts/Chip.d.ts +37 -0
  88. package/components/vibe-menu/parts/Chip.js +62 -0
  89. package/components/vibe-menu/parts/Chip.js.map +1 -0
  90. package/components/vibe-menu/parts/Composer.d.ts +39 -0
  91. package/components/vibe-menu/parts/Composer.js +47 -0
  92. package/components/vibe-menu/parts/Composer.js.map +1 -0
  93. package/components/vibe-menu/parts/DoneButton.d.ts +5 -0
  94. package/components/vibe-menu/parts/DoneButton.js +5 -0
  95. package/components/vibe-menu/parts/DoneButton.js.map +1 -0
  96. package/components/vibe-menu/parts/Glyph.d.ts +9 -0
  97. package/components/vibe-menu/parts/Glyph.js +123 -0
  98. package/components/vibe-menu/parts/Glyph.js.map +1 -0
  99. package/components/vibe-menu/parts/GrantControls.d.ts +13 -0
  100. package/components/vibe-menu/parts/GrantControls.js +61 -0
  101. package/components/vibe-menu/parts/GrantControls.js.map +1 -0
  102. package/components/vibe-menu/parts/MemberRow.d.ts +37 -0
  103. package/components/vibe-menu/parts/MemberRow.js +70 -0
  104. package/components/vibe-menu/parts/MemberRow.js.map +1 -0
  105. package/components/vibe-menu/parts/MenuCard.d.ts +10 -0
  106. package/components/vibe-menu/parts/MenuCard.js +7 -0
  107. package/components/vibe-menu/parts/MenuCard.js.map +1 -0
  108. package/components/vibe-menu/parts/MomentLine.d.ts +23 -0
  109. package/components/vibe-menu/parts/MomentLine.js +58 -0
  110. package/components/vibe-menu/parts/MomentLine.js.map +1 -0
  111. package/components/vibe-menu/parts/Row.d.ts +41 -0
  112. package/components/vibe-menu/parts/Row.js +60 -0
  113. package/components/vibe-menu/parts/Row.js.map +1 -0
  114. package/components/vibe-menu/parts/SectionStrip.d.ts +14 -0
  115. package/components/vibe-menu/parts/SectionStrip.js +26 -0
  116. package/components/vibe-menu/parts/SectionStrip.js.map +1 -0
  117. package/components/vibe-menu/parts/VibesSwitchGlass.d.ts +24 -0
  118. package/components/vibe-menu/parts/VibesSwitchGlass.js +66 -0
  119. package/components/vibe-menu/parts/VibesSwitchGlass.js.map +1 -0
  120. package/components/vibe-menu/parts/radio-keys.d.ts +5 -0
  121. package/components/vibe-menu/parts/radio-keys.js +13 -0
  122. package/components/vibe-menu/parts/radio-keys.js.map +1 -0
  123. package/components/vibe-menu/parts/use-composer-draft.d.ts +9 -0
  124. package/components/vibe-menu/parts/use-composer-draft.js +94 -0
  125. package/components/vibe-menu/parts/use-composer-draft.js.map +1 -0
  126. package/components/vibe-menu/share-fixtures.d.ts +15 -0
  127. package/components/vibe-menu/share-fixtures.js +72 -0
  128. package/components/vibe-menu/share-fixtures.js.map +1 -0
  129. package/components/vibe-menu/shell/VibeMenuShell.d.ts +30 -0
  130. package/components/vibe-menu/shell/VibeMenuShell.js +124 -0
  131. package/components/vibe-menu/shell/VibeMenuShell.js.map +1 -0
  132. package/components/vibe-menu/shell/menu-sequence.d.ts +20 -0
  133. package/components/vibe-menu/shell/menu-sequence.js +35 -0
  134. package/components/vibe-menu/shell/menu-sequence.js.map +1 -0
  135. package/components/vibe-menu/shell/outline-geometry.d.ts +63 -0
  136. package/components/vibe-menu/shell/outline-geometry.js +83 -0
  137. package/components/vibe-menu/shell/outline-geometry.js.map +1 -0
  138. package/components/vibe-menu/shell/shell.styles.d.ts +1 -0
  139. package/components/vibe-menu/shell/shell.styles.js +386 -0
  140. package/components/vibe-menu/shell/shell.styles.js.map +1 -0
  141. package/components/vibe-menu/shell/useMenuSequencer.d.ts +33 -0
  142. package/components/vibe-menu/shell/useMenuSequencer.js +490 -0
  143. package/components/vibe-menu/shell/useMenuSequencer.js.map +1 -0
  144. package/components/vibe-menu/slices.d.ts +182 -0
  145. package/components/vibe-menu/slices.js +5 -0
  146. package/components/vibe-menu/slices.js.map +1 -0
  147. package/components/vibe-menu/use-remix-hint.d.ts +9 -0
  148. package/components/vibe-menu/use-remix-hint.js +21 -0
  149. package/components/vibe-menu/use-remix-hint.js.map +1 -0
  150. package/components/vibe-menu/vibe-menu.styles.d.ts +5 -0
  151. package/components/vibe-menu/vibe-menu.styles.js +1424 -0
  152. package/components/vibe-menu/vibe-menu.styles.js.map +1 -0
  153. package/package.json +4 -4
  154. package/components/AccountView.d.ts +0 -10
  155. package/components/AccountView.js +0 -31
  156. package/components/AccountView.js.map +0 -1
  157. package/components/AdminModeControl.d.ts +0 -7
  158. package/components/AdminModeControl.js +0 -9
  159. package/components/AdminModeControl.js.map +0 -1
  160. package/components/CodingIndicator.d.ts +0 -6
  161. package/components/CodingIndicator.js +0 -53
  162. package/components/CodingIndicator.js.map +0 -1
  163. package/components/CreditsDetailMenu.d.ts +0 -15
  164. package/components/CreditsDetailMenu.js +0 -97
  165. package/components/CreditsDetailMenu.js.map +0 -1
  166. package/components/CreditsRingView.d.ts +0 -15
  167. package/components/CreditsRingView.js +0 -77
  168. package/components/CreditsRingView.js.map +0 -1
  169. package/components/EditorTabStrip.d.ts +0 -10
  170. package/components/EditorTabStrip.js +0 -15
  171. package/components/EditorTabStrip.js.map +0 -1
  172. package/components/LinkLineControls.d.ts +0 -9
  173. package/components/LinkLineControls.js +0 -72
  174. package/components/LinkLineControls.js.map +0 -1
  175. package/components/MadeHereView.d.ts +0 -8
  176. package/components/MadeHereView.js +0 -37
  177. package/components/MadeHereView.js.map +0 -1
  178. package/components/RecentVibesList.d.ts +0 -19
  179. package/components/RecentVibesList.js +0 -106
  180. package/components/RecentVibesList.js.map +0 -1
  181. package/components/RecommendedView.d.ts +0 -22
  182. package/components/RecommendedView.js +0 -172
  183. package/components/RecommendedView.js.map +0 -1
  184. package/components/RemixInviteView.d.ts +0 -4
  185. package/components/RemixInviteView.js +0 -11
  186. package/components/RemixInviteView.js.map +0 -1
  187. package/components/SeedDataOffer.d.ts +0 -8
  188. package/components/SeedDataOffer.js +0 -15
  189. package/components/SeedDataOffer.js.map +0 -1
  190. package/components/SignedInDiscoveryView.d.ts +0 -17
  191. package/components/SignedInDiscoveryView.js +0 -30
  192. package/components/SignedInDiscoveryView.js.map +0 -1
  193. package/components/SignedOutDiscoveryView.d.ts +0 -7
  194. package/components/SignedOutDiscoveryView.js +0 -15
  195. package/components/SignedOutDiscoveryView.js.map +0 -1
  196. package/components/SignedOutSavePrompt.d.ts +0 -4
  197. package/components/SignedOutSavePrompt.js +0 -33
  198. package/components/SignedOutSavePrompt.js.map +0 -1
  199. package/components/UnifiedVibeCard.d.ts +0 -129
  200. package/components/UnifiedVibeCard.js +0 -1658
  201. package/components/UnifiedVibeCard.js.map +0 -1
  202. package/components/UnifiedVibeCardSettings.styles.d.ts +0 -3
  203. package/components/UnifiedVibeCardSettings.styles.js +0 -95
  204. package/components/UnifiedVibeCardSettings.styles.js.map +0 -1
  205. package/components/UnifiedVibeCardShare.styles.d.ts +0 -3
  206. package/components/UnifiedVibeCardShare.styles.js +0 -72
  207. package/components/UnifiedVibeCardShare.styles.js.map +0 -1
  208. package/components/ViewerAccessSummary.d.ts +0 -28
  209. package/components/ViewerAccessSummary.js +0 -105
  210. package/components/ViewerAccessSummary.js.map +0 -1
  211. package/components/ViewerStandingView.d.ts +0 -7
  212. package/components/ViewerStandingView.js +0 -10
  213. package/components/ViewerStandingView.js.map +0 -1
  214. package/hooks/useCardFloat.d.ts +0 -6
  215. package/hooks/useCardFloat.js +0 -191
  216. package/hooks/useCardFloat.js.map +0 -1
  217. package/hooks/useHomeMotionPress.d.ts +0 -17
  218. package/hooks/useHomeMotionPress.js +0 -93
  219. package/hooks/useHomeMotionPress.js.map +0 -1
  220. package/motion/card-motion-source.d.ts +0 -23
  221. package/motion/card-motion-source.js +0 -50
  222. package/motion/card-motion-source.js.map +0 -1
@@ -1,1658 +0,0 @@
1
- import { UNIFIED_VIBE_SHARE_LIGHT_VARS, UNIFIED_VIBE_SHARE_DARK_VARS, UNIFIED_VIBE_SHARE_CSS, } from "./UnifiedVibeCardShare.styles.js";
2
- import { PublishBanner } from "./PublishBanner.js";
3
- import React, { useCallback, useEffect, useRef, useState } from "react";
4
- import { UNIFIED_VIBE_SETTINGS_LIGHT_VARS, UNIFIED_VIBE_SETTINGS_DARK_VARS, UNIFIED_VIBE_SETTINGS_CSS, } from "./UnifiedVibeCardSettings.styles.js";
5
- import { VibesSwitch } from "./VibesSwitch.js";
6
- import { ovalColors } from "./VibesSwitch.styles.js";
7
- import { EditorTabStrip } from "./EditorTabStrip.js";
8
- import { ViewerStandingView } from "./ViewerStandingView.js";
9
- import { OptionButtons } from "./OptionButtons.js";
10
- import { ChatMessageRow } from "./ChatMessageRow.js";
11
- import { ViewerTagView } from "./ViewerTagView.js";
12
- import { SignedOutSavePrompt } from "./SignedOutSavePrompt.js";
13
- import { HandlePickerMenu } from "./HandlePickerMenu.js";
14
- import { CreditsRingView } from "./CreditsRingView.js";
15
- import { CreditsDetailMenu } from "./CreditsDetailMenu.js";
16
- import { CodingIndicator } from "./CodingIndicator.js";
17
- import { CopyLinkIconButton } from "./LinkLineControls.js";
18
- import { AttachmentButton, AttachmentChips } from "./AttachmentRow.js";
19
- import { useAttachments } from "../attachments/use-attachments.js";
20
- import { composerFileDrop } from "../attachments/composer-file-drop.js";
21
- import { PeopleIcon } from "./icons/index.js";
22
- import { useCardFloat } from "../hooks/useCardFloat.js";
23
- import { useHomeMotionPress } from "../hooks/useHomeMotionPress.js";
24
- import { HOME_BRAND_FONT } from "./homeBrandFont.js";
25
- import { SAFE_AREA_TOP } from "../theme/safe-area.js";
26
- const UNIFIED_VIBE_CARD_DARK_VARS = `
27
- --uvc-closed-build-bg: #28262f;
28
- --uvc-closed-build-text: #b5afc2;
29
- --color-dark-primary: #e0e0e0;
30
- --color-dark-secondary: #aaaaaa;
31
- --color-dark-decorative-00: #333333;
32
- --color-dark-decorative-01: #444444;
33
- --color-dark-decorative-02: #e0e0e0;
34
- --color-dark-background-00: #1a1a1a;
35
- --color-dark-background-01: #222222;
36
- --color-dark-background-02: #222222;
37
- /* The global field palette follows the OS. Pin the same semantic dark
38
- values here so an explicit .dark host also darkens fields and borders. */
39
- --vibes-field-bg: var(--vibes-ink-deep, #141414);
40
- --vibes-card-border: var(--vibes-gray-mid, #555555);
41
- --uvc-amber-text: #fbbf24;
42
- --uvc-blue-text: #93c5fd;
43
- --uvc-purple-text: #c4b5fd;
44
- --uvc-suggestion-bg: #262c34;
45
- --uvc-suggestion-border: #6c7685;
46
- --uvc-suggestion-text: #e5e7eb;
47
- --uvc-helper-text: #c2cbd8;
48
- --uvc-build-text: #bac4d2;
49
- --uvc-nav-edit: #fb923c;
50
- --uvc-nav-share: #4ade80;
51
- --uvc-nav-faq: #cbd5e1;
52
- --color-light-primary: var(--color-dark-primary, #e0e0e0);
53
- --color-light-secondary: var(--color-dark-secondary, #e0e0e0);
54
- --color-light-decorative-00: var(--color-dark-decorative-00, #333333);
55
- --color-light-decorative-01: var(--color-dark-decorative-01, #444444);
56
- --color-light-decorative-02: var(--color-dark-decorative-02, #e0e0e0);
57
- --color-light-background-00: var(--color-dark-background-00, #1a1a1a);
58
- --color-light-background-01: var(--color-dark-background-01, #222222);
59
- --color-light-background-02: var(--color-dark-background-02, #222222);`;
60
- const UNIFIED_VIBE_CARD_GLASS_LIGHT_VARS = `${UNIFIED_VIBE_SETTINGS_LIGHT_VARS}
61
- ${UNIFIED_VIBE_SHARE_LIGHT_VARS}
62
- --uvc-reading-ink:#161b18;
63
- --uvc-reading-label:#414b44;
64
- --uvc-reading-veil:rgba(255,255,240,.25);
65
- --uvc-submit-arrow:var(--vibes-cream, #fffff0);
66
- --uvc-submit-fill:rgba(64,89,114,.2);
67
- --uvc-notch-width: 52px;
68
- --uvc-notch-opacity: .25;
69
- --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));
70
- --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);
71
- --uvc-composer-border: rgba(16,26,24,.28);
72
- --uvc-glass-fill: rgba(255,255,255,.8);
73
- --uvc-glass-edge: rgba(255,255,255,.3);
74
- --uvc-glass-highlight: rgba(255,255,255,.8);
75
- --uvc-menu-strip: rgba(255,255,255,.18);
76
- --uvc-menu-tab: rgba(255,255,255,.45);
77
- --uvc-share-muted: #424954;
78
- --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);
79
- --color-light-background-00: rgba(255,255,255,.24);
80
- --color-light-background-01: rgba(255,255,255,.2);
81
- --uvc-suggestion-bg: rgba(255,255,255,.28);
82
- --vibes-field-bg: rgba(255,255,255,.2);
83
- --uvc-nav-home: #2563eb;`;
84
- const UNIFIED_VIBE_CARD_OBSIDIAN_VARS = `
85
- --uvc-glass-fill: rgba(6,9,10,.94);
86
- --uvc-glass-edge: rgba(255,255,255,.12);
87
- --uvc-glass-highlight: rgba(255,255,255,.25);
88
- --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);
89
- --uvc-nav-home: #93b4ff;`;
90
- const UNIFIED_VIBE_CARD_GLASS_DARK_VARS = `${UNIFIED_VIBE_CARD_OBSIDIAN_VARS}
91
- ${UNIFIED_VIBE_SETTINGS_DARK_VARS}
92
- ${UNIFIED_VIBE_SHARE_DARK_VARS}
93
- --uvc-reading-ink:#fffff0;
94
- --uvc-reading-label:#d0d6ce;
95
- --uvc-reading-veil:rgba(10,16,14,.25);
96
- --uvc-submit-arrow:#aaa;
97
- --uvc-submit-fill:#000;
98
- --uvc-notch-opacity: .125;
99
- --uvc-notch-width: 52px;
100
- --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));
101
- --uvc-notch-shadow: inset 0 0 0 1px rgba(230,255,245,.2),0 1px 2px rgba(0,0,0,.3);
102
- --uvc-composer-border: rgba(210,239,228,.28);
103
- --uvc-glass-fill: rgba(6,9,10,.8);
104
- --uvc-menu-strip: rgba(16,26,24,.18);
105
- --uvc-menu-tab: rgba(230,255,245,.12);
106
- --uvc-share-muted: #b7becb;
107
- --color-light-background-00: rgba(16,26,24,.28);
108
- --color-light-background-01: rgba(16,26,24,.24);
109
- --uvc-suggestion-bg: rgba(16,26,24,.32);
110
- --vibes-field-bg: rgba(16,26,24,.28);
111
- --uvc-nav-home: #93b4ff;`;
112
- const UNIFIED_VIBE_CARD_NAV_LIGHT_VARS = `
113
- --uvc-glass-fill:rgba(255,255,255,.08);
114
- --uvc-glass-edge:rgba(255,255,255,.3);
115
- --uvc-glass-highlight:rgba(255,255,255,.8);
116
- --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);
117
- --uvc-nav-home:#2d5bd1; --uvc-nav-edit:#a34b0a; --uvc-nav-share:#18713b;
118
- --uvc-nav-fill:rgba(255,255,240,.92);
119
- --uvc-nav-border:color-mix(in srgb,currentColor 65%,transparent);
120
- --uvc-nav-selected-width:1px;
121
- --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);`;
122
- const UNIFIED_VIBE_CARD_NAV_DARK_VARS = `${UNIFIED_VIBE_CARD_OBSIDIAN_VARS}
123
- --uvc-nav-edit:#fb923c; --uvc-nav-share:#4ade80;
124
- --uvc-nav-fill:rgba(0,0,0,.5);
125
- --uvc-nav-border:currentColor;
126
- --uvc-nav-selected-width:3px;
127
- --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);`;
128
- const unifiedVibeCardDarkCss = `
129
- [data-unified-vibe-card] {${UNIFIED_VIBE_CARD_GLASS_LIGHT_VARS}
130
- }
131
- @media (prefers-color-scheme: dark) {
132
- [data-unified-vibe-card], [data-vibe-handle-menu], [data-vibe-version-menu], [data-vibe-closed-build-status] {${UNIFIED_VIBE_CARD_DARK_VARS}
133
- }
134
- [data-unified-vibe-card] {${UNIFIED_VIBE_CARD_GLASS_DARK_VARS}
135
- }
136
- }
137
- .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}
138
- }
139
- .dark [data-unified-vibe-card] {${UNIFIED_VIBE_CARD_GLASS_DARK_VARS}
140
- }
141
- /* The detached row owns its theme independently of the card content. */
142
- [data-vibe-card-nav] {${UNIFIED_VIBE_CARD_DARK_VARS}${UNIFIED_VIBE_CARD_NAV_LIGHT_VARS}
143
- }
144
- [data-vibe-switch-glass] {${UNIFIED_VIBE_CARD_OBSIDIAN_VARS}
145
- }
146
- /* The card's layout still includes its footer. Only its glass surface ends
147
- at the former divider: retain the outer 1px border and footer's 1px top
148
- border as transparent spacing so the circles and switch keep their layout. */
149
- [data-unified-vibe-card]::before {
150
- content: ""; position: absolute; inset: -1px -1px 50px; border-radius: 14px;
151
- }
152
- /* The agent panel retains its existing 50px switch reservation, without the
153
- ordinary nav row's extra divider pixel. Its body ends one pixel lower. */
154
- [data-unified-vibe-card][data-vibe-agent-panel="true"]::before { bottom: 49px; }
155
- [data-vibe-card-nav] { position: relative; isolation: isolate; }
156
- [data-vibe-card-nav]::before {
157
- content: ""; position: absolute; left: 2px; top: 50%; transform: translateY(-50%); z-index: -1;
158
- width: calc(3 * var(--uvc-nav-size, 38px) + 2 * var(--uvc-nav-gap, 6px) + 12px);
159
- height: calc(var(--uvc-nav-size, 38px) + 10px); border-radius: 999px;
160
- }
161
- [data-unified-vibe-card]::before, [data-vibe-card-nav]::before, [data-vibe-switch-glass]::before {
162
- box-sizing: border-box; border: 1px solid transparent; pointer-events: none;
163
- }
164
- [data-unified-vibe-card]::before, [data-vibe-card-nav]::before, [data-vibe-switch-glass]::before {
165
- background: linear-gradient(to right, transparent, var(--uvc-glass-highlight), transparent) top / 100% 1px no-repeat,
166
- linear-gradient(to bottom, var(--uvc-glass-highlight), transparent, var(--uvc-glass-edge)) left / 1px 100% no-repeat,
167
- var(--uvc-glass-fill);
168
- backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px);
169
- box-shadow: var(--uvc-glass-shadow);
170
- }
171
- [data-vibe-card-nav]::before { backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px); }
172
- @media (prefers-color-scheme: dark) {
173
- [data-vibe-card-nav] {${UNIFIED_VIBE_CARD_NAV_DARK_VARS}}
174
- [data-vibe-card-nav]::before { backdrop-filter:blur(24px); -webkit-backdrop-filter:blur(24px); }
175
- }
176
- .dark [data-vibe-card-nav] {${UNIFIED_VIBE_CARD_NAV_DARK_VARS}}
177
- .dark [data-vibe-card-nav]::before { backdrop-filter:blur(24px); -webkit-backdrop-filter:blur(24px); }
178
- .light [data-vibe-card-nav] {${UNIFIED_VIBE_CARD_NAV_LIGHT_VARS}}
179
- .light [data-vibe-card-nav]::before { backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px); }
180
- [data-vibe-card-nav] [data-vibe-nav-selected="true"] svg { stroke-width:3; }
181
- /* The arrival uses the fitted glass surface, leaving the brand artwork untouched.
182
- Keying the layer by status replays same-color arrivals without a JS timer. */
183
- [data-vibe-switch-glass]:has([data-vibe-switch-highlight])::before { visibility:hidden; }
184
- [data-vibe-switch-rest] {
185
- background:linear-gradient(to right,transparent,var(--uvc-glass-highlight),transparent) top/100% 1px no-repeat,
186
- linear-gradient(to bottom,var(--uvc-glass-highlight),transparent,var(--uvc-glass-edge)) left/1px 100% no-repeat,var(--uvc-glass-fill);
187
- box-shadow:var(--uvc-glass-shadow);
188
- animation:uvc-notification-rest 7s forwards;
189
- }
190
- [data-vibe-switch-highlight] {
191
- --uvc-arrival-fill:color-mix(in srgb,currentColor 80%,transparent);
192
- --uvc-arrival-edge:color-mix(in srgb,color-mix(in srgb,currentColor 50%,white) 60%,transparent);
193
- --uvc-arrival-highlight:color-mix(in srgb,color-mix(in srgb,currentColor 20%,white) 90%,transparent);
194
- background:linear-gradient(to right,transparent,var(--uvc-arrival-highlight),transparent) top/100% 1px no-repeat,
195
- linear-gradient(to bottom,var(--uvc-arrival-highlight),transparent,var(--uvc-arrival-edge)) left/1px 100% no-repeat,var(--uvc-arrival-fill);
196
- 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);
197
- animation:uvc-notification-glass 7s forwards;
198
- }
199
- /* Preserve the original Amber material for the yellow/pending status. */
200
- [data-vibe-switch-highlight][data-arrival-amber="true"] {
201
- --uvc-arrival-fill:rgba(191,142,66,.8);
202
- --uvc-arrival-edge:rgba(255,229,167,.6);
203
- --uvc-arrival-highlight:rgba(255,246,218,.9);
204
- }
205
- [data-vibe-switch-highlight], [data-vibe-switch-rest] {
206
- box-sizing:border-box; border:1px solid transparent; pointer-events:none;
207
- backdrop-filter:blur(24px); -webkit-backdrop-filter:blur(24px);
208
- }
209
- @keyframes uvc-notification-rest {
210
- 0%,100% { opacity:1; }
211
- 14.3%,71.4% { opacity:0; }
212
- }
213
- @keyframes uvc-notification-glass {
214
- 0% { opacity:0; }
215
- 14.3%,71.4% { opacity:1; }
216
- 100% { opacity:0; }
217
- }
218
- @media (prefers-reduced-motion:reduce) {
219
- [data-vibe-switch-highlight] { animation-name:uvc-notification-glass-reduced; }
220
- [data-vibe-switch-rest] { animation-name:uvc-notification-rest-reduced; }
221
- }
222
- @keyframes uvc-notification-rest-reduced {
223
- 0%,71.4% { opacity:0; }
224
- 100% { opacity:1; }
225
- }
226
- @keyframes uvc-notification-glass-reduced {
227
- 0%,71.4% { opacity:1; }
228
- 100% { opacity:0; }
229
- }
230
- /* Fit the painted 102px logo, excluding the SVG's transparent side margins.
231
- Match the nav oval's height without moving the existing 120×44 hit target. */
232
- [data-vibe-switch-glass] { isolation: isolate; }
233
- [data-vibe-switch-glass]::before, [data-vibe-switch-highlight], [data-vibe-switch-rest] {
234
- content: ""; position: absolute; left: 6px; right: 6px; top: calc(50% + 1px);
235
- height: calc(var(--uvc-nav-size, 38px) + 10px); transform: translateY(-50%);
236
- border-radius: 999px; z-index: -1;
237
- }
238
- [data-unified-vibe-card] form.vibes-edit-field {
239
- --vibes-card-border: var(--uvc-composer-border);
240
- }
241
- /* Reading treatment lives inside the card, away from the detached controls.
242
- Style keeps its own palette surfaces; only its text inherits the stronger ink.
243
- History and streaming share one surface marker so a build never drops the veil. */
244
- [data-vibe-card-body] {
245
- --color-light-primary:var(--uvc-reading-ink);
246
- --color-dark-primary:var(--uvc-reading-ink);
247
- --color-light-secondary:var(--uvc-reading-label);
248
- --color-dark-secondary:var(--uvc-reading-label);
249
- --vibes-card-text:var(--uvc-reading-ink);
250
- color:var(--uvc-reading-ink);
251
- }
252
- [data-vibe-card-body] .prose {
253
- color:var(--uvc-reading-ink);
254
- --tw-prose-body:var(--uvc-reading-ink); --tw-prose-bold:var(--uvc-reading-ink);
255
- --tw-prose-invert-body:var(--uvc-reading-ink); --tw-prose-invert-bold:var(--uvc-reading-ink);
256
- }
257
- [data-vibe-card-body] :is([data-message-role="narration"],[data-message-role="user-prompt"]) > :first-child {
258
- color:var(--uvc-reading-label); opacity:1;
259
- }
260
- [data-unified-vibe-card][data-vibe-reading-mode="ink-veil"] [data-vibe-reading-surface],
261
- [data-unified-vibe-card][data-vibe-reading-mode="ink-veil"] form.vibes-edit-field {
262
- background:var(--uvc-reading-veil);
263
- }
264
- [data-unified-vibe-card][data-vibe-reading-mode="ink-veil"] [data-vibe-reading-surface] { border-radius:10px; }
265
- [data-vibe-card-body] form.vibes-edit-field textarea { color:var(--uvc-reading-ink); }
266
- /* Inverse Float at 50% audition strength, preserving the outer opening scale.
267
- Total x/y travel: ovals 2.25/4.5px, contents 2.125/4.25px,
268
- glass 1.875/3.75px, header 1.375/2.75px, conversation .875/1.75px,
269
- composer .375/.75px. Nested text subtracts .25/.5px from its surface.
270
- Every layer moves in the same direction; layout and timing stay unchanged. */
271
- [data-vibe-floating="true"]::before { translate:calc(var(--vibe-float-x,0)*1.875px) calc(var(--vibe-float-y,0)*3.75px); }
272
- [data-vibe-floating="true"] [data-vibe-card-nav],
273
- [data-vibe-floating-controls="true"] [data-vibe-switch-glass] { translate:calc(var(--vibe-float-x,0)*2.25px) calc(var(--vibe-float-y,0)*4.5px); }
274
- [data-vibe-floating="true"] [data-vibe-card-nav] > button,
275
- [data-vibe-floating-controls="true"] [data-vibe-switch-glass] > svg { translate:calc(var(--vibe-float-x,0)*-.125px) calc(var(--vibe-float-y,0)*-.25px); }
276
- [data-vibe-floating="true"] [data-vibe-card-body] { translate:calc(var(--vibe-float-x,0)*1.375px) calc(var(--vibe-float-y,0)*2.75px); }
277
- [data-vibe-floating="true"] [data-testid="vibe-card-scroll"] { translate:calc(var(--vibe-float-x,0)*-.5px) calc(var(--vibe-float-y,0)*-1px); }
278
- [data-vibe-floating="true"] form.vibes-edit-field { translate:calc(var(--vibe-float-x,0)*-1px) calc(var(--vibe-float-y,0)*-2px); }
279
- [data-vibe-floating="true"] [data-vibe-float-text],
280
- [data-vibe-floating="true"] .prose,
281
- [data-vibe-floating="true"] .vibes-chip-well [data-chip-role="option"] > span:first-child { translate:calc(var(--vibe-float-x,0)*-.25px) calc(var(--vibe-float-y,0)*-.5px); }
282
- @media (prefers-reduced-motion:reduce) {
283
- [data-vibe-floating]::before, [data-vibe-floating] [data-vibe-card-body],
284
- [data-vibe-floating] [data-testid="vibe-card-scroll"], [data-vibe-floating] form.vibes-edit-field,
285
- [data-vibe-floating] [data-vibe-float-text], [data-vibe-floating] .prose,
286
- [data-vibe-floating] .vibes-chip-well [data-chip-role="option"] > span:first-child,
287
- [data-vibe-floating] [data-vibe-card-nav], [data-vibe-floating-controls] [data-vibe-switch-glass],
288
- [data-vibe-floating] [data-vibe-card-nav] > button, [data-vibe-floating-controls] [data-vibe-switch-glass] > svg { translate:none; }
289
- }
290
- .light [data-unified-vibe-card] {
291
- ${UNIFIED_VIBE_SHARE_LIGHT_VARS}
292
- --uvc-reading-ink:#161b18; --uvc-reading-label:#414b44; --uvc-reading-veil:rgba(255,255,240,.25);
293
- }
294
- ${UNIFIED_VIBE_SETTINGS_CSS}
295
- /* The selected Frost header rule fades at both ends at half opacity,
296
- without consuming any of the card's content height. */
297
- [data-vibe-card-header]::after {
298
- content: ""; position: absolute; bottom: 0; left: 0; right: 0; height: 1px;
299
- background: linear-gradient(90deg, transparent, var(--uvc-glass-edge) 22%, var(--uvc-glass-edge) 78%, transparent);
300
- opacity: .5; pointer-events: none;
301
- }
302
- ${UNIFIED_VIBE_SHARE_CSS}
303
- /* A · Line: the clip edge sits under the painted switch, inside the 120px
304
- hit box (whose SVG has transparent air at its left). 22px padding leaves
305
- the resting strip at right:132px without changing the switch geometry. */
306
- [data-vibe-closed-build-status] {
307
- position: absolute; left: 12px; right: 110px; bottom: 17px;
308
- z-index: 2; display: flex; justify-content: flex-end; padding-right: 22px;
309
- overflow: hidden; pointer-events: none; box-sizing: border-box;
310
- }
311
- [data-vibe-closed-build-line] {
312
- min-width: 0; max-width: 100%; box-sizing: border-box;
313
- padding: 3px 5px; border-radius: 3px;
314
- background: var(--uvc-closed-build-bg, #fff);
315
- color: var(--uvc-closed-build-text, #6d6973);
316
- font: 12px/18px Inter, system-ui, sans-serif;
317
- transform: translateX(calc(100% + 22px));
318
- transition: transform 240ms cubic-bezier(.22,.68,0,1);
319
- overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
320
- }
321
- [data-vibe-closed-build-line][data-revealed="true"] { transform: translateX(0); }
322
- [data-vibe-closed-build-line] [data-testid="thinking-line"] {
323
- display: flex; align-items: baseline; gap: 6px; min-width: 0;
324
- }
325
- [data-vibe-closed-build-line] [data-testid="thinking-line-glyph"] { flex-shrink: 0; }
326
- [data-vibe-closed-build-line] [data-testid="thinking-line-verb"] {
327
- min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
328
- }
329
- @media (prefers-reduced-motion: reduce) {
330
- [data-vibe-closed-build-line] { transition: none; }
331
- }
332
- /* Composer submit button: a blue pulsating glow while the field holds unsent
333
- content, nudging the user to send it — the same "something's live, act on it"
334
- cue the homepage submit-ripple gives, tinted to the button's vibes-blue.
335
- Applied via className in OtherRow, gated to typed/staged-but-unsent + enabled.
336
- Each stop keeps the .vibes-edit-btn hard offset shadow as the first layer so
337
- the brutalist depth stays put while the ring pulses on top — a bare ring-only
338
- box-shadow would override the class shadow (animations beat normal
339
- declarations) and flicker the depth out exactly when the button wants notice. */
340
- @keyframes vibesSubmitPulse {
341
- 0% {
342
- box-shadow: 2px 2px 0 0 var(--vibes-card-border), 0 0 0 0 rgba(59, 130, 246, 0.5);
343
- }
344
- 70% {
345
- box-shadow: 2px 2px 0 0 var(--vibes-card-border), 0 0 0 7px rgba(59, 130, 246, 0);
346
- }
347
- 100% {
348
- box-shadow: 2px 2px 0 0 var(--vibes-card-border), 0 0 0 0 rgba(59, 130, 246, 0);
349
- }
350
- }
351
- .vibes-submit-pulse {
352
- animation: vibesSubmitPulse 1.5s ease-out infinite;
353
- }
354
- /* Reduced motion: hold one soft static ring instead of the loop so the "ready
355
- to send" cue still reads without any movement (mirrors .submit-ripple) — layered
356
- over the same hard shadow so the brutalist depth is preserved here too. */
357
- @media (prefers-reduced-motion: reduce) {
358
- [data-unified-vibe-card], [data-vibe-card-content] {
359
- transition: none !important;
360
- }
361
- .vibes-submit-pulse {
362
- animation: none;
363
- box-shadow: 2px 2px 0 0 var(--vibes-card-border), 0 0 0 3px rgba(59, 130, 246, 0.35);
364
- }
365
- }
366
- /* Access-wizard held state (#4056): the wizard is a whole codegen turn — tens of
367
- seconds — and the banner spent all of it as a flat blue rectangle with a
368
- static "Securing…" stamp, which reads as hung rather than working. A slow
369
- indeterminate sweep across the banner plus a spinner in the held control give
370
- the wait a pulse, so "still going" is legible without a progress number we
371
- don't have. Self-contained keyframes (no global.css / Tailwind dependency) to
372
- match the rest of this card. Reduced motion holds both still — the banner
373
- keeps a soft static highlight so the state still reads as distinct. */
374
- @keyframes vibesSecuringSweep {
375
- 0% { background-position: 200% 0; }
376
- 100% { background-position: -200% 0; }
377
- }
378
- .vibes-securing {
379
- background-image: linear-gradient(100deg, transparent 35%, rgba(59, 130, 246, 0.16) 50%, transparent 65%);
380
- background-size: 250% 100%;
381
- background-repeat: no-repeat;
382
- animation: vibesSecuringSweep 1.8s linear infinite;
383
- }
384
- @keyframes vibesSecuringSpin {
385
- to { transform: rotate(360deg); }
386
- }
387
- .vibes-securing-spinner {
388
- animation: vibesSecuringSpin 0.8s linear infinite;
389
- }
390
- @media (prefers-reduced-motion: reduce) {
391
- .vibes-securing {
392
- animation: none;
393
- background-image: linear-gradient(100deg, transparent 35%, rgba(59, 130, 246, 0.1) 50%, transparent 65%);
394
- background-size: 100% 100%;
395
- }
396
- .vibes-securing-spinner {
397
- animation: none;
398
- }
399
- }
400
- /* Three shrink-proof nav circles (Home / Edit / Share) share the footer with the 120px logo spacer.
401
- Compact size/gap steps keep them clear of the logo on narrow phones without
402
- flex shrinking circles into ovals. */
403
- @media (max-width: 400px) {
404
- [data-unified-vibe-card], [data-vibe-switch-glass] { --uvc-nav-size: 34px; --uvc-nav-gap: 4px; }
405
- }
406
- @media (max-width: 360px) {
407
- [data-unified-vibe-card], [data-vibe-switch-glass] { --uvc-nav-size: 30px; }
408
- }
409
- /* Composer input row — a FIELD, not a button (Storybook audition C2, #4578).
410
- It used to wear a hard 2px/3px offset shadow over the card's own grey, which
411
- put it one pixel of shadow away from .vibes-edit-btn and made the row read
412
- as three buttons instead of a field with two controls. So: no shadow at all
413
- (the send button is now the only raised thing in the row — that IS
414
- the cue), and a distinct field fill (--vibes-field-bg: white on the pale
415
- card, one step below near-black on the dark card) so the surface you type
416
- into is visibly a different kind of surface from the card. The 2px border and
417
- 8px corners stay — the brutalist language is the border, not the shadow.
418
- Focus swaps the border to brand blue (--vibes-blue, #3b82f6 both modes) in
419
- place of the default focus ring. The caret is the real one: it appears on
420
- focus, because a painted caret on an unfocused input lies about focus state. */
421
- .vibes-edit-field {
422
- border: 2px solid var(--vibes-card-border);
423
- border-radius: 8px;
424
- background: var(--vibes-field-bg, #ffffff);
425
- box-shadow: none;
426
- transition: border-color 0.12s ease;
427
- }
428
- .vibes-edit-field:focus-within {
429
- border-color: var(--vibes-blue);
430
- }
431
- /* The suggestion well shares the transcript’s attributed content column. Quiet
432
- suggestion borders and an opaque invitation distinguish optional actions
433
- from the writing field. Gold upgrades retain their semantic color.
434
- OptionButtons outside this well keep their own presentation. */
435
- .vibes-chip-well {
436
- background: var(--uvc-suggestion-bg, #f1f3f6);
437
- border-radius: 10px;
438
- padding: 10px;
439
- gap: 6px;
440
- }
441
- .vibes-chip-well [data-testid="option-buttons-explainer"] {
442
- opacity: 1;
443
- color: var(--uvc-helper-text, #4b5563);
444
- font-size: 12px;
445
- line-height: 1.5;
446
- }
447
- .vibes-chip-well [data-message-role="brainstorm-options"] {
448
- display: flex;
449
- flex-direction: row;
450
- flex-wrap: wrap;
451
- gap: 6px;
452
- }
453
- .vibes-chip-well [data-testid="option-buttons-explainer"] {
454
- flex: 0 0 100%;
455
- margin: 0;
456
- }
457
- /* Decorated chips keep their adjacent owner control in the same wrapping item. */
458
- .vibes-chip-well [data-message-role="brainstorm-options"] > div {
459
- min-width: 0;
460
- max-width: 100%;
461
- }
462
- .vibes-chip-well [data-message-role="brainstorm-options"] > div [data-chip-role="option"] {
463
- flex: 1 1 auto;
464
- min-width: 0;
465
- }
466
- .vibes-chip-well [data-chip-role="option"]:not([data-chip-tone="gold"]) {
467
- background-color: var(--vibes-field-bg, #ffffff);
468
- border: 1px solid var(--uvc-suggestion-border, #a6adb7);
469
- box-shadow: none;
470
- color: var(--uvc-suggestion-text, #27303d);
471
- }
472
- /* Self-sized suggestions retain a 40px minimum height and a 6px gap. */
473
- .vibes-chip-well [data-chip-role="option"] {
474
- width: auto;
475
- max-width: 100%;
476
- flex: 0 0 auto;
477
- align-self: flex-start;
478
- border-radius: 8px;
479
- font-size: 13px;
480
- line-height: 20px;
481
- padding: 3px 6px;
482
- min-height: 40px;
483
- text-align: left;
484
- overflow-wrap: anywhere;
485
- }
486
- /* The card owns this density; other SubTabStrip and BuildCard consumers keep
487
- their defaults. Tab inline sizes need a scoped override, not a shared edit. */
488
- [data-vibe-card-editor-tabs] [role="tablist"] {
489
- padding-inline: 2px;
490
- gap: 2px;
491
- }
492
- [data-vibe-card-editor-tabs] [role="tab"] {
493
- min-height: 36px;
494
- padding-inline: 3px !important;
495
- font-size: 14px !important;
496
- }
497
- /* Share and Chat/editor strips are pinned outside their content scrollers.
498
- Continue the card glass across both menus; standalone strips retain their
499
- opaque surfaces so scrolling text cannot show through them. */
500
- [data-unified-vibe-card] [data-share-panel] > [role="tablist"],
501
- [data-unified-vibe-card] [data-vibe-card-editor-tabs] [role="tablist"] {
502
- background: var(--uvc-menu-strip);
503
- border-color: var(--uvc-glass-edge);
504
- }
505
- [data-unified-vibe-card] [data-share-panel] > [role="tablist"] [role="tab"][aria-selected="true"],
506
- [data-unified-vibe-card] [data-vibe-card-editor-tabs] [role="tab"][aria-selected="true"] {
507
- background: var(--uvc-menu-tab);
508
- box-shadow: inset 0 1px 0 var(--uvc-glass-edge);
509
- }
510
- [data-unified-vibe-card] [data-share-secondary] {
511
- color: var(--uvc-share-muted);
512
- }
513
- [data-unified-vibe-card] [data-testid="build-card"] {
514
- --text-secondary: var(--uvc-build-text, #526071);
515
- }
516
- /* The field's :focus-within (blue border) is the SINGLE focus cue. The inner
517
- <input> would otherwise draw the app-wide global :focus-visible outline
518
- (2px solid var(--vibes-blue), offset 2px — see base/theme/global-styles.ts),
519
- which paints a second blue rectangle nested inside the field. Suppress it
520
- here; keyboard focus stays visible via the field border, so a11y holds. */
521
- .vibes-edit-field input:focus,
522
- .vibes-edit-field input:focus-visible,
523
- .vibes-edit-field textarea:focus,
524
- .vibes-edit-field textarea:focus-visible {
525
- outline: none;
526
- }
527
- /* Submit / stop buttons get the brutalist treatment: 2px solid border, a hard
528
- mini offset shadow, and the global press effect (translate into the shadow,
529
- which drops — same feel as the neo-brutalist <select>:active). Rounded-square
530
- footprint (borderRadius 8 from OTHER_ROW_BUTTON_STYLE, matching the field's
531
- corners) — the audition winner over the old circle. */
532
- .vibes-edit-btn {
533
- border: 2px solid var(--vibes-card-border);
534
- box-shadow: 2px 2px 0 0 var(--vibes-card-border);
535
- transition: transform 0.12s ease, box-shadow 0.12s ease;
536
- }
537
- .vibes-edit-btn:active:not(:disabled) {
538
- transform: translate(2px, 2px);
539
- box-shadow: none;
540
- }`;
541
- const CARD_VIEWPORT_HEIGHT = "80%";
542
- const CARD_FULL_VIEWPORT_HEIGHT = "94%";
543
- function ClosedBuildStatus({ visible, content }) {
544
- const [retainedContent, setRetainedContent] = useState(visible ? content : undefined);
545
- const [revealed, setRevealed] = useState(false);
546
- const [reducedMotion, setReducedMotion] = useState(false);
547
- useEffect(() => {
548
- if (typeof window.matchMedia !== "function")
549
- return;
550
- const media = window.matchMedia("(prefers-reduced-motion: reduce)");
551
- function updateMotion() {
552
- setReducedMotion(media.matches);
553
- }
554
- updateMotion();
555
- media.addEventListener("change", updateMotion);
556
- return () => media.removeEventListener("change", updateMotion);
557
- }, []);
558
- useEffect(() => {
559
- if (visible)
560
- setRetainedContent(content);
561
- }, [visible, content]);
562
- useEffect(() => {
563
- let firstFrame;
564
- let secondFrame;
565
- let exitTimer;
566
- if (visible) {
567
- if (reducedMotion)
568
- setRevealed(true);
569
- else
570
- firstFrame = requestAnimationFrame(() => {
571
- secondFrame = requestAnimationFrame(() => setRevealed(true));
572
- });
573
- }
574
- else {
575
- setRevealed(false);
576
- if (reducedMotion)
577
- setRetainedContent(undefined);
578
- else
579
- exitTimer = setTimeout(() => setRetainedContent(undefined), 240);
580
- }
581
- return () => {
582
- if (firstFrame !== undefined)
583
- cancelAnimationFrame(firstFrame);
584
- if (secondFrame !== undefined)
585
- cancelAnimationFrame(secondFrame);
586
- clearTimeout(exitTimer);
587
- };
588
- }, [visible, reducedMotion]);
589
- if (retainedContent === undefined || retainedContent === null)
590
- return undefined;
591
- return (React.createElement("div", { "data-vibe-closed-build-status": true, "data-testid": "closed-build-status", "aria-hidden": visible ? undefined : true, inert: !visible },
592
- React.createElement("div", { "data-vibe-closed-build-line": true, "data-revealed": revealed }, retainedContent)));
593
- }
594
- export function UnifiedVibeCard(props) {
595
- const { appTitle, appSlug, isTwinkling, isTeasing, open: controlledOpen, onOpenChange } = props;
596
- const editorTabDisabled = props.editorTab === undefined ? false : props.disabledEditorTabs?.includes(props.editorTab) === true;
597
- const editorTabForRender = editorTabDisabled ? "edit-chat" : props.editorTab;
598
- const retainedEditorVisible = props.editorVisible === true && editorTabDisabled === false;
599
- const retainedEditorMounted = props.retainedEditor != null && editorTabDisabled === false;
600
- const switchGlassColor = props.switchOvalColor ??
601
- (props.versionLocked || props.publishState === "draft" || props.onViewDraft !== undefined
602
- ? ovalColors.yellow
603
- : ovalColors.neutral);
604
- const [internalOpen, setInternalOpen] = useState(false);
605
- const open = controlledOpen ?? internalOpen;
606
- const setOpen = (next) => {
607
- if (controlledOpen === undefined)
608
- setInternalOpen(next);
609
- onOpenChange?.(next);
610
- };
611
- const [hidden, setHidden] = useState(false);
612
- const LONG_PRESS_HIDE_MS = 500;
613
- const HIDE_MOVE_TOLERANCE_PX = 10;
614
- const hidePressTimer = useRef(undefined);
615
- const hideOrigin = useRef(undefined);
616
- const restorePressed = useRef(false);
617
- useEffect(() => () => clearTimeout(hidePressTimer.current), []);
618
- const cancelHidePress = () => {
619
- clearTimeout(hidePressTimer.current);
620
- hidePressTimer.current = undefined;
621
- hideOrigin.current = undefined;
622
- };
623
- const onSwitchPointerDown = (e) => {
624
- if (e.pointerType === "mouse" && e.button !== 0)
625
- return;
626
- hideOrigin.current = { x: e.clientX, y: e.clientY };
627
- clearTimeout(hidePressTimer.current);
628
- hidePressTimer.current = setTimeout(() => {
629
- hidePressTimer.current = undefined;
630
- hideOrigin.current = undefined;
631
- setOpen(false);
632
- setHidden(true);
633
- }, LONG_PRESS_HIDE_MS);
634
- };
635
- const onSwitchPointerMove = (e) => {
636
- if (!hideOrigin.current)
637
- return;
638
- if (Math.hypot(e.clientX - hideOrigin.current.x, e.clientY - hideOrigin.current.y) > HIDE_MOVE_TOLERANCE_PX) {
639
- cancelHidePress();
640
- }
641
- };
642
- const pickerInteractive = props.handles !== undefined || props.onAccountSettings !== undefined;
643
- const [internalPickerOpen, setInternalPickerOpen] = useState(false);
644
- const pickerOpen = props.handlePickerOpen ?? internalPickerOpen;
645
- const setPickerOpen = (next) => {
646
- if (props.handlePickerOpen === undefined)
647
- setInternalPickerOpen(next);
648
- props.onHandlePickerOpenChange?.(next);
649
- if (next === false)
650
- setCreditsOpen(false);
651
- };
652
- const pickerWrapRef = useRef(null);
653
- const menuRef = useRef(null);
654
- useEffect(() => {
655
- if (!pickerOpen)
656
- return;
657
- const onDown = (e) => {
658
- const target = e.target;
659
- if (!pickerWrapRef.current?.contains(target) && !menuRef.current?.contains(target))
660
- setPickerOpen(false);
661
- };
662
- const onBlur = () => {
663
- if (document.activeElement?.tagName === "IFRAME")
664
- setPickerOpen(false);
665
- };
666
- document.addEventListener("pointerdown", onDown);
667
- window.addEventListener("blur", onBlur);
668
- return () => {
669
- document.removeEventListener("pointerdown", onDown);
670
- window.removeEventListener("blur", onBlur);
671
- };
672
- }, [pickerOpen]);
673
- useEffect(() => {
674
- if (open === false && pickerOpen === true)
675
- setPickerOpen(false);
676
- }, [open, pickerOpen]);
677
- const creditsEnabled = props.credits?.enabled === true;
678
- const [internalCreditsOpen, setInternalCreditsOpen] = useState(false);
679
- const creditsOpen = props.creditsMenuOpen ?? internalCreditsOpen;
680
- const setCreditsOpen = (next) => {
681
- if (props.creditsMenuOpen === undefined)
682
- setInternalCreditsOpen(next);
683
- props.onCreditsMenuOpenChange?.(next);
684
- };
685
- useEffect(() => {
686
- if (open === false && creditsOpen === true)
687
- setCreditsOpen(false);
688
- }, [open, creditsOpen]);
689
- const [draft, setDraft] = useState("");
690
- const [queued, setQueued] = useState([]);
691
- const attachmentsCtl = useAttachments();
692
- const prevAppSlugRef = useRef(props.appSlug);
693
- if (prevAppSlugRef.current !== props.appSlug) {
694
- prevAppSlugRef.current = props.appSlug;
695
- setDraft("");
696
- setQueued([]);
697
- attachmentsCtl.clear();
698
- }
699
- const restore = props.restoreComposer;
700
- const stageAttachments = attachmentsCtl.stage;
701
- const prevRestoreSeqRef = useRef(undefined);
702
- useEffect(() => {
703
- if (restore === undefined || restore.seq === prevRestoreSeqRef.current)
704
- return;
705
- prevRestoreSeqRef.current = restore.seq;
706
- setDraft((d) => (d === "" ? restore.text : d));
707
- stageAttachments(restore.attachments);
708
- }, [restore, stageAttachments]);
709
- const seed = props.seedComposer;
710
- const prevSeedSeqRef = useRef(undefined);
711
- useEffect(() => {
712
- if (seed === undefined || seed.seq === prevSeedSeqRef.current)
713
- return;
714
- prevSeedSeqRef.current = seed.seq;
715
- setDraft((d) => (d === "" ? seed.text : d));
716
- }, [seed]);
717
- const { generating, onSubmitOther } = props;
718
- const liveDuringBuild = props.liveDuringBuild;
719
- const canSendNow = props.canSendNow;
720
- const [flushAttempt, setFlushAttempt] = useState(0);
721
- useEffect(() => {
722
- const flushable = liveDuringBuild === true && canSendNow === true ? true : !generating;
723
- if (!flushable || queued.length === 0)
724
- return;
725
- const batch = queued.join("\n\n");
726
- if (onSubmitOther?.(batch) !== false) {
727
- setQueued([]);
728
- setFlushAttempt(0);
729
- return;
730
- }
731
- if (flushAttempt >= QUEUE_FLUSH_MAX_ATTEMPTS) {
732
- setQueued([]);
733
- setFlushAttempt(0);
734
- setDraft((d) => (d === "" ? batch : `${d}\n${batch}`));
735
- return;
736
- }
737
- const timer = setTimeout(() => setFlushAttempt((n) => n + 1), QUEUE_FLUSH_RETRY_MS);
738
- return () => clearTimeout(timer);
739
- }, [generating, queued, onSubmitOther, flushAttempt, liveDuringBuild, canSendNow]);
740
- const [mounted, setMounted] = useState(open);
741
- const [grown, setGrown] = useState(open);
742
- const closeDurationMs = props.closeDurationMs ?? 240;
743
- const scaleTransition = `transform ${open ? 240 : closeDurationMs}ms ${open ? "cubic-bezier(0.34, 1.56, 0.64, 1)" : "ease-in-out"}`;
744
- useEffect(() => {
745
- let t;
746
- if (open) {
747
- setMounted(true);
748
- t = setTimeout(() => setGrown(true), 10);
749
- }
750
- else {
751
- setGrown(false);
752
- t = setTimeout(() => setMounted(false), closeDurationMs);
753
- }
754
- return () => clearTimeout(t);
755
- }, [open, closeDurationMs]);
756
- const [everOpened, setEverOpened] = useState(open);
757
- useEffect(() => {
758
- if (open)
759
- setEverOpened(true);
760
- }, [open]);
761
- const retained = Boolean(props.keepMounted) && everOpened;
762
- const internalScrollRegionRef = useRef(null);
763
- const scrollRegionRef = props.scrollContainerRef ?? internalScrollRegionRef;
764
- const chipsRegionRef = useRef(null);
765
- const stickToBottomRef = useRef(true);
766
- const hasHistory = (props.historyBody != null || props.streamBody != null) && props.body == null;
767
- const streamLive = props.streamBody != null && props.streamSettled !== true;
768
- const chipBuildLive = streamLive && props.activeChip !== undefined;
769
- const visibleChips = props.activeChip === undefined || props.chips?.slice(0, 3).includes(props.activeChip) === true
770
- ? props.chips?.slice(0, 3)
771
- : [props.activeChip, ...(props.chips ?? []).filter((chip) => chip !== props.activeChip)].slice(0, 3);
772
- useEffect(() => {
773
- if (!streamLive)
774
- return;
775
- stickToBottomRef.current = true;
776
- const region = scrollRegionRef.current;
777
- if (region)
778
- region.scrollTop = region.scrollHeight;
779
- }, [streamLive]);
780
- useEffect(() => {
781
- const region = scrollRegionRef.current;
782
- const content = chipsRegionRef.current;
783
- if (!hasHistory || !region || !content)
784
- return;
785
- const pin = () => {
786
- if (stickToBottomRef.current)
787
- region.scrollTop = region.scrollHeight;
788
- };
789
- const onScroll = () => {
790
- stickToBottomRef.current = region.scrollHeight - region.scrollTop - region.clientHeight < 48;
791
- };
792
- region.addEventListener("scroll", onScroll);
793
- pin();
794
- const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(pin) : undefined;
795
- ro?.observe(content);
796
- return () => {
797
- region.removeEventListener("scroll", onScroll);
798
- ro?.disconnect();
799
- };
800
- }, [hasHistory, mounted]);
801
- const [userHeight, setUserHeight] = useState(null);
802
- const cardRef = useRef(null);
803
- const switchWrapRef = useRef(null);
804
- const float = useCardFloat(cardRef, open && !hidden && (mounted || retained), switchWrapRef);
805
- useEffect(() => {
806
- if (open === false)
807
- return;
808
- const onOutsideClick = (event) => {
809
- if (event.defaultPrevented || event.cancelBubble)
810
- return;
811
- const path = event.composedPath();
812
- if ([cardRef.current, switchWrapRef.current].some((node) => node !== null && path.includes(node)))
813
- return;
814
- if (path.some((node) => "hasAttribute" in node &&
815
- typeof node.hasAttribute === "function" &&
816
- (node.hasAttribute("data-vibe-handle-menu") || node.hasAttribute("data-vibe-version-menu"))))
817
- return;
818
- setOpen(false);
819
- };
820
- window.addEventListener("click", onOutsideClick);
821
- return () => window.removeEventListener("click", onOutsideClick);
822
- }, [open, controlledOpen, onOpenChange]);
823
- const dragStateRef = useRef(null);
824
- const [dragging, setDragging] = useState(false);
825
- const RESIZE_MIN = 220;
826
- const FLICK_VELOCITY = 0.6;
827
- const FLICK_MIN_SAMPLE_MS = 8;
828
- const FLICK_MAX_IDLE_MS = 120;
829
- function resizeMaxHeight() {
830
- const parentEl = cardRef.current?.offsetParent ?? null;
831
- const viewportH = typeof window === "undefined" ? RESIZE_MIN : window.innerHeight;
832
- const parentH = parentEl instanceof HTMLElement && parentEl.clientHeight > 0 ? parentEl.clientHeight : viewportH;
833
- return Math.max(RESIZE_MIN, parentH - 24);
834
- }
835
- function clampHeight(px) {
836
- return Math.min(Math.max(px, RESIZE_MIN), resizeMaxHeight());
837
- }
838
- const heightFractionRef = useRef(null);
839
- function setForcedHeight(px) {
840
- const clamped = clampHeight(px);
841
- const max = resizeMaxHeight();
842
- heightFractionRef.current = max > 0 ? clamped / max : null;
843
- setUserHeight(clamped);
844
- }
845
- function clearForcedHeight() {
846
- heightFractionRef.current = null;
847
- setUserHeight(null);
848
- }
849
- useEffect(() => {
850
- if (typeof window === "undefined")
851
- return;
852
- const onResize = () => {
853
- const frac = heightFractionRef.current;
854
- if (frac === null)
855
- return;
856
- setUserHeight(clampHeight(frac * resizeMaxHeight()));
857
- };
858
- window.addEventListener("resize", onResize);
859
- return () => window.removeEventListener("resize", onResize);
860
- }, []);
861
- function currentHeight() {
862
- return userHeight ?? cardRef.current?.offsetHeight ?? 0;
863
- }
864
- function onHandlePointerDown(e) {
865
- e.preventDefault();
866
- setDragging(true);
867
- const now = performance.now();
868
- dragStateRef.current = { startY: e.clientY, startHeight: currentHeight(), lastY: e.clientY, lastT: now, velocity: 0 };
869
- if (typeof e.currentTarget.setPointerCapture === "function")
870
- e.currentTarget.setPointerCapture(e.pointerId);
871
- }
872
- function onHandlePointerMove(e) {
873
- const drag = dragStateRef.current;
874
- if (drag === null)
875
- return;
876
- const now = performance.now();
877
- const dt = now - drag.lastT;
878
- if (dt >= FLICK_MIN_SAMPLE_MS) {
879
- drag.velocity = (drag.lastY - e.clientY) / dt;
880
- drag.lastY = e.clientY;
881
- drag.lastT = now;
882
- }
883
- setForcedHeight(drag.startHeight + (drag.startY - e.clientY));
884
- }
885
- function cancelHandleDrag(e) {
886
- dragStateRef.current = null;
887
- setDragging(false);
888
- if (typeof e.currentTarget.releasePointerCapture === "function")
889
- e.currentTarget.releasePointerCapture(e.pointerId);
890
- }
891
- function endHandleDrag(e) {
892
- const drag = dragStateRef.current;
893
- dragStateRef.current = null;
894
- setDragging(false);
895
- if (typeof e.currentTarget.releasePointerCapture === "function")
896
- e.currentTarget.releasePointerCapture(e.pointerId);
897
- if (drag === null)
898
- return;
899
- const settled = performance.now() - drag.lastT > FLICK_MAX_IDLE_MS;
900
- const velocity = settled ? 0 : drag.velocity;
901
- if (velocity > FLICK_VELOCITY) {
902
- setForcedHeight(resizeMaxHeight());
903
- }
904
- else if (velocity < -FLICK_VELOCITY) {
905
- clearForcedHeight();
906
- setOpen(false);
907
- }
908
- }
909
- function onHandleKeyDown(e) {
910
- if (e.key !== "ArrowUp" && e.key !== "ArrowDown")
911
- return;
912
- e.preventDefault();
913
- setForcedHeight(currentHeight() + (e.key === "ArrowUp" ? 24 : -24));
914
- }
915
- const cardHeight = userHeight ?? (props.fullHeight ? CARD_FULL_VIEWPORT_HEIGHT : CARD_VIEWPORT_HEIGHT);
916
- const agentPanelActive = props.agentPanel != null;
917
- const resizeValueMax = resizeMaxHeight();
918
- const resizeValueNow = Math.round(Math.min(Math.max(userHeight ?? cardRef.current?.offsetHeight ?? 0, RESIZE_MIN), resizeValueMax));
919
- return (React.createElement(React.Fragment, null,
920
- React.createElement("style", null, unifiedVibeCardDarkCss),
921
- React.createElement(ClosedBuildStatus, { visible: open === false && hidden === false && props.closedBuildStatus !== undefined && props.closedBuildStatus !== null, content: props.closedBuildStatus }),
922
- !hidden && (mounted || retained) && (React.createElement("div", { ref: cardRef, role: "dialog", "aria-label": "Vibe menu", "data-unified-vibe-card": true, "data-vibe-settings-active": editorTabForRender === "settings" && props.selectedNav !== "share" && !agentPanelActive, "data-vibe-agent-panel": agentPanelActive, "data-vibe-reading-mode": editorTabForRender === "style" && props.selectedNav !== "share" && !agentPanelActive ? "ink" : "ink-veil", className: props.className, "aria-hidden": mounted ? undefined : true, inert: mounted ? undefined : true, style: {
923
- position: "absolute",
924
- left: 2,
925
- right: 2,
926
- bottom: 2,
927
- zIndex: 2,
928
- borderRadius: 14,
929
- maxHeight: cardHeight,
930
- height: cardHeight,
931
- overflow: "visible",
932
- display: "flex",
933
- flexDirection: "column",
934
- background: "transparent",
935
- border: "1px solid transparent",
936
- transformOrigin: "bottom right",
937
- transform: grown ? "scale(1)" : "scale(0)",
938
- transition: dragging ? scaleTransition : `${scaleTransition}, max-height 0.24s ease, height 0.24s ease`,
939
- pointerEvents: grown ? "auto" : "none",
940
- ...(mounted ? null : { visibility: "hidden" }),
941
- } },
942
- React.createElement("div", { "data-vibe-card-content": true, style: {
943
- position: "relative",
944
- display: "flex",
945
- flexDirection: "column",
946
- minHeight: 0,
947
- flex: "1 1 auto",
948
- opacity: grown ? 1 : 0,
949
- transition: grown ? "opacity 0.18s ease 0.14s" : "opacity 0.1s ease",
950
- }, className: "text-light-primary dark:text-dark-primary" },
951
- React.createElement("div", { "data-vibe-card-body": true, style: {
952
- display: "flex",
953
- flexDirection: "column",
954
- flex: "1 1 auto",
955
- minHeight: 0,
956
- overflow: "hidden",
957
- borderRadius: 13,
958
- } },
959
- React.createElement("div", { "data-vibe-card-header": true, style: {
960
- display: "flex",
961
- alignItems: "center",
962
- gap: 8,
963
- padding: "10px 10px 10px",
964
- flexShrink: 0,
965
- position: "relative",
966
- userSelect: "none",
967
- } },
968
- 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: {
969
- position: "absolute",
970
- top: 0,
971
- left: 0,
972
- right: 0,
973
- bottom: 0,
974
- zIndex: 0,
975
- cursor: "ns-resize",
976
- touchAction: "none",
977
- } },
978
- React.createElement("span", { "aria-hidden": true, style: {
979
- position: "absolute",
980
- top: 4,
981
- left: "50%",
982
- transform: "translateX(-50%)",
983
- width: "var(--uvc-notch-width, 44px)",
984
- height: 4,
985
- borderRadius: 999,
986
- background: "var(--uvc-notch-fill)",
987
- boxShadow: "var(--uvc-notch-shadow)",
988
- opacity: "var(--uvc-notch-opacity)",
989
- } })),
990
- React.createElement("div", { style: {
991
- position: "relative",
992
- width: 30,
993
- height: 30,
994
- borderRadius: 8,
995
- flexShrink: 0,
996
- overflow: "hidden",
997
- background: "var(--vibes-cream, #fffff0)",
998
- border: "1px solid rgba(0,0,0,0.15)",
999
- pointerEvents: "none",
1000
- } },
1001
- props.appIconUrl && (React.createElement("img", { src: props.appIconUrl, alt: "", style: { width: "100%", height: "100%", objectFit: "cover", transform: "scale(2)" } })),
1002
- React.createElement(CodingIndicator, { active: props.coding === true })),
1003
- React.createElement("div", { style: { lineHeight: 1.2, minWidth: 0, flex: "1 1 auto" } },
1004
- React.createElement("strong", { "data-vibe-float-text": true, style: {
1005
- fontFamily: HOME_BRAND_FONT,
1006
- display: "block",
1007
- fontSize: 13,
1008
- lineHeight: 1.3,
1009
- overflow: "hidden",
1010
- textOverflow: "ellipsis",
1011
- whiteSpace: "nowrap",
1012
- } }, appTitle),
1013
- appSlug &&
1014
- (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 } },
1015
- props.onOpenApp === undefined ? (React.createElement("span", { className: "text-light-secondary dark:text-dark-secondary", style: {
1016
- fontSize: 12,
1017
- minWidth: 0,
1018
- overflow: "hidden",
1019
- textOverflow: "ellipsis",
1020
- whiteSpace: "nowrap",
1021
- } }, appSlug)) : (React.createElement("button", { type: "button", "data-testid": "vibe-card-slug-link", onClick: props.onOpenApp, className: "text-light-secondary dark:text-dark-secondary", style: {
1022
- font: "inherit",
1023
- fontSize: 12,
1024
- lineHeight: "20px",
1025
- overflow: "hidden",
1026
- textOverflow: "ellipsis",
1027
- whiteSpace: "nowrap",
1028
- minWidth: 0,
1029
- textDecoration: "underline",
1030
- textUnderlineOffset: 2,
1031
- background: "none",
1032
- border: "none",
1033
- padding: 0,
1034
- cursor: "pointer",
1035
- textAlign: "left",
1036
- } }, appSlug)),
1037
- props.onCopyAppUrl !== undefined && (React.createElement(CopyLinkIconButton, { onCopy: props.onCopyAppUrl, color: "currentColor" })))))),
1038
- (() => {
1039
- const badges = [
1040
- props.publishState === "old" ? React.createElement(DraftBadge, { key: "old", variant: "old" }) : null,
1041
- props.publishState === "draft" && !props.versionLocked ? React.createElement(DraftBadge, { key: "draft", variant: "draft" }) : null,
1042
- props.versionLocked ? React.createElement(LockedBadge, { key: "locked" }) : null,
1043
- !props.publishState && props.onViewDraft && !props.versionLocked ? (React.createElement(ViewDraftBadge, { key: "view-draft", onClick: props.onViewDraft })) : null,
1044
- ].filter(Boolean);
1045
- 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));
1046
- })(),
1047
- React.createElement("div", { "data-vibe-card-header-controls": "true", style: {
1048
- position: "relative",
1049
- zIndex: 1,
1050
- marginLeft: "auto",
1051
- display: "flex",
1052
- alignItems: "center",
1053
- gap: 10,
1054
- flexShrink: 0,
1055
- maxWidth: "38%",
1056
- minWidth: 0,
1057
- } }, props.hideViewerTag ? null : props.handleSlug ? (React.createElement("div", { ref: pickerWrapRef, style: { position: "relative", minWidth: 0, maxWidth: "100%" } },
1058
- 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: {
1059
- background: "var(--color-light-background-01, #eee)",
1060
- border: "1px solid var(--color-light-decorative-01, #ddd)",
1061
- color: "var(--color-light-primary, #333)",
1062
- fontSize: 13,
1063
- padding: "3px 8px 3px 4px",
1064
- minWidth: 0,
1065
- maxWidth: "100%",
1066
- } }))) : 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 },
1067
- React.createElement(ViewerTagView, { slug: "?", displayName: "Account", onTagClick: () => setPickerOpen(!pickerOpen), tagExpanded: pickerOpen, tagLabel: "Account and handles" }))) : pickerInteractive ? (React.createElement("div", { ref: pickerWrapRef },
1068
- React.createElement(ViewerTagView, { slug: "?", displayName: "Account", onTagClick: () => setPickerOpen(!pickerOpen), tagExpanded: pickerOpen, tagLabel: "Account and handles" }))) : (React.createElement(ViewerTagView, { slug: "?", anonymous: true, onSignIn: props.onSignIn })))),
1069
- (() => {
1070
- const wizardBanner = (props.publishWizardActive === true || props.publishWizardError === true) && props.onPublish;
1071
- if (wizardBanner && props.selectedNav !== "share") {
1072
- return (React.createElement("div", { style: { padding: "0 14px", flexShrink: 0 } },
1073
- 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 })));
1074
- }
1075
- if (props.autoPublishError === true && props.selectedNav !== "share") {
1076
- return (React.createElement("div", { style: { padding: "0 14px", flexShrink: 0 } },
1077
- React.createElement(PublishBanner, { variant: undefined, autoPublishError: true, onRetryAutoPublish: props.onRetryAutoPublish })));
1078
- }
1079
- if (props.statusBanner && props.selectedNav !== "share") {
1080
- return React.createElement("div", { style: { padding: "0 14px", flexShrink: 0 } }, props.statusBanner);
1081
- }
1082
- if (props.versionLocked === true && props.selectedNav !== "share") {
1083
- return (React.createElement("div", { style: { padding: "0 14px", flexShrink: 0 } },
1084
- React.createElement(PublishBanner, { variant: undefined, locked: true, onUnlock: props.onUnlock })));
1085
- }
1086
- if (props.signedOutSavePrompt && props.selectedNav !== "share") {
1087
- return (React.createElement("div", { style: { padding: "0 14px", flexShrink: 0 } },
1088
- React.createElement(SignedOutSavePrompt, { onSignIn: props.onSignIn })));
1089
- }
1090
- if (props.selectedNav === "share")
1091
- return null;
1092
- const hasPendingVersion = props.publishState === "draft" || props.publishState === "old";
1093
- if (hasPendingVersion && Boolean(props.onPublish)) {
1094
- return (React.createElement("div", { style: { padding: "0 14px", flexShrink: 0 } },
1095
- React.createElement(PublishBanner, { variant: props.publishState === "draft" || props.publishState === "old" ? props.publishState : undefined, onPublish: props.onPublish, publishing: props.publishing })));
1096
- }
1097
- return null;
1098
- })(),
1099
- editorTabForRender != null && props.selectedNav !== "share" && !agentPanelActive && (React.createElement("div", { "data-vibe-card-editor-tabs": true, style: { padding: "0 14px 6px", flexShrink: 0 } },
1100
- React.createElement(EditorTabStrip, { tab: editorTabForRender, disabledTabs: props.disabledEditorTabs, onTab: (t) => {
1101
- if (props.disabledEditorTabs?.includes(t) === true)
1102
- return;
1103
- props.onEditorTab?.(t);
1104
- }, standing: {
1105
- viewerMode: props.viewerMode,
1106
- memberReadOnly: props.memberReadOnly,
1107
- adminMode: props.adminMode,
1108
- versionLocked: props.versionLocked,
1109
- publishState: props.publishState,
1110
- publishAccess: props.publishAccess,
1111
- } }))),
1112
- (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: {
1113
- padding: "0 14px",
1114
- marginBottom: 6,
1115
- flexShrink: 0,
1116
- ...(streamLive ? { visibility: "hidden" } : null),
1117
- } }, props.composerControls)),
1118
- React.createElement("div", { ref: scrollRegionRef, "data-testid": "vibe-card-scroll", "data-vibe-reading-surface": (props.body != null && editorTabDisabled === false) ||
1119
- retainedEditorVisible ||
1120
- agentPanelActive ||
1121
- editorTabForRender === "standing"
1122
- ? true
1123
- : undefined, style: {
1124
- padding: "0 14px 4px",
1125
- overflowY: "auto",
1126
- overflowX: "hidden",
1127
- flex: "1 1 auto",
1128
- minHeight: 0,
1129
- ...((props.bodyFill && props.body) || agentPanelActive || props.body == null
1130
- ? { display: "flex", flexDirection: "column" }
1131
- : null),
1132
- } },
1133
- 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)),
1134
- 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,
1135
- React.createElement("div", { ref: chipsRegionRef, style: {
1136
- flex: "1 0 auto",
1137
- minHeight: 0,
1138
- display: "flex",
1139
- flexDirection: "column",
1140
- justifyContent: "flex-end",
1141
- } },
1142
- props.historyBody && (React.createElement("div", { "data-testid": "vibe-card-history", "data-vibe-reading-surface": true, "aria-hidden": props.streamBody ? true : undefined, inert: props.streamBody ? true : undefined, style: { marginBottom: 8, ...(props.streamBody ? { display: "none" } : null) } }, props.historyBody)),
1143
- React.createElement("div", { "data-testid": "vibe-card-stream-region" },
1144
- props.streamBody && (React.createElement("div", { "data-testid": streamLive ? undefined : "vibe-card-stream-settled", "data-vibe-reading-surface": true, style: { marginBottom: 8 } },
1145
- props.streamBody,
1146
- props.liveDuringBuild === true && React.createElement(PendingMessages, { messages: queued }))),
1147
- React.createElement("div", { "aria-hidden": streamLive && !chipBuildLive ? true : undefined, inert: streamLive && !chipBuildLive ? true : undefined, style: streamLive && !chipBuildLive ? { display: "none" } : undefined }, (props.seedOffer || (visibleChips && visibleChips.length > 0)) && (React.createElement("div", { className: "mx-auto mt-2 w-full max-w-5xl" },
1148
- React.createElement(ChatMessageRow, { role: "ai", rowAttrs: { "data-message-role": "suggestions" } },
1149
- React.createElement("div", { className: "vibes-chip-well flex flex-col", "data-testid": "vibe-chip-set" },
1150
- props.seedOffer && (React.createElement("div", { className: "flex flex-col gap-[6px]", inert: props.activeChip !== undefined }, props.seedOffer)),
1151
- visibleChips && visibleChips.length > 0 && (React.createElement(OptionButtons, { options: visibleChips, activeOption: props.activeChip, inSet: true, isFirst: props.activeChip === undefined, firstMessage: props.viewerPending === true || props.isOwner === true
1152
- ? "Suggested requests:"
1153
- : "Describe a change to remix your own copy of this app:", onSelect: (o) => {
1154
- const r = props.onSelectChip?.(o);
1155
- return r && typeof r === "object" && "then" in r ? r.then((ok) => ok ?? false) : false;
1156
- }, decorate: (option) => decorateChip(option, props) }))))))),
1157
- props.streamFooter !== undefined && React.createElement("div", { className: "mx-auto w-full max-w-5xl" }, props.streamFooter)))))),
1158
- (props.body == null || editorTabDisabled) && !agentPanelActive && (React.createElement("div", { style: { flexShrink: 0, padding: "0 14px 10px" } },
1159
- 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 })))),
1160
- agentPanelActive ? (React.createElement("div", { "aria-hidden": true, style: { display: "flex", flexShrink: 0, padding: "0 8px" } },
1161
- React.createElement("div", { style: { width: 120, height: 50, marginLeft: "auto" } }))) : (React.createElement("div", { "data-vibe-card-nav": true, style: {
1162
- display: "flex",
1163
- alignItems: "center",
1164
- gap: "var(--uvc-nav-gap, 6px)",
1165
- padding: "0 8px",
1166
- borderTop: "1px solid transparent",
1167
- flexShrink: 0,
1168
- } },
1169
- React.createElement(NavIcon, { label: "Home", color: "var(--uvc-nav-home, #2563eb)", onClick: props.onHome, onLongPress: float.toggle },
1170
- 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" },
1171
- React.createElement("path", { d: "M3 12l9-9 9 9" }),
1172
- React.createElement("path", { d: "M5 10v10a1 1 0 001 1h4v-6h4v6h4a1 1 0 001-1V10" }))),
1173
- React.createElement("span", { role: "status", className: "sr-only" }, float.status),
1174
- 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 },
1175
- 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" },
1176
- React.createElement("path", { d: "M12 20h9" }),
1177
- 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" }))),
1178
- React.createElement(NavIcon, { label: "Share", color: "var(--uvc-nav-share, #15803d)", selected: props.selectedNav === "share", onClick: props.onShare, buttonRef: props.shareButtonRef, badgeCount: props.pendingRequestCount ?? 0 },
1179
- React.createElement(PeopleIcon, null)),
1180
- React.createElement("div", { "aria-hidden": true, style: { width: 120, height: 50, flexShrink: 0, marginLeft: "auto" } })))))),
1181
- open && pickerInteractive && pickerOpen && (React.createElement("div", { ref: menuRef, "data-vibe-handle-menu": true, className: "text-light-primary dark:text-dark-primary", style: {
1182
- position: "fixed",
1183
- top: `calc(${SAFE_AREA_TOP} + 16px)`,
1184
- right: 3,
1185
- zIndex: 4,
1186
- pointerEvents: "auto",
1187
- } }, React.createElement(HandlePickerMenu, { handles: props.handles ?? [], activeSlug: props.handleSlug, busy: props.handlePickerBusy, header: creditsEnabled ? (React.createElement("div", { style: { padding: 8 } },
1188
- React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10 } },
1189
- React.createElement(CreditsRingView, { remaining: props.credits?.remaining, grantIncluded: props.credits?.grantIncluded, loading: props.credits?.loading, expanded: creditsOpen, onClick: () => setCreditsOpen(!creditsOpen) }),
1190
- React.createElement("span", null, "Credits")),
1191
- creditsOpen && (React.createElement(CreditsDetailMenu, { plan: props.credits?.plan, remaining: props.credits?.remaining, grantRemaining: props.credits?.grantRemaining, grantIncluded: props.credits?.grantIncluded, grantResetsAt: props.credits?.grantResetsAt, purchasedRemaining: props.credits?.purchasedRemaining, loading: props.credits?.loading, style: {
1192
- position: "static",
1193
- right: "auto",
1194
- top: "auto",
1195
- bottom: "auto",
1196
- width: "100%",
1197
- minWidth: 0,
1198
- boxShadow: "none",
1199
- border: "none",
1200
- padding: 0,
1201
- }, onManage: props.onManageCredits
1202
- ? () => {
1203
- props.onManageCredits?.();
1204
- setCreditsOpen(false);
1205
- }
1206
- : undefined })))) : undefined, onSignIn: props.onSignIn, onChooseHandle: props.viewerNeedsHandle ? props.onChooseHandle : undefined, onSettings: props.onAccountSettings
1207
- ? () => {
1208
- props.onAccountSettings?.();
1209
- setPickerOpen(false);
1210
- }
1211
- : undefined, onMyVibes: props.onMyVibes
1212
- ? () => {
1213
- props.onMyVibes?.();
1214
- setPickerOpen(false);
1215
- }
1216
- : undefined, onMemberships: props.onMemberships
1217
- ? () => {
1218
- props.onMemberships?.();
1219
- setPickerOpen(false);
1220
- }
1221
- : undefined, style: { position: "static", right: "auto", top: "auto", bottom: "auto" }, onSelect: (slug) => {
1222
- props.onSelectHandle?.(slug);
1223
- setPickerOpen(false);
1224
- }, onNewHandle: props.onNewHandle
1225
- ? (handle) => {
1226
- props.onNewHandle?.(handle);
1227
- setPickerOpen(false);
1228
- }
1229
- : undefined, onLogout: props.onLogout
1230
- ? () => {
1231
- props.onLogout?.();
1232
- setPickerOpen(false);
1233
- }
1234
- : undefined }))),
1235
- !hidden && (React.createElement("div", { ref: switchWrapRef, style: {
1236
- position: "absolute",
1237
- right: 2,
1238
- bottom: 7,
1239
- zIndex: 3,
1240
- pointerEvents: "auto",
1241
- display: "flex",
1242
- flexDirection: "column",
1243
- alignItems: "center",
1244
- gap: 22,
1245
- } },
1246
- !open && props.versionLocked && (React.createElement("button", { type: "button", "data-testid": "locked-chiclet", "aria-label": "Version locked \u2014 new edits aren't live. Open the vibe menu to unlock.", title: "A version is locked as the live release \u2014 open the menu to unlock", onClick: () => setOpen(true), style: {
1247
- display: "inline-flex",
1248
- alignItems: "center",
1249
- gap: 4,
1250
- borderRadius: 999,
1251
- padding: "2px 9px",
1252
- fontSize: 11,
1253
- fontWeight: 600,
1254
- lineHeight: 1.4,
1255
- color: "#92400e",
1256
- background: "#fef5e7",
1257
- border: "1px solid rgba(245,158,11,0.4)",
1258
- boxShadow: "0 1px 4px rgba(0,0,0,0.25)",
1259
- whiteSpace: "nowrap",
1260
- cursor: "pointer",
1261
- } },
1262
- React.createElement(LockGlyphSmall, null),
1263
- "Locked")),
1264
- !open && !props.versionLocked && (props.publishState === "draft" || props.onViewDraft !== undefined) && (React.createElement("button", { type: "button", "data-testid": "draft-chiclet", "aria-label": "Draft \u2014 not live yet. Open the vibe menu to update.", title: "Your latest changes aren't live yet \u2014 open the menu to update", onClick: () => setOpen(true), style: {
1265
- display: "inline-flex",
1266
- alignItems: "center",
1267
- gap: 4,
1268
- borderRadius: 999,
1269
- padding: "2px 9px",
1270
- fontSize: 11,
1271
- fontWeight: 600,
1272
- lineHeight: 1.4,
1273
- color: "#92400e",
1274
- background: "#fef5e7",
1275
- border: "1px solid rgba(245,158,11,0.4)",
1276
- boxShadow: "0 1px 4px rgba(0,0,0,0.25)",
1277
- whiteSpace: "nowrap",
1278
- cursor: "pointer",
1279
- } },
1280
- React.createElement("span", { "aria-hidden": true, style: { width: 6, height: 6, borderRadius: "50%", background: "#f59e0b" } }),
1281
- "draft")),
1282
- React.createElement("button", { type: "button", "data-vibe-switch-glass": true, "aria-label": open
1283
- ? "Close vibe menu"
1284
- : (props.pendingRequestCount ?? 0) > 0
1285
- ?
1286
- `Open vibe menu (${props.pendingRequestCount} pending access request${props.pendingRequestCount === 1 ? "" : "s"})`
1287
- : "Open vibe menu", onClick: () => setOpen(!open), onPointerDown: onSwitchPointerDown, onPointerMove: onSwitchPointerMove, onPointerUp: cancelHidePress, onPointerLeave: cancelHidePress, onPointerCancel: cancelHidePress, title: "Hold to hide the vibe controls", style: {
1288
- display: "block",
1289
- background: "none",
1290
- border: "none",
1291
- padding: 0,
1292
- cursor: "pointer",
1293
- width: 120,
1294
- height: 44,
1295
- position: "relative",
1296
- overflow: "visible",
1297
- } },
1298
- switchGlassColor !== ovalColors.neutral && (React.createElement(React.Fragment, { key: `${switchGlassColor}|${props.switchOvalStatusKey ?? ""}` },
1299
- React.createElement("span", { "data-vibe-switch-rest": true, "aria-hidden": "true" }),
1300
- 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 } }))),
1301
- React.createElement(VibesSwitch, { size: 60, style: { position: "absolute", right: 0, bottom: 0, pointerEvents: "none" }, isActive: !open, isTwinkling: !open && isTwinkling, isTeasing: !open && isTeasing }),
1302
- !open && (props.pendingRequestCount ?? 0) > 0 && (React.createElement("span", { "data-testid": "vibe-switch-pending-badge", "aria-hidden": "true", style: {
1303
- position: "absolute",
1304
- top: -4,
1305
- right: -4,
1306
- minWidth: 18,
1307
- height: 18,
1308
- padding: "0 5px",
1309
- boxSizing: "border-box",
1310
- borderRadius: 9999,
1311
- background: "var(--vibes-near-black, #1a1a1a)",
1312
- color: "#fff",
1313
- border: "1px solid #fff",
1314
- fontSize: 11,
1315
- lineHeight: "16px",
1316
- fontWeight: 700,
1317
- display: "flex",
1318
- alignItems: "center",
1319
- justifyContent: "center",
1320
- pointerEvents: "none",
1321
- } }, (props.pendingRequestCount ?? 0) > 9 ? "9+" : String(props.pendingRequestCount)))))),
1322
- hidden && (React.createElement("button", { type: "button", "data-testid": "vibe-controls-restore", "aria-label": "Show vibe controls", title: "Show vibe controls", onPointerDown: () => {
1323
- restorePressed.current = true;
1324
- }, onClick: (e) => {
1325
- const keyboard = e.detail === 0;
1326
- if (!restorePressed.current && !keyboard)
1327
- return;
1328
- restorePressed.current = false;
1329
- setHidden(false);
1330
- }, style: {
1331
- position: "absolute",
1332
- right: 16,
1333
- bottom: 24,
1334
- zIndex: 3,
1335
- pointerEvents: "auto",
1336
- width: 24,
1337
- height: 24,
1338
- borderRadius: "50%",
1339
- display: "flex",
1340
- alignItems: "center",
1341
- justifyContent: "center",
1342
- padding: 0,
1343
- cursor: "pointer",
1344
- opacity: 0.35,
1345
- background: "rgba(0,0,0,0.28)",
1346
- border: "1px solid rgba(255,255,255,0.35)",
1347
- boxShadow: "0 1px 3px rgba(0,0,0,0.25)",
1348
- } },
1349
- React.createElement("span", { "aria-hidden": true, style: { width: 5, height: 5, borderRadius: "50%", background: "rgba(255,255,255,0.9)" } })))));
1350
- }
1351
- function LockGlyphSmall() {
1352
- return (React.createElement("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", width: 11, height: 11, fill: "none", stroke: "currentColor", strokeWidth: 2.4, strokeLinecap: "round", strokeLinejoin: "round", style: { display: "block" } },
1353
- React.createElement("rect", { x: "5", y: "11", width: "14", height: "10", rx: "2" }),
1354
- React.createElement("path", { d: "M8 11V7a4 4 0 0 1 8 0v4" })));
1355
- }
1356
- function LockedBadge() {
1357
- return (React.createElement("span", { role: "status", "aria-label": "Version locked \u2014 new edits aren't live", title: "A version is locked as the live release \u2014 new edits won't go live until you unlock", style: {
1358
- flexShrink: 0,
1359
- display: "inline-flex",
1360
- alignItems: "center",
1361
- gap: 4,
1362
- borderRadius: 999,
1363
- padding: "2px 8px",
1364
- fontSize: 11,
1365
- fontWeight: 600,
1366
- lineHeight: 1.4,
1367
- color: "var(--uvc-amber-text, #92400e)",
1368
- background: "rgba(245,158,11,0.14)",
1369
- border: "1px solid rgba(245,158,11,0.45)",
1370
- whiteSpace: "nowrap",
1371
- } },
1372
- React.createElement(LockGlyphSmall, null),
1373
- "Locked"));
1374
- }
1375
- function DraftBadge({ variant }) {
1376
- const draft = variant === "draft";
1377
- return (React.createElement("span", { role: "status", "aria-label": draft ? "Draft — not live yet" : "Older version — not the published one", title: draft ? "You're viewing a draft that isn't live yet" : "You're viewing a version that isn't the published one", style: {
1378
- flexShrink: 0,
1379
- display: "inline-flex",
1380
- alignItems: "center",
1381
- gap: 4,
1382
- borderRadius: 999,
1383
- padding: "2px 8px",
1384
- fontSize: 11,
1385
- fontWeight: 600,
1386
- lineHeight: 1.4,
1387
- color: "var(--uvc-amber-text, #92400e)",
1388
- background: "rgba(245,158,11,0.14)",
1389
- border: "1px solid rgba(245,158,11,0.45)",
1390
- whiteSpace: "nowrap",
1391
- } },
1392
- React.createElement("span", { "aria-hidden": true, style: { width: 6, height: 6, borderRadius: "50%", background: "#f59e0b" } }),
1393
- draft ? "Draft" : "Older version"));
1394
- }
1395
- function ViewDraftBadge({ onClick }) {
1396
- return (React.createElement("button", { type: "button", "aria-label": "Newer draft available \u2014 view it", title: "You have a draft that isn't live yet \u2014 click to view it", onClick: onClick, style: {
1397
- flexShrink: 0,
1398
- display: "inline-flex",
1399
- alignItems: "center",
1400
- gap: 4,
1401
- borderRadius: 999,
1402
- padding: "2px 8px",
1403
- fontSize: 11,
1404
- fontWeight: 600,
1405
- lineHeight: 1.4,
1406
- color: "var(--uvc-amber-text, #92400e)",
1407
- background: "rgba(245,158,11,0.14)",
1408
- border: "1px solid rgba(245,158,11,0.45)",
1409
- whiteSpace: "nowrap",
1410
- cursor: "pointer",
1411
- } },
1412
- React.createElement("span", { "aria-hidden": true, style: { width: 6, height: 6, borderRadius: "50%", background: "#f59e0b" } }),
1413
- "Draft \u00B7 view"));
1414
- }
1415
- function decorateChip(option, props) {
1416
- const fp = props.chipFastPaths?.[option];
1417
- if (!fp)
1418
- return undefined;
1419
- if (fp.jump)
1420
- return { tone: "gold", leading: React.createElement(ChipJumpBadge, null) };
1421
- const badge = fp.shielded ? React.createElement(ChipShieldBadge, null) : undefined;
1422
- const aside = fp.canUnbless && props.onUnblessChip ? (React.createElement(FastPathToggle, { blessed: true, onClick: () => props.onUnblessChip?.(option) })) : fp.canBless && props.onBlessChip ? (React.createElement(FastPathToggle, { onClick: () => props.onBlessChip?.(option) })) : undefined;
1423
- return badge || aside ? { badge, aside } : undefined;
1424
- }
1425
- function ChipJumpBadge() {
1426
- return (React.createElement("span", { role: "img", "aria-label": "Opens another app", title: "Instant curated jump \u2014 opens another app (no sign-in)", className: "inline-flex shrink-0 text-amber-500 dark:text-amber-300" },
1427
- React.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" },
1428
- React.createElement("path", { d: "M5 12h14" }),
1429
- React.createElement("path", { d: "M13 6l6 6-6 6" }))));
1430
- }
1431
- function ChipShieldBadge() {
1432
- return (React.createElement("span", { role: "img", "aria-label": "Stays here", title: "Featured fast path \u2014 clicking stays here, on the same data", style: { display: "inline-flex", flexShrink: 0, color: "#16a34a" } },
1433
- React.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" },
1434
- React.createElement("path", { d: "M12 3l7 3v5c0 4.5-3 7.5-7 9-4-1.5-7-4.5-7-9V6l7-3z" }))));
1435
- }
1436
- function FastPathToggle({ blessed, onClick }) {
1437
- const label = blessed ? "Remove fast path (unbless)" : "Feature as fast path (bless)";
1438
- return (React.createElement("button", { type: "button", "aria-label": label, title: label, "aria-pressed": Boolean(blessed), onClick: onClick, className: "shrink-0 rounded-md border px-2 py-1.5 text-xs font-semibold transition-colors", style: {
1439
- display: "inline-flex",
1440
- alignItems: "center",
1441
- gap: 4,
1442
- cursor: "pointer",
1443
- whiteSpace: "nowrap",
1444
- ...(blessed
1445
- ? { color: "#15803d", background: "rgba(22,163,74,0.12)", borderColor: "rgba(22,163,74,0.5)" }
1446
- : {
1447
- color: "var(--color-light-primary, #333)",
1448
- background: "var(--color-light-background-01, #eee)",
1449
- borderColor: "var(--color-light-decorative-01, #ddd)",
1450
- }),
1451
- } },
1452
- React.createElement("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: blessed ? "currentColor" : "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" },
1453
- React.createElement("path", { d: "M12 3l7 3v5c0 4.5-3 7.5-7 9-4-1.5-7-4.5-7-9V6l7-3z" })),
1454
- React.createElement("span", null, blessed ? "Featured" : "Feature")));
1455
- }
1456
- function NavIcon({ label, color, selected, onClick, onLongPress, buttonRef, badgeCount, children, }) {
1457
- const press = useHomeMotionPress({ onHome: () => onClick?.(), onLongPress });
1458
- const showBadge = typeof badgeCount === "number" && badgeCount > 0;
1459
- const badgeText = showBadge ? (badgeCount > 99 ? "99+" : String(badgeCount)) : "";
1460
- return (React.createElement("button", { ref: buttonRef, type: "button", "aria-label": showBadge ? `${label} (${badgeCount})` : label, title: onLongPress === undefined ? label : "Home · Hold and release to toggle Float (Alt+Enter)", "data-vibe-nav-selected": selected === true, ...press, style: {
1461
- position: "relative",
1462
- width: "var(--uvc-nav-size, 38px)",
1463
- height: "var(--uvc-nav-size, 38px)",
1464
- flexShrink: 0,
1465
- boxSizing: "border-box",
1466
- borderRadius: "50%",
1467
- background: "var(--uvc-nav-fill)",
1468
- display: "flex",
1469
- alignItems: "center",
1470
- justifyContent: "center",
1471
- color,
1472
- fontSize: 16,
1473
- cursor: "pointer",
1474
- border: selected ? "var(--uvc-nav-selected-width) solid currentColor" : "1px solid var(--uvc-nav-border)",
1475
- boxShadow: selected ? "var(--uvc-nav-selected-shadow)" : "none",
1476
- } },
1477
- children,
1478
- showBadge ? (React.createElement("span", { "aria-hidden": "true", style: {
1479
- position: "absolute",
1480
- top: -4,
1481
- right: -4,
1482
- minWidth: 16,
1483
- height: 16,
1484
- padding: "0 4px",
1485
- boxSizing: "border-box",
1486
- borderRadius: 9999,
1487
- background: "var(--vibes-near-black, #1a1a1a)",
1488
- color: "#fff",
1489
- border: "1px solid #fff",
1490
- fontSize: 10,
1491
- lineHeight: "14px",
1492
- fontWeight: 700,
1493
- display: "flex",
1494
- alignItems: "center",
1495
- justifyContent: "center",
1496
- } }, badgeText)) : null));
1497
- }
1498
- function PendingMessages({ messages }) {
1499
- if (messages.length === 0)
1500
- return null;
1501
- return (React.createElement("div", { role: "status", "data-testid": "pending-messages", style: {
1502
- flexShrink: 0,
1503
- padding: "6px 0 2px",
1504
- display: "flex",
1505
- flexDirection: "column",
1506
- gap: 6,
1507
- fontFamily: "var(--font-serif, Georgia, serif)",
1508
- } },
1509
- messages.map((message, i) => (React.createElement("div", { key: `${i}-${message}`, style: { display: "flex", justifyContent: "flex-end" } },
1510
- React.createElement("div", { className: "text-light-primary dark:text-dark-primary", style: {
1511
- maxWidth: "85%",
1512
- borderRadius: 10,
1513
- background: "rgba(139,92,246,0.10)",
1514
- border: "1px solid rgba(139,92,246,0.28)",
1515
- padding: "6px 9px",
1516
- fontSize: 13,
1517
- lineHeight: 1.35,
1518
- overflowWrap: "anywhere",
1519
- } }, message)))),
1520
- React.createElement("div", { className: "text-light-secondary dark:text-dark-secondary", style: { fontSize: 11, textAlign: "right" } }, "sends when this build finishes")));
1521
- }
1522
- const QUEUE_FLUSH_RETRY_MS = 400;
1523
- const QUEUE_FLUSH_MAX_ATTEMPTS = 25;
1524
- const OTHER_ROW_BUTTON_STYLE = {
1525
- width: 30,
1526
- height: 30,
1527
- flexShrink: 0,
1528
- borderRadius: 8,
1529
- color: "#fff",
1530
- fontSize: 17,
1531
- fontWeight: "bold",
1532
- lineHeight: 1,
1533
- cursor: "pointer",
1534
- display: "flex",
1535
- alignItems: "center",
1536
- justifyContent: "center",
1537
- };
1538
- function AutoGrowTextarea({ value, onValueChange, ariaLabel, }) {
1539
- const ref = useRef(null);
1540
- const measure = useCallback(() => {
1541
- const ta = ref.current;
1542
- if (ta === null)
1543
- return;
1544
- ta.style.height = "auto";
1545
- ta.style.height = `${Math.min(ta.scrollHeight, 120)}px`;
1546
- }, []);
1547
- useEffect(() => {
1548
- measure();
1549
- }, [value, measure]);
1550
- useEffect(() => {
1551
- const ta = ref.current;
1552
- if (ta === null || typeof ResizeObserver === "undefined")
1553
- return;
1554
- let lastWidth = ta.clientWidth;
1555
- const ro = new ResizeObserver(() => {
1556
- if (ta.clientWidth !== lastWidth) {
1557
- lastWidth = ta.clientWidth;
1558
- measure();
1559
- }
1560
- });
1561
- ro.observe(ta);
1562
- return () => ro.disconnect();
1563
- }, [measure]);
1564
- return (React.createElement("textarea", { ref: ref, value: value, rows: 1, onChange: (e) => onValueChange(e.target.value), onKeyDown: (e) => {
1565
- if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing && e.nativeEvent.keyCode !== 229) {
1566
- e.preventDefault();
1567
- e.currentTarget.form?.requestSubmit();
1568
- }
1569
- }, "aria-label": ariaLabel, className: "w-full bg-transparent text-sm text-light-primary dark:text-dark-primary outline-none", style: { resize: "none", overflowY: "auto", maxHeight: 120, fontSize: 15, lineHeight: "22px" } }));
1570
- }
1571
- function OtherRow({ value, onValueChange, queued, onQueuedChange, onSubmitOther, generating, liveDuringBuild, canSendNow, onStop, stopDisabled, attachmentsCtl, }) {
1572
- const showStop = Boolean(generating) && value.trim() === "";
1573
- const submitDisabled = attachmentsCtl.busy || (Boolean(generating) && attachmentsCtl.readyAttachments.length > 0);
1574
- const sendNow = liveDuringBuild === true && canSendNow === true && queued.length === 0 && attachmentsCtl.readyAttachments.length === 0;
1575
- const hasUnsentContent = value.trim() !== "" || attachmentsCtl.readyAttachments.length > 0;
1576
- const composerDrop = composerFileDrop({ onFiles: attachmentsCtl.add });
1577
- return (React.createElement(React.Fragment, null,
1578
- React.createElement(AttachmentChips, { chips: attachmentsCtl.attachments, onRemove: attachmentsCtl.remove }),
1579
- React.createElement("form", { onDragOver: composerDrop.onDragOver, onDrop: composerDrop.onDrop, onSubmit: (e) => {
1580
- e.preventDefault();
1581
- const text = value.trim();
1582
- const files = attachmentsCtl.readyAttachments;
1583
- if (!text && files.length === 0)
1584
- return;
1585
- if (attachmentsCtl.busy)
1586
- return;
1587
- if (generating && !sendNow) {
1588
- if (files.length > 0)
1589
- return;
1590
- if (!text)
1591
- return;
1592
- onQueuedChange([...queued, text]);
1593
- }
1594
- else {
1595
- const sent = onSubmitOther?.(text || (files.length === 1 ? "Use the attached file." : "Use the attached files."), files.length > 0 ? files : undefined);
1596
- if (sent === false)
1597
- return;
1598
- attachmentsCtl.clear();
1599
- }
1600
- onValueChange("");
1601
- }, style: { display: "flex", flexDirection: "column", gap: 6, marginTop: 6, padding: "10px 10px 10px 12px" }, className: "vibes-edit-field" },
1602
- React.createElement("div", { "data-vibe-float-text": true, style: { position: "relative", minWidth: 0, display: "flex" } },
1603
- React.createElement(AutoGrowTextarea, { value: value, onValueChange: onValueChange, ariaLabel: "Change the app" }),
1604
- value === "" && (React.createElement("span", { "aria-hidden": true, className: "text-sm text-light-secondary dark:text-dark-secondary", style: {
1605
- position: "absolute",
1606
- left: 0,
1607
- top: 0,
1608
- bottom: 0,
1609
- display: "flex",
1610
- alignItems: "center",
1611
- pointerEvents: "none",
1612
- whiteSpace: "nowrap",
1613
- color: "var(--uvc-helper-text, #4b5563)",
1614
- } },
1615
- "Change the app with ",
1616
- React.createElement("s", { style: { margin: "0 0.25em" } }, "magic"),
1617
- " words\u2026"))),
1618
- React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } },
1619
- React.createElement(AttachmentButton, { onFiles: attachmentsCtl.add }),
1620
- React.createElement("div", { style: { flex: 1 } }),
1621
- showStop ? (React.createElement("button", { type: "button", "aria-label": "Stop generating", disabled: stopDisabled, className: "vibes-edit-btn", onClick: () => {
1622
- if (queued.length > 0) {
1623
- onValueChange(queued.join("\n"));
1624
- onQueuedChange([]);
1625
- }
1626
- onStop?.();
1627
- }, style: { ...OTHER_ROW_BUTTON_STYLE, background: "var(--vibes-near-black, #1a1a1a)" } },
1628
- React.createElement("span", { "aria-hidden": "true", style: { width: 10, height: 10, borderRadius: 2, background: "#fff", display: "block" } }))) : (React.createElement("button", { type: "submit", "aria-label": generating && !sendNow ? "Queue message" : "Submit change", disabled: submitDisabled, className: `vibes-edit-btn${hasUnsentContent && !submitDisabled ? " vibes-submit-pulse" : ""}`, title: Boolean(generating) && attachmentsCtl.readyAttachments.length > 0
1629
- ? "Files send with the next change — wait for this one to finish"
1630
- : generating && !sendNow
1631
- ? "Sends when the current change finishes"
1632
- : undefined, style: {
1633
- ...OTHER_ROW_BUTTON_STYLE,
1634
- background: "var(--uvc-submit-fill, rgba(64,89,114,.2))",
1635
- color: "var(--uvc-submit-arrow, var(--vibes-cream, #fffff0))",
1636
- boxShadow: "none",
1637
- opacity: submitDisabled ? 0.5 : 1,
1638
- cursor: submitDisabled ? "default" : "pointer",
1639
- } },
1640
- React.createElement("span", { "aria-hidden": "true" }, "\u2191"))))),
1641
- liveDuringBuild !== true && Boolean(generating) && queued.length > 0 && (React.createElement("div", { role: "status", className: "text-xs text-light-secondary dark:text-dark-secondary", style: { marginTop: 6, lineHeight: 1.4 } },
1642
- React.createElement("div", null,
1643
- queued.length === 1 ? "1 message queued" : `${queued.length} messages queued`,
1644
- " \u2014 sends when this one finishes (Stop returns it to the box)."),
1645
- React.createElement("ol", { style: {
1646
- margin: "3px 0 0",
1647
- padding: 0,
1648
- listStyle: "none",
1649
- maxHeight: 68,
1650
- overflowY: "auto",
1651
- overscrollBehavior: "contain",
1652
- } }, queued.map((message, i) => (React.createElement("li", { key: `${i}-${message}`, style: { display: "flex", gap: 5, marginTop: 1 } },
1653
- React.createElement("span", { "aria-hidden": "true", style: { opacity: 0.6 } },
1654
- i + 1,
1655
- "."),
1656
- React.createElement("span", { style: { minWidth: 0, overflowWrap: "anywhere" } }, message)))))))));
1657
- }
1658
- //# sourceMappingURL=UnifiedVibeCard.js.map