@antigenic-oss/paint 0.1.0

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 (158) hide show
  1. package/LICENSE +178 -0
  2. package/NOTICE +4 -0
  3. package/README.md +180 -0
  4. package/bin/paint.js +266 -0
  5. package/next-env.d.ts +6 -0
  6. package/next.config.ts +19 -0
  7. package/package.json +81 -0
  8. package/postcss.config.mjs +8 -0
  9. package/public/dev-editor-inspector.js +1872 -0
  10. package/src/app/api/claude/analyze/route.ts +319 -0
  11. package/src/app/api/claude/apply/route.ts +185 -0
  12. package/src/app/api/claude/pick-folder/route.ts +64 -0
  13. package/src/app/api/claude/scan/route.ts +221 -0
  14. package/src/app/api/claude/status/route.ts +55 -0
  15. package/src/app/api/project/scan/route.ts +634 -0
  16. package/src/app/api/project-scan/css-variables/route.ts +238 -0
  17. package/src/app/api/project-scan/route.ts +40 -0
  18. package/src/app/api/project-scan/tailwind-config/route.ts +172 -0
  19. package/src/app/api/proxy/[[...path]]/route.ts +2400 -0
  20. package/src/app/docs/DocsClient.tsx +322 -0
  21. package/src/app/docs/layout.tsx +7 -0
  22. package/src/app/docs/page.tsx +855 -0
  23. package/src/app/globals.css +176 -0
  24. package/src/app/layout.tsx +19 -0
  25. package/src/app/page.tsx +46 -0
  26. package/src/bridge/api-handlers.ts +885 -0
  27. package/src/bridge/proxy-handler.ts +329 -0
  28. package/src/bridge/server.ts +113 -0
  29. package/src/components/BreakpointTabs.tsx +72 -0
  30. package/src/components/ChangeSummaryModal.tsx +267 -0
  31. package/src/components/ConnectModal.tsx +994 -0
  32. package/src/components/Editor.tsx +90 -0
  33. package/src/components/PageSelector.tsx +208 -0
  34. package/src/components/PreviewFrame.tsx +299 -0
  35. package/src/components/ProjectFolderBanner.tsx +91 -0
  36. package/src/components/ResponsiveToolbar.tsx +222 -0
  37. package/src/components/TargetSelector.tsx +243 -0
  38. package/src/components/TopBar.tsx +315 -0
  39. package/src/components/common/CollapsibleSection.tsx +36 -0
  40. package/src/components/common/ColorPicker.tsx +920 -0
  41. package/src/components/common/EditablePre.tsx +136 -0
  42. package/src/components/common/ErrorBoundary.tsx +65 -0
  43. package/src/components/common/ResizablePanel.tsx +83 -0
  44. package/src/components/common/ScanAnimation.tsx +76 -0
  45. package/src/components/common/ToastContainer.tsx +97 -0
  46. package/src/components/common/UnitInput.tsx +77 -0
  47. package/src/components/common/VariableColorPicker.tsx +622 -0
  48. package/src/components/left-panel/AddElementPanel.tsx +237 -0
  49. package/src/components/left-panel/ComponentsPanel.tsx +609 -0
  50. package/src/components/left-panel/IconSidebar.tsx +99 -0
  51. package/src/components/left-panel/LayerNode.tsx +874 -0
  52. package/src/components/left-panel/LayerSearch.tsx +23 -0
  53. package/src/components/left-panel/LayersPanel.tsx +52 -0
  54. package/src/components/left-panel/LeftPanel.tsx +122 -0
  55. package/src/components/left-panel/PagesPanel.tsx +114 -0
  56. package/src/components/left-panel/icons.tsx +162 -0
  57. package/src/components/left-panel/terminal/ScanOverlay.tsx +66 -0
  58. package/src/components/left-panel/terminal/TerminalPanel.tsx +217 -0
  59. package/src/components/right-panel/ElementLogBox.tsx +248 -0
  60. package/src/components/right-panel/PanelTabs.tsx +83 -0
  61. package/src/components/right-panel/RightPanel.tsx +41 -0
  62. package/src/components/right-panel/changes/AiScanResultPanel.tsx +285 -0
  63. package/src/components/right-panel/changes/ChangeEntry.tsx +59 -0
  64. package/src/components/right-panel/changes/ChangelogActions.tsx +105 -0
  65. package/src/components/right-panel/changes/ChangesPanel.tsx +1474 -0
  66. package/src/components/right-panel/claude/ApplyConfirmModal.tsx +376 -0
  67. package/src/components/right-panel/claude/ClaudeErrorState.tsx +125 -0
  68. package/src/components/right-panel/claude/ClaudeIntegrationPanel.tsx +482 -0
  69. package/src/components/right-panel/claude/ClaudeProgressIndicator.tsx +76 -0
  70. package/src/components/right-panel/claude/DiffCard.tsx +130 -0
  71. package/src/components/right-panel/claude/DiffViewer.tsx +54 -0
  72. package/src/components/right-panel/claude/ProjectRootSelector.tsx +275 -0
  73. package/src/components/right-panel/claude/ResultsSummary.tsx +119 -0
  74. package/src/components/right-panel/claude/SetupFlow.tsx +315 -0
  75. package/src/components/right-panel/console/ConsolePanel.tsx +209 -0
  76. package/src/components/right-panel/design/AppearanceSection.tsx +703 -0
  77. package/src/components/right-panel/design/BackgroundSection.tsx +516 -0
  78. package/src/components/right-panel/design/BorderSection.tsx +161 -0
  79. package/src/components/right-panel/design/CSSRawView.tsx +412 -0
  80. package/src/components/right-panel/design/DesignCSSTabToggle.tsx +51 -0
  81. package/src/components/right-panel/design/DesignPanel.tsx +275 -0
  82. package/src/components/right-panel/design/ElementBreadcrumb.tsx +51 -0
  83. package/src/components/right-panel/design/GradientEditor.tsx +726 -0
  84. package/src/components/right-panel/design/LayoutSection.tsx +1948 -0
  85. package/src/components/right-panel/design/PositionSection.tsx +865 -0
  86. package/src/components/right-panel/design/PropertiesSection.tsx +86 -0
  87. package/src/components/right-panel/design/SVGSection.tsx +361 -0
  88. package/src/components/right-panel/design/ShadowBlurSection.tsx +227 -0
  89. package/src/components/right-panel/design/SizeSection.tsx +183 -0
  90. package/src/components/right-panel/design/TextSection.tsx +719 -0
  91. package/src/components/right-panel/design/icons.tsx +948 -0
  92. package/src/components/right-panel/design/inputs/BoxModelPreview.tsx +467 -0
  93. package/src/components/right-panel/design/inputs/ColorInput.tsx +43 -0
  94. package/src/components/right-panel/design/inputs/CompactInput.tsx +333 -0
  95. package/src/components/right-panel/design/inputs/DraggableLabel.tsx +118 -0
  96. package/src/components/right-panel/design/inputs/IconToggleGroup.tsx +54 -0
  97. package/src/components/right-panel/design/inputs/LinkedInputPair.tsx +174 -0
  98. package/src/components/right-panel/design/inputs/SectionHeader.tsx +79 -0
  99. package/src/components/right-panel/variables/VariablesPanel.tsx +388 -0
  100. package/src/hooks/useBridge.ts +95 -0
  101. package/src/hooks/useChangeTracker.ts +563 -0
  102. package/src/hooks/useClaudeAPI.ts +118 -0
  103. package/src/hooks/useDOMTree.ts +25 -0
  104. package/src/hooks/useKeyboardShortcuts.ts +76 -0
  105. package/src/hooks/usePostMessage.ts +589 -0
  106. package/src/hooks/useProjectScan.ts +204 -0
  107. package/src/hooks/useResizable.ts +20 -0
  108. package/src/hooks/useSelectedElement.ts +51 -0
  109. package/src/hooks/useTargetUrl.ts +81 -0
  110. package/src/inspector/DOMTraverser.ts +71 -0
  111. package/src/inspector/ElementSelector.ts +23 -0
  112. package/src/inspector/HoverHighlighter.ts +54 -0
  113. package/src/inspector/SelectionHighlighter.ts +27 -0
  114. package/src/inspector/StyleExtractor.ts +19 -0
  115. package/src/inspector/inspector.ts +17 -0
  116. package/src/inspector/messaging.ts +30 -0
  117. package/src/lib/apiBase.ts +15 -0
  118. package/src/lib/classifyElement.ts +430 -0
  119. package/src/lib/claude-bin.ts +197 -0
  120. package/src/lib/claude-stream.ts +158 -0
  121. package/src/lib/clientProjectScanner.ts +344 -0
  122. package/src/lib/componentMatcher.ts +156 -0
  123. package/src/lib/constants.ts +573 -0
  124. package/src/lib/cssVariableUtils.ts +409 -0
  125. package/src/lib/diffParser.ts +206 -0
  126. package/src/lib/folderPicker.ts +84 -0
  127. package/src/lib/gradientParser.ts +160 -0
  128. package/src/lib/projectScanner.ts +355 -0
  129. package/src/lib/promptBuilder.ts +402 -0
  130. package/src/lib/shadowParser.ts +124 -0
  131. package/src/lib/tailwindClassParser.ts +248 -0
  132. package/src/lib/textShadowUtils.ts +106 -0
  133. package/src/lib/utils.ts +299 -0
  134. package/src/lib/validatePath.ts +40 -0
  135. package/src/proxy.ts +92 -0
  136. package/src/server/terminal-server.ts +104 -0
  137. package/src/store/changeSlice.ts +288 -0
  138. package/src/store/claudeSlice.ts +222 -0
  139. package/src/store/componentSlice.ts +90 -0
  140. package/src/store/consoleSlice.ts +51 -0
  141. package/src/store/cssVariableSlice.ts +94 -0
  142. package/src/store/elementSlice.ts +78 -0
  143. package/src/store/index.ts +35 -0
  144. package/src/store/terminalSlice.ts +30 -0
  145. package/src/store/treeSlice.ts +69 -0
  146. package/src/store/uiSlice.ts +327 -0
  147. package/src/types/changelog.ts +49 -0
  148. package/src/types/claude.ts +131 -0
  149. package/src/types/component.ts +49 -0
  150. package/src/types/cssVariables.ts +18 -0
  151. package/src/types/element.ts +21 -0
  152. package/src/types/file-system-access.d.ts +27 -0
  153. package/src/types/gradient.ts +12 -0
  154. package/src/types/messages.ts +392 -0
  155. package/src/types/shadow.ts +8 -0
  156. package/src/types/tree.ts +9 -0
  157. package/tsconfig.json +42 -0
  158. package/tsconfig.server.json +12 -0
@@ -0,0 +1,23 @@
1
+ 'use client'
2
+
3
+ import { useEditorStore } from '@/store'
4
+
5
+ export function LayerSearch() {
6
+ const searchQuery = useEditorStore((s) => s.searchQuery)
7
+ const setSearchQuery = useEditorStore((s) => s.setSearchQuery)
8
+
9
+ return (
10
+ <div
11
+ className="px-2 py-1.5 flex-shrink-0"
12
+ style={{ borderBottom: '1px solid var(--border)' }}
13
+ >
14
+ <input
15
+ type="text"
16
+ value={searchQuery}
17
+ onChange={(e) => setSearchQuery(e.target.value)}
18
+ placeholder="Search elements..."
19
+ className="w-full text-xs py-1 px-2"
20
+ />
21
+ </div>
22
+ )
23
+ }
@@ -0,0 +1,52 @@
1
+ 'use client'
2
+
3
+ import { useMemo } from 'react'
4
+ import { useEditorStore } from '@/store'
5
+ import { LayerNode } from './LayerNode'
6
+ import { LayerSearch } from './LayerSearch'
7
+
8
+ export function LayersPanel() {
9
+ const rootNode = useEditorStore((s) => s.rootNode)
10
+ const searchQuery = useEditorStore((s) => s.searchQuery)
11
+ const styleChanges = useEditorStore((s) => s.styleChanges)
12
+
13
+ const { changedSelectors, deletedSelectors } = useMemo(() => {
14
+ const changed = new Set<string>()
15
+ const deleted = new Set<string>()
16
+ for (const change of styleChanges) {
17
+ changed.add(change.elementSelector)
18
+ if (change.property === '__element_deleted__') {
19
+ deleted.add(change.elementSelector)
20
+ }
21
+ }
22
+ return { changedSelectors: changed, deletedSelectors: deleted }
23
+ }, [styleChanges])
24
+
25
+ if (!rootNode) {
26
+ return (
27
+ <div
28
+ className="flex items-center justify-center flex-1 text-xs"
29
+ style={{ color: 'var(--text-muted)' }}
30
+ >
31
+ Loading tree...
32
+ </div>
33
+ )
34
+ }
35
+
36
+ return (
37
+ <div className="flex flex-col flex-1 overflow-hidden">
38
+ <LayerSearch />
39
+ <div className="flex-1 overflow-auto py-1">
40
+ <div style={{ minWidth: 'max-content' }}>
41
+ <LayerNode
42
+ node={rootNode}
43
+ depth={0}
44
+ searchQuery={searchQuery}
45
+ changedSelectors={changedSelectors}
46
+ deletedSelectors={deletedSelectors}
47
+ />
48
+ </div>
49
+ </div>
50
+ </div>
51
+ )
52
+ }
@@ -0,0 +1,122 @@
1
+ 'use client'
2
+
3
+ import React, { Suspense } from 'react'
4
+ import { ResizablePanel } from '@/components/common/ResizablePanel'
5
+ import { useEditorStore } from '@/store'
6
+ import { LayersPanel } from './LayersPanel'
7
+ import { PagesPanel } from './PagesPanel'
8
+ import { AddElementPanel } from './AddElementPanel'
9
+ import { IconSidebar } from './IconSidebar'
10
+ import { PANEL_DEFAULTS } from '@/lib/constants'
11
+
12
+ const ComponentsPanel = React.lazy(() => import('./ComponentsPanel'))
13
+ const TerminalPanel = React.lazy(() =>
14
+ import('./terminal/TerminalPanel').then((m) => ({
15
+ default: m.TerminalPanel,
16
+ })),
17
+ )
18
+
19
+ type LeftTab = 'layers' | 'pages' | 'components' | 'terminal' | 'add-element'
20
+
21
+ const TAB_LABELS: Record<LeftTab, string> = {
22
+ layers: 'Navigator',
23
+ pages: 'Pages',
24
+ components: 'Components',
25
+ 'add-element': 'Add Element',
26
+ terminal: 'Terminal',
27
+ }
28
+
29
+ interface LeftPanelProps {
30
+ width: number
31
+ }
32
+
33
+ export function LeftPanel({ width }: LeftPanelProps) {
34
+ const setLeftPanelWidth = useEditorStore((s) => s.setLeftPanelWidth)
35
+ const connectionStatus = useEditorStore((s) => s.connectionStatus)
36
+ const activeTab = useEditorStore((s) => s.activeLeftTab)
37
+ const leftPanelOpen = useEditorStore((s) => s.leftPanelOpen)
38
+
39
+ const isInspectorTab = activeTab !== 'terminal'
40
+ const showNotConnected = isInspectorTab && connectionStatus !== 'connected'
41
+
42
+ return (
43
+ <div className="flex h-full">
44
+ <IconSidebar />
45
+ {leftPanelOpen && (
46
+ <ResizablePanel
47
+ width={width}
48
+ minWidth={PANEL_DEFAULTS.leftMin}
49
+ maxWidth={PANEL_DEFAULTS.leftMax}
50
+ onResize={setLeftPanelWidth}
51
+ side="left"
52
+ >
53
+ <div className="flex flex-col h-full">
54
+ {/* Panel header */}
55
+ <div
56
+ className="flex items-center flex-shrink-0 h-8"
57
+ style={{
58
+ padding: '0 12px',
59
+ borderBottom: '1px solid var(--border)',
60
+ }}
61
+ >
62
+ <span
63
+ className="text-xs font-medium"
64
+ style={{ color: 'var(--text-primary)' }}
65
+ >
66
+ {TAB_LABELS[activeTab]}
67
+ </span>
68
+ </div>
69
+
70
+ {/* Tab content */}
71
+ {activeTab === 'terminal' ? (
72
+ <Suspense
73
+ fallback={
74
+ <div
75
+ style={{
76
+ color: 'var(--text-muted)',
77
+ padding: '8px',
78
+ fontSize: '11px',
79
+ }}
80
+ >
81
+ Loading terminal...
82
+ </div>
83
+ }
84
+ >
85
+ <TerminalPanel />
86
+ </Suspense>
87
+ ) : showNotConnected ? (
88
+ <div
89
+ className="flex items-center justify-center flex-1 text-xs"
90
+ style={{ color: 'var(--text-muted)' }}
91
+ >
92
+ Connect to inspect
93
+ </div>
94
+ ) : activeTab === 'layers' ? (
95
+ <LayersPanel />
96
+ ) : activeTab === 'pages' ? (
97
+ <PagesPanel />
98
+ ) : activeTab === 'add-element' ? (
99
+ <AddElementPanel />
100
+ ) : (
101
+ <Suspense
102
+ fallback={
103
+ <div
104
+ style={{
105
+ color: 'var(--text-muted)',
106
+ padding: '8px',
107
+ fontSize: '11px',
108
+ }}
109
+ >
110
+ Loading...
111
+ </div>
112
+ }
113
+ >
114
+ <ComponentsPanel />
115
+ </Suspense>
116
+ )}
117
+ </div>
118
+ </ResizablePanel>
119
+ )}
120
+ </div>
121
+ )
122
+ }
@@ -0,0 +1,114 @@
1
+ 'use client'
2
+
3
+ import { useCallback } from 'react'
4
+ import { useEditorStore } from '@/store'
5
+
6
+ export function PagesPanel() {
7
+ const pageLinks = useEditorStore((s) => s.pageLinks)
8
+ const currentPagePath = useEditorStore((s) => s.currentPagePath)
9
+ const setCurrentPagePath = useEditorStore((s) => s.setCurrentPagePath)
10
+ const targetUrl = useEditorStore((s) => s.targetUrl)
11
+
12
+ const handleNavigate = useCallback(
13
+ (path: string) => {
14
+ if (!targetUrl) return
15
+ setCurrentPagePath(path)
16
+ },
17
+ [targetUrl, setCurrentPagePath],
18
+ )
19
+
20
+ // Build page list: always include "/" plus discovered links
21
+ const seen = new Set<string>()
22
+ const pages: Array<{ href: string; text: string }> = []
23
+
24
+ pages.push({ href: '/', text: 'Home' })
25
+ seen.add('/')
26
+
27
+ for (const link of pageLinks) {
28
+ if (!seen.has(link.href)) {
29
+ seen.add(link.href)
30
+ pages.push(link)
31
+ }
32
+ }
33
+
34
+ return (
35
+ <div className="flex flex-col flex-1 overflow-hidden">
36
+ {/* Header count */}
37
+ <div
38
+ className="px-3 py-1.5 text-[10px] font-medium uppercase tracking-wider flex-shrink-0"
39
+ style={{
40
+ color: 'var(--text-muted)',
41
+ borderBottom: '1px solid var(--border)',
42
+ }}
43
+ >
44
+ {pages.length} page{pages.length !== 1 ? 's' : ''} found
45
+ </div>
46
+
47
+ {/* Page list */}
48
+ <div className="flex-1 overflow-y-auto">
49
+ {pages.map((page) => {
50
+ const isActive = page.href === currentPagePath
51
+ return (
52
+ <button
53
+ key={page.href}
54
+ onClick={() => handleNavigate(page.href)}
55
+ className="w-full text-left px-3 py-2 text-xs transition-colors flex items-center gap-2"
56
+ style={{
57
+ color: isActive ? 'var(--accent)' : 'var(--text-primary)',
58
+ background: isActive
59
+ ? 'rgba(74, 158, 255, 0.08)'
60
+ : 'transparent',
61
+ borderBottom: '1px solid var(--border)',
62
+ }}
63
+ title={page.href}
64
+ >
65
+ {/* Page icon */}
66
+ <svg
67
+ width="12"
68
+ height="12"
69
+ viewBox="0 0 16 16"
70
+ fill="none"
71
+ stroke={isActive ? 'var(--accent)' : 'var(--text-muted)'}
72
+ strokeWidth="1.5"
73
+ strokeLinecap="round"
74
+ strokeLinejoin="round"
75
+ className="flex-shrink-0"
76
+ >
77
+ <rect x="2" y="2" width="12" height="12" rx="1.5" />
78
+ <path d="M2 5.5h12" />
79
+ </svg>
80
+ <div className="truncate flex-1">
81
+ <div className="truncate font-medium">
82
+ {page.text || page.href}
83
+ </div>
84
+ {page.text && page.href !== page.text && (
85
+ <div
86
+ className="truncate"
87
+ style={{ color: 'var(--text-muted)', fontSize: '10px' }}
88
+ >
89
+ {page.href}
90
+ </div>
91
+ )}
92
+ </div>
93
+ {isActive && (
94
+ <div
95
+ className="w-1.5 h-1.5 rounded-full flex-shrink-0"
96
+ style={{ background: 'var(--accent)' }}
97
+ />
98
+ )}
99
+ </button>
100
+ )
101
+ })}
102
+
103
+ {pages.length <= 1 && (
104
+ <div
105
+ className="px-3 py-4 text-[10px] text-center"
106
+ style={{ color: 'var(--text-muted)' }}
107
+ >
108
+ No additional pages found
109
+ </div>
110
+ )}
111
+ </div>
112
+ </div>
113
+ )
114
+ }
@@ -0,0 +1,162 @@
1
+ import React from 'react'
2
+
3
+ export function LayersIcon(props: React.SVGProps<SVGSVGElement>) {
4
+ return (
5
+ <svg
6
+ width={16}
7
+ height={16}
8
+ viewBox="0 0 16 16"
9
+ fill="none"
10
+ xmlns="http://www.w3.org/2000/svg"
11
+ {...props}
12
+ >
13
+ <path
14
+ d="M8 2L2 5.5l6 3.5 6-3.5L8 2Z"
15
+ stroke="currentColor"
16
+ strokeWidth={1.3}
17
+ strokeLinejoin="round"
18
+ />
19
+ <path
20
+ d="M2 8l6 3.5L14 8"
21
+ stroke="currentColor"
22
+ strokeWidth={1.3}
23
+ strokeLinecap="round"
24
+ strokeLinejoin="round"
25
+ />
26
+ <path
27
+ d="M2 10.5L8 14l6-3.5"
28
+ stroke="currentColor"
29
+ strokeWidth={1.3}
30
+ strokeLinecap="round"
31
+ strokeLinejoin="round"
32
+ />
33
+ </svg>
34
+ )
35
+ }
36
+
37
+ export function PagesIcon(props: React.SVGProps<SVGSVGElement>) {
38
+ return (
39
+ <svg
40
+ width={16}
41
+ height={16}
42
+ viewBox="0 0 16 16"
43
+ fill="none"
44
+ xmlns="http://www.w3.org/2000/svg"
45
+ {...props}
46
+ >
47
+ <rect
48
+ x={4.5}
49
+ y={1.5}
50
+ width={9}
51
+ height={11}
52
+ rx={1}
53
+ stroke="currentColor"
54
+ strokeWidth={1.3}
55
+ />
56
+ <path
57
+ d="M2.5 4.5v9a1 1 0 0 0 1 1h7"
58
+ stroke="currentColor"
59
+ strokeWidth={1.3}
60
+ strokeLinecap="round"
61
+ strokeLinejoin="round"
62
+ />
63
+ </svg>
64
+ )
65
+ }
66
+
67
+ export function ComponentsIcon(props: React.SVGProps<SVGSVGElement>) {
68
+ return (
69
+ <svg
70
+ width={16}
71
+ height={16}
72
+ viewBox="0 0 16 16"
73
+ fill="none"
74
+ xmlns="http://www.w3.org/2000/svg"
75
+ {...props}
76
+ >
77
+ <path
78
+ d="M8 1.5L14 5v6l-6 3.5L2 11V5l6-3.5Z"
79
+ stroke="currentColor"
80
+ strokeWidth={1.3}
81
+ strokeLinejoin="round"
82
+ />
83
+ <path
84
+ d="M8 8.5V14.5"
85
+ stroke="currentColor"
86
+ strokeWidth={1.3}
87
+ strokeLinecap="round"
88
+ />
89
+ <path
90
+ d="M2 5l6 3.5L14 5"
91
+ stroke="currentColor"
92
+ strokeWidth={1.3}
93
+ strokeLinejoin="round"
94
+ />
95
+ </svg>
96
+ )
97
+ }
98
+
99
+ export function AddElementIcon(props: React.SVGProps<SVGSVGElement>) {
100
+ return (
101
+ <svg
102
+ width={16}
103
+ height={16}
104
+ viewBox="0 0 16 16"
105
+ fill="none"
106
+ xmlns="http://www.w3.org/2000/svg"
107
+ {...props}
108
+ >
109
+ <rect
110
+ x={2.5}
111
+ y={2.5}
112
+ width={11}
113
+ height={11}
114
+ rx={1.5}
115
+ stroke="currentColor"
116
+ strokeWidth={1.3}
117
+ />
118
+ <path
119
+ d="M8 5.5v5M5.5 8h5"
120
+ stroke="currentColor"
121
+ strokeWidth={1.3}
122
+ strokeLinecap="round"
123
+ />
124
+ </svg>
125
+ )
126
+ }
127
+
128
+ export function TerminalIcon(props: React.SVGProps<SVGSVGElement>) {
129
+ return (
130
+ <svg
131
+ width={16}
132
+ height={16}
133
+ viewBox="0 0 16 16"
134
+ fill="none"
135
+ xmlns="http://www.w3.org/2000/svg"
136
+ {...props}
137
+ >
138
+ <rect
139
+ x={1.5}
140
+ y={2.5}
141
+ width={13}
142
+ height={11}
143
+ rx={1.5}
144
+ stroke="currentColor"
145
+ strokeWidth={1.3}
146
+ />
147
+ <path
148
+ d="M4.5 6l2.5 2-2.5 2"
149
+ stroke="currentColor"
150
+ strokeWidth={1.3}
151
+ strokeLinecap="round"
152
+ strokeLinejoin="round"
153
+ />
154
+ <path
155
+ d="M8.5 10.5h3"
156
+ stroke="currentColor"
157
+ strokeWidth={1.3}
158
+ strokeLinecap="round"
159
+ />
160
+ </svg>
161
+ )
162
+ }
@@ -0,0 +1,66 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useState } from 'react'
4
+ import { useEditorStore } from '@/store'
5
+ import { ScanAnimation } from '@/components/common/ScanAnimation'
6
+
7
+ export function ScanOverlay() {
8
+ const aiScanStatus = useEditorStore((s) => s.aiScanStatus)
9
+ const [visible, setVisible] = useState(false)
10
+ const [fadeOut, setFadeOut] = useState(false)
11
+
12
+ useEffect(() => {
13
+ if (aiScanStatus === 'scanning') {
14
+ setFadeOut(false)
15
+ setVisible(true)
16
+ } else if (visible) {
17
+ // Fade out when scan finishes
18
+ setFadeOut(true)
19
+ const timer = setTimeout(() => setVisible(false), 600)
20
+ return () => clearTimeout(timer)
21
+ }
22
+ }, [aiScanStatus, visible])
23
+
24
+ if (!visible) return null
25
+
26
+ return (
27
+ <div
28
+ className="absolute inset-0 z-10 flex flex-col items-center justify-center overflow-hidden pointer-events-none"
29
+ style={{
30
+ background: 'rgba(30, 30, 30, 0.75)',
31
+ transition: 'opacity 0.4s ease',
32
+ opacity: fadeOut ? 0 : 1,
33
+ }}
34
+ >
35
+ {/* Sweep line */}
36
+ <div
37
+ style={{
38
+ position: 'absolute',
39
+ left: 0,
40
+ right: 0,
41
+ height: '2px',
42
+ background:
43
+ 'linear-gradient(90deg, transparent 0%, var(--accent) 30%, var(--accent) 70%, transparent 100%)',
44
+ boxShadow: '0 0 12px 3px rgba(74, 158, 255, 0.4)',
45
+ animation: 'scan-sweep 2.4s ease-in-out infinite',
46
+ }}
47
+ />
48
+
49
+ {/* Second sweep line (offset) */}
50
+ <div
51
+ style={{
52
+ position: 'absolute',
53
+ left: 0,
54
+ right: 0,
55
+ height: '1px',
56
+ background:
57
+ 'linear-gradient(90deg, transparent 0%, rgba(74, 158, 255, 0.4) 40%, rgba(74, 158, 255, 0.4) 60%, transparent 100%)',
58
+ animation: 'scan-sweep 2.4s ease-in-out infinite',
59
+ animationDelay: '1.2s',
60
+ }}
61
+ />
62
+
63
+ <ScanAnimation active={!fadeOut} label="SCANNING" />
64
+ </div>
65
+ )
66
+ }