@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,855 @@
1
+ import type { Metadata } from 'next'
2
+ import Link from 'next/link'
3
+ import {
4
+ CodeBlock,
5
+ CopyButton,
6
+ FaqAccordion,
7
+ FaqSection,
8
+ FrameworkAccordion,
9
+ FrameworkSection,
10
+ Sidebar,
11
+ } from './DocsClient'
12
+
13
+ export const metadata: Metadata = {
14
+ title: 'pAInt — Setup Guide',
15
+ description:
16
+ 'Framework-specific setup instructions for connecting pAInt to your localhost project.',
17
+ }
18
+
19
+ const SCRIPT_TAG =
20
+ '<script src="https://dev-editor-flow.vercel.app/dev-editor-inspector.js"></script>'
21
+
22
+ export default function DocsPage() {
23
+ return (
24
+ <div
25
+ className="min-h-screen"
26
+ style={{ background: 'var(--bg-primary)', color: 'var(--text-primary)' }}
27
+ >
28
+ <div className="mx-auto px-6 py-12" style={{ maxWidth: '1024px' }}>
29
+ {/* Header */}
30
+ <header className="mb-12">
31
+ <Link
32
+ href="/"
33
+ className="inline-flex items-center gap-1 text-sm mb-6 no-underline transition-colors"
34
+ style={{ color: 'var(--accent)' }}
35
+ >
36
+ &larr; Back to Editor
37
+ </Link>
38
+ <h1
39
+ className="text-3xl font-semibold mb-3"
40
+ style={{ color: 'var(--text-primary)' }}
41
+ >
42
+ pAInt — Setup Guide
43
+ </h1>
44
+ <p
45
+ style={{
46
+ color: 'var(--text-secondary)',
47
+ fontSize: '1rem',
48
+ lineHeight: 1.6,
49
+ }}
50
+ >
51
+ Connect the visual editor to your localhost project in seconds.
52
+ </p>
53
+ </header>
54
+
55
+ <div className="flex gap-10">
56
+ {/* Sidebar */}
57
+ <Sidebar />
58
+
59
+ {/* Main content */}
60
+ <div className="flex-1 min-w-0">
61
+ {/* How It Works */}
62
+ <Section id="how-it-works" title="How It Works">
63
+ <p style={bodyText}>
64
+ pAInt is a visual design tool that sits alongside your localhost
65
+ dev server. It gives you a visual-editor-style interface for
66
+ inspecting elements, editing CSS, and repositioning components —
67
+ all without touching your source files directly.
68
+ </p>
69
+ <p style={{ ...bodyText, marginTop: '0.75rem' }}>
70
+ When you click{' '}
71
+ <strong style={{ color: 'var(--text-primary)' }}>
72
+ Connect
73
+ </strong>
74
+ , the editor loads your page through a{' '}
75
+ <strong style={{ color: 'var(--text-primary)' }}>
76
+ built-in reverse proxy
77
+ </strong>
78
+ . The proxy fetches your HTML, strips client-side scripts (to
79
+ prevent routing conflicts), and injects a lightweight inspector
80
+ script. This inspector communicates with the editor via{' '}
81
+ <code style={inlineCodeStyle}>postMessage</code> — reporting
82
+ element metadata when you hover or click, and applying style
83
+ previews in real time.
84
+ </p>
85
+ <p style={{ ...bodyText, marginTop: '0.75rem' }}>
86
+ Every change you make is tracked as a changelog entry with
87
+ original and new values. When you&apos;re done, export the
88
+ changelog and paste it into Claude Code (or use the built-in
89
+ Send to Claude Code feature) to have the changes applied to your
90
+ actual source files.
91
+ </p>
92
+ <p style={{ ...bodyText, marginTop: '0.75rem' }}>
93
+ If automatic injection fails (e.g., non-standard HTML
94
+ responses), a banner will prompt you to add the script tag
95
+ manually. The framework guides below show exactly where to place
96
+ it.
97
+ </p>
98
+ </Section>
99
+
100
+ {/* Use Cases */}
101
+ <Section id="use-cases" title="Use Cases">
102
+ <div className="flex flex-col gap-4">
103
+ <UseCaseItem
104
+ title="Visual Style Tweaking"
105
+ description="Select any element on your page, then adjust colors, spacing, typography, borders, and layout from the right panel. Changes preview instantly in the iframe."
106
+ />
107
+ <UseCaseItem
108
+ title="Responsive Design Testing"
109
+ description="Switch between Mobile (375px), Tablet (768px), and Desktop (1280px) breakpoints in the top bar. Make per-breakpoint style adjustments and export them all at once."
110
+ />
111
+ <UseCaseItem
112
+ title="Layout Debugging"
113
+ description="Use the left panel DOM tree (Layers) to navigate the page structure. Click any node to highlight it in the preview. Inspect flexbox and grid properties, then adjust layout in the right panel."
114
+ />
115
+ <UseCaseItem
116
+ title="Drag-and-Drop Repositioning"
117
+ description="Toggle Free Position mode to drag elements to new positions, or Reorder mode to rearrange siblings within flex and grid containers."
118
+ />
119
+ <UseCaseItem
120
+ title="Change Tracking & Export"
121
+ description="Every style edit is tracked with original and new values. Review all changes in the Changes tab, undo individual edits, or export a structured changelog."
122
+ />
123
+ <UseCaseItem
124
+ title="Claude Code Integration"
125
+ description="Click Copy Changelog to get a formatted log you can paste into Claude Code, which reads it and applies the CSS changes to your actual source files. Or use Send to Claude Code for direct CLI integration."
126
+ />
127
+ <UseCaseItem
128
+ title="Multi-Page Editing"
129
+ description="Navigate between pages using the PageSelector dropdown without leaving the editor. Changes are persisted per-page and included in a combined changelog export."
130
+ />
131
+ </div>
132
+ </Section>
133
+
134
+ {/* Quick Start */}
135
+ <Section id="quick-start" title="Quick Start">
136
+ <ol className="list-none p-0 m-0 flex flex-col gap-5">
137
+ <Step number={1}>
138
+ <strong
139
+ style={{ color: 'var(--text-primary)', fontSize: '1rem' }}
140
+ >
141
+ Open pAInt
142
+ </strong>
143
+ <p style={{ ...bodyText, marginTop: '0.25rem' }}>
144
+ Go to{' '}
145
+ <a
146
+ href="https://dev-editor-flow.vercel.app/"
147
+ target="_blank"
148
+ rel="noopener noreferrer"
149
+ style={{ color: 'var(--accent)' }}
150
+ >
151
+ dev-editor-flow.vercel.app
152
+ </a>
153
+ </p>
154
+ <p style={{ ...mutedText, marginTop: '0.25rem' }}>
155
+ Running locally? Start with{' '}
156
+ <code style={inlineCodeStyle}>bun dev</code> (defaults to{' '}
157
+ <code style={inlineCodeStyle}>http://localhost:4000</code>).
158
+ </p>
159
+ </Step>
160
+ <Step number={2}>
161
+ <strong
162
+ style={{ color: 'var(--text-primary)', fontSize: '1rem' }}
163
+ >
164
+ Start your target project
165
+ </strong>
166
+ <p style={{ ...bodyText, marginTop: '0.25rem' }}>
167
+ Run your project&apos;s dev server as usual (e.g.,{' '}
168
+ <code style={inlineCodeStyle}>npm run dev</code> on port
169
+ 3000).
170
+ </p>
171
+ </Step>
172
+ <Step number={3}>
173
+ <strong
174
+ style={{ color: 'var(--text-primary)', fontSize: '1rem' }}
175
+ >
176
+ Connect
177
+ </strong>
178
+ <p style={{ ...bodyText, marginTop: '0.25rem' }}>
179
+ Open pAInt in your browser, select your project&apos;s port
180
+ from the dropdown, and click{' '}
181
+ <strong style={{ color: 'var(--text-primary)' }}>
182
+ Connect
183
+ </strong>
184
+ . The inspector activates automatically.
185
+ </p>
186
+ </Step>
187
+ </ol>
188
+ </Section>
189
+
190
+ {/* Framework Guides */}
191
+ <Section id="framework-guides" title="Framework Guides">
192
+ <p style={{ ...bodyText, marginBottom: '1rem' }}>
193
+ If the automatic connection doesn&apos;t detect the inspector
194
+ within 5 seconds, add the script tag manually to your project.
195
+ Select your framework below for the exact file and placement.
196
+ </p>
197
+ <div
198
+ className="flex items-center gap-2 px-4 py-3 rounded-md mb-4 text-sm"
199
+ style={{
200
+ background: 'var(--accent-bg)',
201
+ border: '1px solid var(--accent)',
202
+ color: 'var(--text-secondary)',
203
+ }}
204
+ >
205
+ <span style={{ color: 'var(--accent)' }}>Note:</span>
206
+ <span>
207
+ The snippets below use the hosted URL. If running pAInt
208
+ locally, replace{' '}
209
+ <code style={{ ...inlineCodeStyle, fontSize: '0.8em' }}>
210
+ https://dev-editor-flow.vercel.app
211
+ </code>{' '}
212
+ with{' '}
213
+ <code style={{ ...inlineCodeStyle, fontSize: '0.8em' }}>
214
+ http://localhost:4000
215
+ </code>{' '}
216
+ (or your custom port).
217
+ </span>
218
+ </div>
219
+ <FrameworkAccordion>
220
+ {/* Next.js */}
221
+ <FrameworkSection id="nextjs" title="Next.js" icon="&#9650;">
222
+ <div>
223
+ <h4
224
+ className="text-base font-medium mb-1"
225
+ style={{ color: 'var(--text-primary)' }}
226
+ >
227
+ App Router
228
+ </h4>
229
+ <p style={{ ...mutedText, marginBottom: '0.5rem' }}>
230
+ Add to <code style={inlineCodeStyle}>app/layout.tsx</code>
231
+ </p>
232
+ <CodeBlock
233
+ language="tsx"
234
+ copyText={SCRIPT_TAG}
235
+ code={`// app/layout.tsx
236
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
237
+ return (
238
+ <html lang="en">
239
+ <body>
240
+ {children}
241
+ <script src="https://dev-editor-flow.vercel.app/dev-editor-inspector.js"></script>
242
+ </body>
243
+ </html>
244
+ );
245
+ }`}
246
+ />
247
+ </div>
248
+ <div>
249
+ <h4
250
+ className="text-base font-medium mb-1"
251
+ style={{ color: 'var(--text-primary)' }}
252
+ >
253
+ Pages Router
254
+ </h4>
255
+ <p style={{ ...mutedText, marginBottom: '0.5rem' }}>
256
+ Add to{' '}
257
+ <code style={inlineCodeStyle}>pages/_document.tsx</code>
258
+ </p>
259
+ <CodeBlock
260
+ language="tsx"
261
+ copyText={SCRIPT_TAG}
262
+ code={`// pages/_document.tsx
263
+ import { Html, Head, Main, NextScript } from 'next/document';
264
+
265
+ export default function Document() {
266
+ return (
267
+ <Html>
268
+ <Head />
269
+ <body>
270
+ <Main />
271
+ <NextScript />
272
+ <script src="https://dev-editor-flow.vercel.app/dev-editor-inspector.js"></script>
273
+ </body>
274
+ </Html>
275
+ );
276
+ }`}
277
+ />
278
+ </div>
279
+ </FrameworkSection>
280
+
281
+ {/* Vite + React */}
282
+ <FrameworkSection
283
+ id="vite-react"
284
+ title="Vite + React"
285
+ icon="&#9889;"
286
+ >
287
+ <p style={{ ...mutedText, marginBottom: '0.5rem' }}>
288
+ Add to <code style={inlineCodeStyle}>index.html</code>{' '}
289
+ (project root)
290
+ </p>
291
+ <CodeBlock
292
+ language="html"
293
+ copyText={SCRIPT_TAG}
294
+ code={`<!-- index.html -->
295
+ <!DOCTYPE html>
296
+ <html lang="en">
297
+ <head>
298
+ <meta charset="UTF-8" />
299
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
300
+ <title>My App</title>
301
+ </head>
302
+ <body>
303
+ <div id="root"></div>
304
+ <script type="module" src="/src/main.tsx"></script>
305
+ <script src="https://dev-editor-flow.vercel.app/dev-editor-inspector.js"></script>
306
+ </body>
307
+ </html>`}
308
+ />
309
+ </FrameworkSection>
310
+
311
+ {/* Create React App */}
312
+ <FrameworkSection
313
+ id="create-react-app"
314
+ title="Create React App"
315
+ icon="&#9883;"
316
+ >
317
+ <p style={{ ...mutedText, marginBottom: '0.5rem' }}>
318
+ Add to{' '}
319
+ <code style={inlineCodeStyle}>public/index.html</code>
320
+ </p>
321
+ <CodeBlock
322
+ language="html"
323
+ copyText={SCRIPT_TAG}
324
+ code={`<!-- public/index.html -->
325
+ <!DOCTYPE html>
326
+ <html lang="en">
327
+ <head>
328
+ <meta charset="utf-8" />
329
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
330
+ <title>My App</title>
331
+ </head>
332
+ <body>
333
+ <div id="root"></div>
334
+ <script src="https://dev-editor-flow.vercel.app/dev-editor-inspector.js"></script>
335
+ </body>
336
+ </html>`}
337
+ />
338
+ </FrameworkSection>
339
+
340
+ {/* Plain HTML */}
341
+ <FrameworkSection
342
+ id="plain-html"
343
+ title="Plain HTML"
344
+ icon="&#128196;"
345
+ >
346
+ <p style={{ ...mutedText, marginBottom: '0.5rem' }}>
347
+ Add before{' '}
348
+ <code style={inlineCodeStyle}>&lt;/body&gt;</code> in any{' '}
349
+ <code style={inlineCodeStyle}>.html</code> file
350
+ </p>
351
+ <CodeBlock
352
+ language="html"
353
+ copyText={SCRIPT_TAG}
354
+ code={`<!DOCTYPE html>
355
+ <html>
356
+ <head>
357
+ <title>My Page</title>
358
+ </head>
359
+ <body>
360
+ <h1>Hello</h1>
361
+ <script src="https://dev-editor-flow.vercel.app/dev-editor-inspector.js"></script>
362
+ </body>
363
+ </html>`}
364
+ />
365
+ </FrameworkSection>
366
+
367
+ {/* React Native / Expo Web */}
368
+ <FrameworkSection
369
+ id="react-native-expo"
370
+ title="React Native / Expo Web"
371
+ icon="&#128241;"
372
+ >
373
+ <div>
374
+ <h4
375
+ className="text-base font-medium mb-1"
376
+ style={{ color: 'var(--text-primary)' }}
377
+ >
378
+ Expo Router
379
+ </h4>
380
+ <p style={{ ...mutedText, marginBottom: '0.5rem' }}>
381
+ Add to{' '}
382
+ <code style={inlineCodeStyle}>app/_layout.tsx</code> using{' '}
383
+ a <code style={inlineCodeStyle}>&lt;Script&gt;</code>{' '}
384
+ component or the web{' '}
385
+ <code style={inlineCodeStyle}>index.html</code>
386
+ </p>
387
+ <CodeBlock
388
+ language="tsx"
389
+ copyText={SCRIPT_TAG}
390
+ code={`// app/_layout.tsx
391
+ import { Slot } from 'expo-router';
392
+ import { Platform } from 'react-native';
393
+ import { useEffect } from 'react';
394
+
395
+ export default function RootLayout() {
396
+ useEffect(() => {
397
+ if (Platform.OS === 'web') {
398
+ const script = document.createElement('script');
399
+ script.src = 'https://dev-editor-flow.vercel.app/dev-editor-inspector.js';
400
+ document.body.appendChild(script);
401
+ }
402
+ }, []);
403
+
404
+ return <Slot />;
405
+ }`}
406
+ />
407
+ </div>
408
+ <div>
409
+ <h4
410
+ className="text-base font-medium mb-1"
411
+ style={{ color: 'var(--text-primary)' }}
412
+ >
413
+ Custom <code style={inlineCodeStyle}>web/index.html</code>
414
+ </h4>
415
+ <p style={{ ...mutedText, marginBottom: '0.5rem' }}>
416
+ If your Expo project has a custom web entry point
417
+ </p>
418
+ <CodeBlock
419
+ language="html"
420
+ copyText={SCRIPT_TAG}
421
+ code={`<!-- web/index.html -->
422
+ <!DOCTYPE html>
423
+ <html>
424
+ <head>
425
+ <meta charset="utf-8" />
426
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
427
+ </head>
428
+ <body>
429
+ <div id="root"></div>
430
+ <script src="https://dev-editor-flow.vercel.app/dev-editor-inspector.js"></script>
431
+ </body>
432
+ </html>`}
433
+ />
434
+ </div>
435
+ </FrameworkSection>
436
+
437
+ {/* Vue / Nuxt */}
438
+ <FrameworkSection
439
+ id="vue-nuxt"
440
+ title="Vue / Nuxt"
441
+ icon="&#9899;"
442
+ >
443
+ <div>
444
+ <h4
445
+ className="text-base font-medium mb-1"
446
+ style={{ color: 'var(--text-primary)' }}
447
+ >
448
+ Vue (Vite)
449
+ </h4>
450
+ <p style={{ ...mutedText, marginBottom: '0.5rem' }}>
451
+ Add to <code style={inlineCodeStyle}>index.html</code>{' '}
452
+ (project root)
453
+ </p>
454
+ <CodeBlock
455
+ language="html"
456
+ copyText={SCRIPT_TAG}
457
+ code={`<!-- index.html -->
458
+ <!DOCTYPE html>
459
+ <html lang="en">
460
+ <head>
461
+ <meta charset="UTF-8" />
462
+ <title>My Vue App</title>
463
+ </head>
464
+ <body>
465
+ <div id="app"></div>
466
+ <script type="module" src="/src/main.ts"></script>
467
+ <script src="https://dev-editor-flow.vercel.app/dev-editor-inspector.js"></script>
468
+ </body>
469
+ </html>`}
470
+ />
471
+ </div>
472
+ <div>
473
+ <h4
474
+ className="text-base font-medium mb-1"
475
+ style={{ color: 'var(--text-primary)' }}
476
+ >
477
+ Nuxt 3
478
+ </h4>
479
+ <p style={{ ...mutedText, marginBottom: '0.5rem' }}>
480
+ Add via{' '}
481
+ <code style={inlineCodeStyle}>nuxt.config.ts</code> or{' '}
482
+ <code style={inlineCodeStyle}>app.html</code>
483
+ </p>
484
+ <CodeBlock
485
+ language="ts"
486
+ copyText={`app: {\n head: {\n script: [{ src: 'https://dev-editor-flow.vercel.app/dev-editor-inspector.js' }]\n }\n}`}
487
+ code={`// nuxt.config.ts
488
+ export default defineNuxtConfig({
489
+ app: {
490
+ head: {
491
+ script: [
492
+ { src: 'https://dev-editor-flow.vercel.app/dev-editor-inspector.js' }
493
+ ]
494
+ }
495
+ }
496
+ });`}
497
+ />
498
+ </div>
499
+ </FrameworkSection>
500
+
501
+ {/* Svelte / SvelteKit */}
502
+ <FrameworkSection
503
+ id="svelte-sveltekit"
504
+ title="Svelte / SvelteKit"
505
+ icon="&#127793;"
506
+ >
507
+ <p style={{ ...mutedText, marginBottom: '0.5rem' }}>
508
+ Add to <code style={inlineCodeStyle}>src/app.html</code>
509
+ </p>
510
+ <CodeBlock
511
+ language="html"
512
+ copyText={SCRIPT_TAG}
513
+ code={`<!-- src/app.html -->
514
+ <!DOCTYPE html>
515
+ <html lang="en">
516
+ <head>
517
+ <meta charset="utf-8" />
518
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
519
+ %sveltekit.head%
520
+ </head>
521
+ <body data-sveltekit-preload-data="hover">
522
+ <div style="display: contents">%sveltekit.body%</div>
523
+ <script src="https://dev-editor-flow.vercel.app/dev-editor-inspector.js"></script>
524
+ </body>
525
+ </html>`}
526
+ />
527
+ </FrameworkSection>
528
+ </FrameworkAccordion>
529
+ </Section>
530
+
531
+ {/* Troubleshooting */}
532
+ <Section id="troubleshooting" title="Troubleshooting">
533
+ <div className="flex flex-col gap-4">
534
+ <TroubleshootItem title="Inspector script not detected">
535
+ <p>
536
+ If the banner appears after 5 seconds, the automatic proxy
537
+ injection didn&apos;t work for your setup. Add the script
538
+ tag manually using the framework guides above. Make sure
539
+ pAInt is running on the port shown in the script URL.
540
+ </p>
541
+ </TroubleshootItem>
542
+
543
+ <TroubleshootItem title="CORS or Cross-Origin errors">
544
+ <p>
545
+ pAInt proxy runs on a different port than your project. If
546
+ your project sets strict CORS headers, the proxy may be
547
+ blocked. The automatic method handles this by serving
548
+ everything from the same origin. For the manual method,
549
+ ensure your dev server allows requests from{' '}
550
+ <code style={inlineCodeStyle}>localhost:4000</code>.
551
+ </p>
552
+ </TroubleshootItem>
553
+
554
+ <TroubleshootItem title="COEP / COOP headers blocking the iframe">
555
+ <p>
556
+ Some frameworks set{' '}
557
+ <code style={inlineCodeStyle}>
558
+ Cross-Origin-Embedder-Policy
559
+ </code>{' '}
560
+ or{' '}
561
+ <code style={inlineCodeStyle}>
562
+ Cross-Origin-Opener-Policy
563
+ </code>{' '}
564
+ headers that prevent loading in an iframe. Check your server
565
+ config or middleware and relax these headers in development.
566
+ </p>
567
+ </TroubleshootItem>
568
+
569
+ <TroubleshootItem title="Infinite iframe reload">
570
+ <p>
571
+ This happens when the target page&apos;s client-side router
572
+ detects the proxy URL and redirects. pAInt&apos;s proxy
573
+ strips <code style={inlineCodeStyle}>&lt;script&gt;</code>{' '}
574
+ tags to prevent this. If you still see reloads, check that
575
+ no inline scripts or meta-refresh tags are causing
576
+ navigation.
577
+ </p>
578
+ </TroubleshootItem>
579
+
580
+ <TroubleshootItem title="Styles look different in the editor">
581
+ <p>
582
+ The proxy serves SSR HTML with CSS intact but strips
583
+ JavaScript. If your styles depend on client-side JS (e.g.,
584
+ CSS-in-JS runtime injection), some styles may be missing.
585
+ Use the manual script method with your full dev server if
586
+ CSS-in-JS is critical.
587
+ </p>
588
+ </TroubleshootItem>
589
+ </div>
590
+ </Section>
591
+
592
+ {/* FAQ */}
593
+ <Section id="faq" title="FAQ">
594
+ <FaqAccordion>
595
+ <FaqSection
596
+ id="faq-safe"
597
+ question="Is it safe to add the inspector script to my project?"
598
+ >
599
+ <p>
600
+ Yes. The inspector script is a lightweight, read-only
601
+ observer that listens for hover/click events and reports
602
+ element metadata (tag name, styles, bounding box) back to
603
+ pAInt via <code style={inlineCodeStyle}>postMessage</code>.
604
+ It does not modify your source code, send data to external
605
+ servers, or execute arbitrary code. It only communicates
606
+ with pAInt origin.
607
+ </p>
608
+ </FaqSection>
609
+
610
+ <FaqSection
611
+ id="faq-source"
612
+ question="Does pAInt have access to my source files?"
613
+ >
614
+ <p>
615
+ Not directly. The editor works with the rendered HTML/CSS in
616
+ the browser — it never reads or writes your project&apos;s
617
+ source files on its own. When you use the{' '}
618
+ <strong style={{ color: 'var(--text-primary)' }}>
619
+ Send to Claude Code
620
+ </strong>{' '}
621
+ feature, a changelog of your visual edits is sent to Claude
622
+ Code (running locally on your machine), which then applies
623
+ changes to your files. The editor itself has no filesystem
624
+ access.
625
+ </p>
626
+ </FaqSection>
627
+
628
+ <FaqSection
629
+ id="faq-proxy"
630
+ question="Does the reverse proxy forward requests to external servers?"
631
+ >
632
+ <p>
633
+ No. The proxy enforces{' '}
634
+ <strong style={{ color: 'var(--text-primary)' }}>
635
+ localhost-only
636
+ </strong>{' '}
637
+ validation. It will only connect to{' '}
638
+ <code style={inlineCodeStyle}>127.0.0.1</code> /{' '}
639
+ <code style={inlineCodeStyle}>localhost</code> addresses.
640
+ Any attempt to proxy to an external host is rejected.
641
+ </p>
642
+ </FaqSection>
643
+
644
+ <FaqSection
645
+ id="faq-production"
646
+ question="Should I remove the script tag before deploying to production?"
647
+ >
648
+ <p>
649
+ Yes. The inspector script is intended for local development
650
+ only. Remove it (or wrap it in an environment check) before
651
+ deploying. If you forget, the script will try to connect to
652
+ pAInt origin and silently fail — it won&apos;t affect your
653
+ users — but it&apos;s best practice to keep it out of
654
+ production builds.
655
+ </p>
656
+ </FaqSection>
657
+
658
+ <FaqSection
659
+ id="faq-network"
660
+ question="Can other people on my network see my project through the editor?"
661
+ >
662
+ <p>
663
+ By default, both pAInt and your dev server run on{' '}
664
+ <code style={inlineCodeStyle}>localhost</code>, which is
665
+ only accessible from your machine. If you&apos;ve configured
666
+ your dev server to listen on{' '}
667
+ <code style={inlineCodeStyle}>0.0.0.0</code>, other devices
668
+ on your network could access it directly, but the
669
+ pAInt&apos;s proxy still only connects to localhost.
670
+ </p>
671
+ </FaqSection>
672
+
673
+ <FaqSection
674
+ id="faq-hosted"
675
+ question="Is my data sent anywhere when using the hosted version?"
676
+ >
677
+ <p>
678
+ The hosted version at{' '}
679
+ <code style={inlineCodeStyle}>
680
+ dev-editor-flow.vercel.app
681
+ </code>{' '}
682
+ serves the editor UI only. Your project&apos;s HTML is
683
+ fetched by the proxy running on that server, but no project
684
+ data is stored, logged, or shared. All style edits and
685
+ changelogs are kept in your browser&apos;s{' '}
686
+ <code style={inlineCodeStyle}>localStorage</code>.
687
+ </p>
688
+ </FaqSection>
689
+ </FaqAccordion>
690
+ </Section>
691
+ </div>
692
+ {/* end main content */}
693
+ </div>
694
+ {/* end flex row */}
695
+
696
+ {/* Footer */}
697
+ <footer
698
+ className="mt-16 pt-6 text-sm text-center"
699
+ style={{
700
+ borderTop: '1px solid var(--border)',
701
+ color: 'var(--text-muted)',
702
+ }}
703
+ >
704
+ pAInt &middot;{' '}
705
+ <Link
706
+ href="/"
707
+ className="no-underline"
708
+ style={{ color: 'var(--accent)' }}
709
+ >
710
+ Open Editor
711
+ </Link>
712
+ </footer>
713
+ </div>
714
+ </div>
715
+ )
716
+ }
717
+
718
+ /* ─── Helper components (server-rendered) ─── */
719
+
720
+ function Section({
721
+ id,
722
+ title,
723
+ children,
724
+ }: {
725
+ id?: string
726
+ title: string
727
+ children: React.ReactNode
728
+ }) {
729
+ return (
730
+ <section id={id} className="mb-14 scroll-mt-12">
731
+ <h2
732
+ className="text-xl font-semibold mb-5 pb-2"
733
+ style={{
734
+ color: 'var(--text-primary)',
735
+ borderBottom: '1px solid var(--border)',
736
+ }}
737
+ >
738
+ {title}
739
+ </h2>
740
+ {children}
741
+ </section>
742
+ )
743
+ }
744
+
745
+ function Step({
746
+ number,
747
+ children,
748
+ }: {
749
+ number: number
750
+ children: React.ReactNode
751
+ }) {
752
+ return (
753
+ <li className="flex gap-4">
754
+ <span
755
+ className="shrink-0 w-7 h-7 rounded-full flex items-center justify-center text-sm font-bold"
756
+ style={{
757
+ background: 'var(--accent)',
758
+ color: '#fff',
759
+ }}
760
+ >
761
+ {number}
762
+ </span>
763
+ <div className="flex-1 flex flex-col gap-2">{children}</div>
764
+ </li>
765
+ )
766
+ }
767
+
768
+ function TroubleshootItem({
769
+ title,
770
+ children,
771
+ }: {
772
+ title: string
773
+ children: React.ReactNode
774
+ }) {
775
+ return (
776
+ <div
777
+ className="rounded-md px-4 py-4"
778
+ style={{
779
+ background: 'var(--bg-secondary)',
780
+ border: '1px solid var(--border)',
781
+ }}
782
+ >
783
+ <h4
784
+ className="text-base font-medium mb-2"
785
+ style={{ color: 'var(--text-primary)' }}
786
+ >
787
+ {title}
788
+ </h4>
789
+ <div
790
+ style={{
791
+ color: 'var(--text-secondary)',
792
+ fontSize: '0.9rem',
793
+ lineHeight: 1.6,
794
+ }}
795
+ >
796
+ {children}
797
+ </div>
798
+ </div>
799
+ )
800
+ }
801
+
802
+ function UseCaseItem({
803
+ title,
804
+ description,
805
+ }: {
806
+ title: string
807
+ description: string
808
+ }) {
809
+ return (
810
+ <div
811
+ className="rounded-md px-4 py-4"
812
+ style={{
813
+ background: 'var(--bg-secondary)',
814
+ border: '1px solid var(--border)',
815
+ }}
816
+ >
817
+ <h4
818
+ className="text-base font-medium mb-2"
819
+ style={{ color: 'var(--text-primary)' }}
820
+ >
821
+ {title}
822
+ </h4>
823
+ <p
824
+ style={{
825
+ color: 'var(--text-secondary)',
826
+ fontSize: '0.9rem',
827
+ lineHeight: 1.6,
828
+ margin: 0,
829
+ }}
830
+ >
831
+ {description}
832
+ </p>
833
+ </div>
834
+ )
835
+ }
836
+
837
+ const bodyText: React.CSSProperties = {
838
+ color: 'var(--text-secondary)',
839
+ fontSize: '1rem',
840
+ lineHeight: 1.7,
841
+ }
842
+
843
+ const mutedText: React.CSSProperties = {
844
+ color: 'var(--text-muted)',
845
+ fontSize: '0.875rem',
846
+ lineHeight: 1.6,
847
+ }
848
+
849
+ const inlineCodeStyle: React.CSSProperties = {
850
+ background: 'var(--bg-tertiary)',
851
+ padding: '2px 6px',
852
+ borderRadius: '3px',
853
+ fontSize: '0.85em',
854
+ color: 'var(--warning)',
855
+ }