launchframe 0.2.4 → 0.2.6
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.
- package/package.json +1 -1
- package/template/.amazonq/cli-agents/clone-website.json +1 -1
- package/template/.amazonq/rules/project.md +70 -0
- package/template/.augment/commands/clone-website.md +42 -12
- package/template/.claude/skills/clone-website/SKILL.md +42 -12
- package/template/.claude/skills/marketing-social-proof-motion/SKILL.md +47 -0
- package/template/.clinerules +75 -1
- package/template/.codex/skills/clone-website/SKILL.md +42 -12
- package/template/.continue/commands/clone-website.md +42 -12
- package/template/.continue/rules/project.md +70 -0
- package/template/.cursor/commands/clone-website.md +42 -12
- package/template/.cursor/commands/marketing-social-proof-motion.md +42 -0
- package/template/.cursor/rules/project.mdc +1 -1
- package/template/.gemini/commands/clone-website.toml +42 -12
- package/template/.github/copilot-instructions.md +70 -0
- package/template/.github/skills/clone-website/SKILL.md +42 -12
- package/template/.opencode/commands/clone-website.md +42 -12
- package/template/.windsurf/workflows/clone-website.md +42 -12
- package/template/AGENTS.md +61 -1
- package/template/docs/research/INSPECTION_GUIDE.md +15 -0
- package/template/package-lock.json +8795 -0
- package/template/scripts/recon-playwright.mjs +90 -17
- package/template/src/app/globals.css +93 -1
- package/template/src/app/layout.tsx +3 -2
- package/template/src/app/page.tsx +3 -7
- package/template/src/components/marketing/scribewise-landing.tsx +34 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Reads URL from launchframe.config.json (or --url). Writes:
|
|
6
6
|
* - docs/design-references/playwright-<host>-<w>px.png (full page)
|
|
7
|
-
* - docs/research/computed-snapshot.json (styles + asset inventory + bot-wall hint)
|
|
7
|
+
* - docs/research/computed-snapshot.json (styles + merged asset inventory + bot-wall hint)
|
|
8
8
|
* - docs/research/MEDIA_MANIFEST.md (table of discovered media URLs)
|
|
9
9
|
*
|
|
10
10
|
* Usage:
|
|
@@ -52,27 +52,91 @@ function safeHost(url) {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
/**
|
|
55
|
+
/**
|
|
56
|
+
* Scroll the full document depth slowly so lazy-loaded media and below-fold DOM resolve.
|
|
57
|
+
* Repeats until scrollHeight stabilizes (handles incremental infinite layouts up to max rounds).
|
|
58
|
+
*/
|
|
56
59
|
async function scrollFullPage(page) {
|
|
57
60
|
await page.evaluate(async () => {
|
|
58
61
|
const delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
const stepSize = () => Math.max(1, Math.floor(window.innerHeight * 0.85));
|
|
63
|
+
|
|
64
|
+
let unchangedStreak = 0;
|
|
65
|
+
let prevTotal = -1;
|
|
66
|
+
|
|
67
|
+
for (let round = 0; round < 12; round++) {
|
|
68
|
+
let y = 0;
|
|
69
|
+
let limit = document.documentElement.scrollHeight;
|
|
70
|
+
|
|
71
|
+
while (y < limit) {
|
|
72
|
+
y = Math.min(y + stepSize(), limit);
|
|
73
|
+
window.scrollTo(0, y);
|
|
74
|
+
await delay(130);
|
|
75
|
+
limit = document.documentElement.scrollHeight;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
window.scrollTo(0, document.documentElement.scrollHeight);
|
|
79
|
+
await delay(420);
|
|
80
|
+
|
|
81
|
+
const h = document.documentElement.scrollHeight;
|
|
82
|
+
|
|
83
|
+
window.scrollTo(0, 0);
|
|
84
|
+
await delay(220);
|
|
85
|
+
|
|
86
|
+
if (h === prevTotal) unchangedStreak++;
|
|
87
|
+
else unchangedStreak = 0;
|
|
88
|
+
prevTotal = h;
|
|
89
|
+
|
|
90
|
+
if (unchangedStreak >= 2) break;
|
|
66
91
|
}
|
|
67
|
-
window.scrollTo(0, 0);
|
|
68
|
-
await delay(300);
|
|
69
92
|
});
|
|
70
93
|
}
|
|
71
94
|
|
|
95
|
+
function mergeAssetInventory(base, extra) {
|
|
96
|
+
if (!extra) return base;
|
|
97
|
+
const imgKey = (i) => i.src || "";
|
|
98
|
+
const seenImg = new Set(base.images.map(imgKey));
|
|
99
|
+
const images = [...base.images];
|
|
100
|
+
for (const i of extra.images) {
|
|
101
|
+
const k = imgKey(i);
|
|
102
|
+
if (k && !seenImg.has(k)) {
|
|
103
|
+
seenImg.add(k);
|
|
104
|
+
images.push(i);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const vidKey = (v) => `${v.src || ""}|${v.poster || ""}`;
|
|
109
|
+
const seenVid = new Set(base.videos.map(vidKey));
|
|
110
|
+
const videos = [...base.videos];
|
|
111
|
+
for (const v of extra.videos) {
|
|
112
|
+
const k = vidKey(v);
|
|
113
|
+
if ((v.src || v.poster) && !seenVid.has(k)) {
|
|
114
|
+
seenVid.add(k);
|
|
115
|
+
videos.push(v);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const seenBg = new Set(base.backgroundImages.map((b) => b.url));
|
|
120
|
+
const backgroundImages = [...base.backgroundImages];
|
|
121
|
+
for (const b of extra.backgroundImages) {
|
|
122
|
+
if (b.url && !seenBg.has(b.url)) {
|
|
123
|
+
seenBg.add(b.url);
|
|
124
|
+
backgroundImages.push(b);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
images,
|
|
130
|
+
videos,
|
|
131
|
+
backgroundImages,
|
|
132
|
+
svgCount: Math.max(base.svgCount ?? 0, extra.svgCount ?? 0),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
72
136
|
async function gatherPageData(page) {
|
|
73
137
|
return page.evaluate(() => {
|
|
74
138
|
const title = document.title || "";
|
|
75
|
-
const text = (document.body?.innerText || "").slice(0,
|
|
139
|
+
const text = (document.body?.innerText || "").slice(0, 80000);
|
|
76
140
|
const likelyBotWall =
|
|
77
141
|
/just a moment/i.test(title) ||
|
|
78
142
|
/checking your browser/i.test(text) ||
|
|
@@ -145,7 +209,7 @@ async function gatherPageData(page) {
|
|
|
145
209
|
return {
|
|
146
210
|
title,
|
|
147
211
|
likelyBotWall,
|
|
148
|
-
textSample: text.slice(0,
|
|
212
|
+
textSample: text.slice(0, 24000),
|
|
149
213
|
assetInventory: {
|
|
150
214
|
images: imgs.filter((i) => i.src),
|
|
151
215
|
videos,
|
|
@@ -272,23 +336,32 @@ async function main() {
|
|
|
272
336
|
await new Promise((r) => setTimeout(r, 2500));
|
|
273
337
|
snapshot.finalUrl = page.url();
|
|
274
338
|
|
|
339
|
+
let mergedInventory = null;
|
|
340
|
+
let desktopLandmarks = null;
|
|
341
|
+
|
|
275
342
|
for (const w of [1440, 390]) {
|
|
276
343
|
await page.setViewportSize({ width: w, height: w === 1440 ? 900 : 844 });
|
|
277
|
-
|
|
344
|
+
await scrollFullPage(page);
|
|
278
345
|
|
|
279
346
|
const shotPath = join(ROOT, "docs", "design-references", `playwright-${host}-${w}px.png`);
|
|
280
347
|
await page.screenshot({ path: shotPath, fullPage: true });
|
|
281
348
|
snapshot.viewports[w] = { screenshot: `docs/design-references/playwright-${host}-${w}px.png` };
|
|
282
349
|
|
|
350
|
+
const data = await gatherPageData(page);
|
|
351
|
+
|
|
283
352
|
if (w === 1440) {
|
|
284
|
-
const data = await gatherPageData(page);
|
|
285
353
|
snapshot.title = data.title;
|
|
286
354
|
snapshot.likelyBotWall = data.likelyBotWall;
|
|
287
|
-
|
|
288
|
-
|
|
355
|
+
mergedInventory = data.assetInventory;
|
|
356
|
+
desktopLandmarks = data.computedLandmarks;
|
|
289
357
|
snapshot.textSample = data.textSample;
|
|
358
|
+
} else {
|
|
359
|
+
mergedInventory = mergeAssetInventory(mergedInventory, data.assetInventory);
|
|
290
360
|
}
|
|
291
361
|
}
|
|
362
|
+
|
|
363
|
+
snapshot.assetInventory = mergedInventory;
|
|
364
|
+
snapshot.computedLandmarks = desktopLandmarks;
|
|
292
365
|
} finally {
|
|
293
366
|
await browser.close();
|
|
294
367
|
}
|
|
@@ -46,6 +46,9 @@
|
|
|
46
46
|
--radius-2xl: calc(var(--radius) * 1.8);
|
|
47
47
|
--radius-3xl: calc(var(--radius) * 2.2);
|
|
48
48
|
--radius-4xl: calc(var(--radius) * 2.6);
|
|
49
|
+
--color-brand: var(--brand);
|
|
50
|
+
--color-brand-foreground: var(--brand-foreground);
|
|
51
|
+
--color-brand-muted: var(--brand-muted);
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
:root {
|
|
@@ -73,6 +76,10 @@
|
|
|
73
76
|
--chart-4: oklch(0.371 0 0);
|
|
74
77
|
--chart-5: oklch(0.269 0 0);
|
|
75
78
|
--radius: 0.625rem;
|
|
79
|
+
/* Scribewise brand accents (ink + notebook blue) */
|
|
80
|
+
--brand: oklch(0.52 0.14 252);
|
|
81
|
+
--brand-foreground: oklch(0.985 0 0);
|
|
82
|
+
--brand-muted: oklch(0.94 0.03 252);
|
|
76
83
|
--sidebar: oklch(0.985 0 0);
|
|
77
84
|
--sidebar-foreground: oklch(0.145 0 0);
|
|
78
85
|
--sidebar-primary: oklch(0.205 0 0);
|
|
@@ -84,6 +91,9 @@
|
|
|
84
91
|
}
|
|
85
92
|
|
|
86
93
|
.dark {
|
|
94
|
+
--brand: oklch(0.62 0.12 252);
|
|
95
|
+
--brand-foreground: oklch(0.145 0 0);
|
|
96
|
+
--brand-muted: oklch(0.26 0.04 252);
|
|
87
97
|
--background: oklch(0.145 0 0);
|
|
88
98
|
--foreground: oklch(0.985 0 0);
|
|
89
99
|
--card: oklch(0.205 0 0);
|
|
@@ -117,6 +127,88 @@
|
|
|
117
127
|
--sidebar-ring: oklch(0.556 0 0);
|
|
118
128
|
}
|
|
119
129
|
|
|
130
|
+
@keyframes scribewise-marquee {
|
|
131
|
+
from {
|
|
132
|
+
transform: translateX(0);
|
|
133
|
+
}
|
|
134
|
+
to {
|
|
135
|
+
transform: translateX(-50%);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@keyframes scribewise-float {
|
|
140
|
+
0%,
|
|
141
|
+
100% {
|
|
142
|
+
transform: translateY(0);
|
|
143
|
+
}
|
|
144
|
+
50% {
|
|
145
|
+
transform: translateY(-8px);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@keyframes scribewise-pixel-blink {
|
|
150
|
+
0%,
|
|
151
|
+
49% {
|
|
152
|
+
opacity: 1;
|
|
153
|
+
}
|
|
154
|
+
50%,
|
|
155
|
+
100% {
|
|
156
|
+
opacity: 0.25;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
@keyframes scribewise-grid-shift {
|
|
161
|
+
0% {
|
|
162
|
+
transform: translate(0, 0);
|
|
163
|
+
}
|
|
164
|
+
100% {
|
|
165
|
+
transform: translate(-24px, -24px);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.scribewise-marquee-track {
|
|
170
|
+
display: flex;
|
|
171
|
+
width: max-content;
|
|
172
|
+
gap: 3rem;
|
|
173
|
+
animation: scribewise-marquee 42s linear infinite;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.scribewise-bg-grid {
|
|
177
|
+
animation: scribewise-grid-shift 18s linear infinite;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
@media (prefers-reduced-motion: reduce) {
|
|
181
|
+
.scribewise-marquee-track,
|
|
182
|
+
.scribewise-bg-grid {
|
|
183
|
+
animation: none !important;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.scribewise-pixel-brain rect,
|
|
187
|
+
.scribewise-soft-float {
|
|
188
|
+
animation: none !important;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.scribewise-pixel-brain rect {
|
|
193
|
+
animation: scribewise-pixel-blink 1s steps(1, end) infinite;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.scribewise-pixel-brain rect:nth-child(1) {
|
|
197
|
+
animation-delay: 0s;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.scribewise-pixel-brain rect:nth-child(2) {
|
|
201
|
+
animation-delay: 0.12s;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.scribewise-pixel-brain rect:nth-child(3) {
|
|
205
|
+
animation-delay: 0.24s;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.scribewise-pixel-brain rect:nth-child(4) {
|
|
209
|
+
animation-delay: 0.36s;
|
|
210
|
+
}
|
|
211
|
+
|
|
120
212
|
@layer base {
|
|
121
213
|
* {
|
|
122
214
|
@apply border-border outline-ring/50;
|
|
@@ -127,4 +219,4 @@
|
|
|
127
219
|
html {
|
|
128
220
|
@apply font-sans;
|
|
129
221
|
}
|
|
130
|
-
}
|
|
222
|
+
}
|
|
@@ -13,8 +13,9 @@ const geistMono = Geist_Mono({
|
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
export const metadata: Metadata = {
|
|
16
|
-
title: "
|
|
17
|
-
description:
|
|
16
|
+
title: "Scribewise — AI workspace for structured notes",
|
|
17
|
+
description:
|
|
18
|
+
"Capture voice, screenshots, and messy thoughts into structured notes synced everywhere.",
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
export default function RootLayout({
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
+
import { ScribewiseLanding } from "@/components/marketing/scribewise-landing";
|
|
2
|
+
|
|
1
3
|
export default function Home() {
|
|
2
|
-
return
|
|
3
|
-
<main className="flex min-h-screen items-center justify-center">
|
|
4
|
-
<p className="text-muted-foreground">
|
|
5
|
-
Clone target not yet built. Run <code className="font-mono text-foreground">/clone-website</code> to start.
|
|
6
|
-
</p>
|
|
7
|
-
</main>
|
|
8
|
-
);
|
|
4
|
+
return <ScribewiseLanding />;
|
|
9
5
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
|
|
3
|
+
import { Button } from "@/components/ui/button";
|
|
4
|
+
|
|
5
|
+
export function ScribewiseLanding() {
|
|
6
|
+
return (
|
|
7
|
+
<main className="relative flex flex-1 flex-col items-center justify-center gap-8 overflow-hidden px-6 py-24 text-center">
|
|
8
|
+
<div
|
|
9
|
+
aria-hidden
|
|
10
|
+
className="pointer-events-none absolute inset-0 -z-10 bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,color-mix(in_oklab,var(--primary)_28%,transparent),transparent)]"
|
|
11
|
+
/>
|
|
12
|
+
<p className="text-muted-foreground text-xs font-medium tracking-[0.2em] uppercase">
|
|
13
|
+
Launchframe template
|
|
14
|
+
</p>
|
|
15
|
+
<div className="max-w-2xl space-y-4">
|
|
16
|
+
<h1 className="text-balance font-semibold text-4xl tracking-tight sm:text-5xl">
|
|
17
|
+
Ready for `/clone-website`
|
|
18
|
+
</h1>
|
|
19
|
+
<p className="text-muted-foreground text-balance text-lg">
|
|
20
|
+
Add your target URL and SaaS idea via <code className="text-foreground">launchframe.config.json</code>,
|
|
21
|
+
run recon if needed, then let your agent ship the full landing.
|
|
22
|
+
</p>
|
|
23
|
+
</div>
|
|
24
|
+
<div className="flex flex-wrap items-center justify-center gap-3">
|
|
25
|
+
<Button nativeButton={false} render={<Link href="https://github.com/evangruhlkey/launchframe" />}>
|
|
26
|
+
Launchframe docs
|
|
27
|
+
</Button>
|
|
28
|
+
<Button variant="outline" nativeButton={false} render={<Link href="/" />}>
|
|
29
|
+
Replace this page
|
|
30
|
+
</Button>
|
|
31
|
+
</div>
|
|
32
|
+
</main>
|
|
33
|
+
);
|
|
34
|
+
}
|