launchframe 0.3.0 → 0.4.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 (38) hide show
  1. package/.claude/skills/clone-website/SKILL.md +473 -564
  2. package/.clinerules +147 -285
  3. package/.codex/skills/clone-website/SKILL.md +473 -564
  4. package/.continue/commands/clone-website.md +475 -566
  5. package/.continue/rules/project.md +151 -285
  6. package/.cursor/commands/clone-website.md +470 -561
  7. package/.cursor/rules/project.mdc +7 -22
  8. package/.gemini/commands/clone-website.toml +476 -567
  9. package/.github/copilot-instructions.md +147 -281
  10. package/.github/skills/clone-website/SKILL.md +473 -564
  11. package/.gitignore +49 -0
  12. package/.opencode/commands/clone-website.md +473 -564
  13. package/.windsurf/workflows/clone-website.md +470 -561
  14. package/AGENTS.md +65 -160
  15. package/README.md +162 -121
  16. package/bin/launchframe.mjs +343 -0
  17. package/docs/research/INSPECTION_GUIDE.md +80 -124
  18. package/package.json +97 -54
  19. package/src/app/globals.css +1 -93
  20. package/src/app/layout.tsx +16 -5
  21. package/src/app/page.tsx +37 -2
  22. package/src/lib/launchframe-config.ts +8 -0
  23. package/.amazonq/cli-agents/clone-website.json +0 -9
  24. package/.amazonq/rules/project.md +0 -281
  25. package/.augment/commands/clone-website.md +0 -565
  26. package/.claude/skills/marketing-social-proof-motion/SKILL.md +0 -47
  27. package/.cursor/commands/marketing-social-proof-motion.md +0 -42
  28. package/.nvmrc +0 -1
  29. package/CHANGELOG.md +0 -80
  30. package/START_HERE.md +0 -15
  31. package/docs/design-references/playwright-example.com-1440px.png +0 -0
  32. package/docs/design-references/playwright-example.com-390px.png +0 -0
  33. package/launchframe.config.json +0 -14
  34. package/public/images/.gitkeep +0 -0
  35. package/public/seo/.gitkeep +0 -0
  36. package/public/videos/.gitkeep +0 -0
  37. package/scripts/recon-playwright.mjs +0 -396
  38. package/src/components/marketing/scribewise-landing.tsx +0 -34
@@ -1,42 +0,0 @@
1
- <!-- Canonical source: `.claude/skills/marketing-social-proof-motion/SKILL.md` — edit there first, then mirror here if needed. -->
2
-
3
- # Marketing social proof, logos & motion
4
-
5
- Ship sections that **feel alive**: named companies, visible marks, and **layered animation**. Generic gray blobs and static stacks fail this skill.
6
-
7
- ## Named companies & copy
8
-
9
- - **Name real companies** where they strengthen the story: integrations (“Works with Slack, Salesforce…”), ecosystems, categories, or illustrative comparables the user allows.
10
- - **Do not invent endorsements.** Prefer truthful framings — “integrations,” “compatible with,” “shared stack,” “customers like” — unless the user supplied verified quotes/customers.
11
- - **Keep lists purposeful.** Match companies to `launchframe.config.json#idea` when present; avoid unrelated mega-brand name-dropping.
12
-
13
- ## Logo marks & imagery
14
-
15
- - **Prefer real marks** when implementation-ready: monochrome SVG or PNG from vendor press/media kits, neutral compatibility strips, or OSS icon sets where license permits. Normalize to one visual system (single height, consistent padding, grayscale or duotone).
16
- - **Store assets** under `public/images/logos/` or `public/images/partners/` with predictable filenames; reference **local paths** in components — no hotlinked SVGs from third-party CDNs in production UI.
17
- - **If a mark is missing, blocked, or unclear:**
18
- - Generate **original** imagery (image-generation tool or hand-authored SVG) — illustrated tiles, abstract glyphs, or metaphor icons tied to the **idea** — **not** counterfeit logos.
19
- - Or ship **clean text lockups** (company wordmark as styled type) where safer than guessing trademark artwork.
20
-
21
- ## Motion emphasis (non-negotiable bar)
22
-
23
- Motion sells credibility — implement several layers using **`framer-motion`** + CSS loops where appropriate; honor **`prefers-reduced-motion`**.
24
-
25
- | Layer | Goal | Typical implementation |
26
- |-------|------|-------------------------|
27
- | **Entrance** | Logos/testimonials **stagger in** | Parent `variants` + `staggerChildren`; opacity + `y` or blur-in on type |
28
- | **Scroll** | Proof bands **ease up / fade** on first view | `whileInView` + `viewport.once`; sensible `margin` so reveals feel early |
29
- | **Ambient** | Strip feels “live” | **Infinite marquee** (slow), gradient drift, subtle opacity pulse on logos |
30
- | **Hover / focus** | Cards lift; logos brighten | `whileHover` / CSS transitions on transforms + shadow |
31
- | **Depth** | Premium polish | Light pointer **parallax** on hero/logo cluster (small ranges) or scroll-linked `useTransform` |
32
-
33
- Also align tier **A–E** choreography and reduced-motion fallbacks described in **`AGENTS.md`** (Production polish → Motion choreography).
34
-
35
- ## Hand-off checklist
36
-
37
- - [ ] Companies named in copy match user intent and truthful framing
38
- - [ ] Every logo path documented; placeholders explained if generated
39
- - [ ] At least **two** motion layers beyond a single fade (e.g., stagger + marquee, or scroll reveal + hover lift)
40
- - [ ] Reduced-motion path removes loops and tightens entrances
41
-
42
- When cloning an existing page, **extract** logo SVGs/rasters like any other asset; when authoring from scratch, follow this skill instead of shipping motionless filler.
package/.nvmrc DELETED
@@ -1 +0,0 @@
1
- 24
package/CHANGELOG.md DELETED
@@ -1,80 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [Unreleased]
9
-
10
- ### Changed
11
- - Raised the project Node.js baseline to 24 across local development, CI, Docker, and contributor-facing documentation
12
-
13
- ## [0.3.1] - 2026-03-29
14
-
15
- ### Fixed
16
- - `sync-agent-rules.sh` failing to resolve `@file` imports on Windows due to CRLF line endings — platform instruction files now correctly inline the Inspection Guide content
17
-
18
- ## [0.3.0] - 2026-03-29
19
-
20
- ### Added
21
- - Multi-URL support for `/clone-website` — clone multiple sites in a single command with parallel processing and isolated output
22
- - CI quality gates via GitHub Actions — automated lint, typecheck, and build on every push and PR
23
- - `npm run typecheck` and `npm run check` scripts for local quality validation
24
- - `.gitattributes` for cross-platform line ending normalization
25
- - `.nvmrc` to pin Node.js 20 for contributor consistency
26
-
27
- ### Changed
28
- - Streamlined PR template — removed redundant checklist items and screenshots section
29
- - Improved project description and README — clearer use cases, limitations, and modern wording
30
- - Refined documentation and agent rules across all platforms for clarity and consistency
31
- - Fixed CRLF handling in `sync-skills.mjs` for reliable Windows operation
32
-
33
- ### Removed
34
- - Outdated use case from README documentation
35
-
36
- ## [0.2.0] - 2026-03-28
37
-
38
- ### Added
39
- - Multi-platform AI agent support: Claude Code, Codex CLI, OpenCode, GitHub Copilot, Cursor, Windsurf, Gemini CLI, Cline/Roo Code, Continue, Amazon Q, Augment Code, Aider
40
- - Platform-specific instruction files and `/clone-website` skill for each supported agent
41
- - `scripts/sync-agent-rules.sh` to regenerate platform instruction files from AGENTS.md
42
- - `scripts/sync-skills.mjs` to regenerate `/clone-website` skill across all platforms
43
- - GEMINI.md for Gemini CLI configuration
44
- - Supported Platforms table in README
45
- - "Updating for Other Platforms" documentation section in README
46
-
47
- ### Changed
48
- - README now describes the project as multi-agent (Claude Code recommended, not required)
49
- - AGENTS.md updated with sync script reminders
50
-
51
- ## [0.1.1] - 2026-03-28
52
-
53
- ### Added
54
- - Bug report and feature request issue templates
55
- - Pull request template with checklist
56
- - CHANGELOG.md following Keep a Changelog format
57
- - Package.json metadata (description, repository, homepage, keywords, engines)
58
-
59
- ### Fixed
60
- - LICENSE copyright holder now attributed to JCodesMore
61
-
62
- ## [0.1.0] - 2026-03-28
63
-
64
- ### Added
65
- - Initial template scaffold for website reverse-engineering with Claude Code
66
- - `/clone-website` skill for full-site cloning pipeline
67
- - `/build-from-spec` and `/customize` skills
68
- - Parallel builder agents with git worktree isolation
69
- - Chrome MCP integration for design token extraction
70
- - Comprehensive inspection guide and project structure documentation
71
- - Next.js 16 + shadcn/ui + Tailwind CSS v4 base scaffold
72
- - MIT license
73
- - README with badges, demo section, quick start, and star history
74
-
75
- [Unreleased]: https://github.com/JCodesMore/ai-website-cloner-template/compare/v0.3.1...HEAD
76
- [0.3.1]: https://github.com/JCodesMore/ai-website-cloner-template/compare/v0.3.0...v0.3.1
77
- [0.3.0]: https://github.com/JCodesMore/ai-website-cloner-template/compare/v0.2.0...v0.3.0
78
- [0.2.0]: https://github.com/JCodesMore/ai-website-cloner-template/compare/v0.1.1...v0.2.0
79
- [0.1.1]: https://github.com/JCodesMore/ai-website-cloner-template/compare/v0.1.0...v0.1.1
80
- [0.1.0]: https://github.com/JCodesMore/ai-website-cloner-template/releases/tag/v0.1.0
package/START_HERE.md DELETED
@@ -1,15 +0,0 @@
1
- # Start here
2
-
3
- Launchframe wrote files into **this folder** (your project root), including **`.cursor`** and **`.claude`**, so they work when you open this directory in your editor.
4
-
5
- ## Do this next
6
-
7
- 1. **Open this folder** in [Cursor](https://cursor.com/) (the root that contains `.cursor` — not a parent directory).
8
- 2. In chat, say: **Build it.**
9
-
10
- That tells your AI to read `launchframe.config.json` and `AGENTS.md` and run the full **clone + SaaS rebrand** pipeline (same work as `/clone-website`).
11
-
12
- ## Optional
13
-
14
- - Prefer a slash command? Use **`/clone-website`** in Cursor.
15
- - Wrong URL or idea? Edit **`launchframe.config.json`**, then say **Build it** again.
@@ -1,14 +0,0 @@
1
- {
2
- "$schema": "https://launchframe.dev/schema/launchframe.config.json",
3
- "url": "https://vercel.com/",
4
- "idea": "saas-idea",
5
- "createdAt": "2026-05-15T00:27:49.833Z",
6
- "launchframeVersion": "0.2.6",
7
- "notes": [
8
- "The /clone-website skill reads this file at the start of every run.",
9
- "After scaffold: open this folder in Cursor (or your AI editor) and say **Build it** — same workflow.",
10
- "`url` is the visual source-of-truth (clone its layout, spacing, tokens, motion).",
11
- "`idea` is the rebranding directive applied AFTER the pixel-perfect clone.",
12
- "Edit either field and re-invoke the skill to re-run."
13
- ]
14
- }
File without changes
File without changes
File without changes
@@ -1,396 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Playwright recon when Browser / Chrome DevTools MCP is unavailable or broken.
4
- *
5
- * Reads URL from launchframe.config.json (or --url). Writes:
6
- * - docs/design-references/playwright-<host>-<w>px.png (full page)
7
- * - docs/research/computed-snapshot.json (styles + merged asset inventory + bot-wall hint)
8
- * - docs/research/MEDIA_MANIFEST.md (table of discovered media URLs)
9
- *
10
- * Usage:
11
- * npm run recon
12
- * npm run recon:headed
13
- * node scripts/recon-playwright.mjs --url https://example.com --headed
14
- *
15
- * First time: npx playwright install chromium
16
- */
17
-
18
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
19
- import { dirname, join } from "node:path";
20
- import { fileURLToPath } from "node:url";
21
-
22
- const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
23
-
24
- function parseArgs(argv) {
25
- const out = { url: null, headed: false, channel: process.env.PW_CHANNEL || undefined };
26
- for (let i = 0; i < argv.length; i++) {
27
- const a = argv[i];
28
- if (a === "--headed" || a === "-H") out.headed = true;
29
- else if (a === "--url" || a === "-u") out.url = argv[++i];
30
- else if (a.startsWith("--url=")) out.url = a.slice(6);
31
- else if (a === "--channel" || a === "-c") out.channel = argv[++i] || undefined;
32
- }
33
- return out;
34
- }
35
-
36
- function readLaunchframeUrl() {
37
- const p = join(ROOT, "launchframe.config.json");
38
- if (!existsSync(p)) return null;
39
- try {
40
- const j = JSON.parse(readFileSync(p, "utf8"));
41
- return j.url || null;
42
- } catch {
43
- return null;
44
- }
45
- }
46
-
47
- function safeHost(url) {
48
- try {
49
- return new URL(url).hostname.replace(/[^a-z0-9.-]+/gi, "_");
50
- } catch {
51
- return "unknown-host";
52
- }
53
- }
54
-
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
- */
59
- async function scrollFullPage(page) {
60
- await page.evaluate(async () => {
61
- const delay = (ms) => new Promise((r) => setTimeout(r, ms));
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;
91
- }
92
- });
93
- }
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
-
136
- async function gatherPageData(page) {
137
- return page.evaluate(() => {
138
- const title = document.title || "";
139
- const text = (document.body?.innerText || "").slice(0, 80000);
140
- const likelyBotWall =
141
- /just a moment/i.test(title) ||
142
- /checking your browser/i.test(text) ||
143
- /cf-browser-verification/i.test(document.documentElement?.innerHTML || "") ||
144
- document.querySelector("#challenge-running, .cf-browser-verification, #cf-challenge-running") !== null;
145
-
146
- const imgs = [...document.querySelectorAll("img")].map((img) => ({
147
- src: img.currentSrc || img.src || "",
148
- srcset: img.srcset || "",
149
- alt: img.alt || "",
150
- w: img.naturalWidth,
151
- h: img.naturalHeight,
152
- }));
153
-
154
- const videos = [...document.querySelectorAll("video")].map((v) => ({
155
- src: v.currentSrc || v.src || "",
156
- poster: v.poster || "",
157
- autoplay: v.autoplay,
158
- loop: v.loop,
159
- muted: v.muted,
160
- }));
161
-
162
- const bgUrls = [];
163
- const seen = new Set();
164
- for (const el of document.querySelectorAll("*")) {
165
- if (bgUrls.length > 400) break;
166
- const bg = getComputedStyle(el).backgroundImage;
167
- if (!bg || bg === "none") continue;
168
- const m = /url\(["']?([^"')]+)["']?\)/.exec(bg);
169
- if (m && m[1] && !seen.has(m[1])) {
170
- seen.add(m[1]);
171
- bgUrls.push({ url: m[1], tag: el.tagName.toLowerCase() });
172
- }
173
- }
174
-
175
- const props = [
176
- "fontSize",
177
- "fontWeight",
178
- "fontFamily",
179
- "lineHeight",
180
- "letterSpacing",
181
- "color",
182
- "backgroundColor",
183
- "padding",
184
- "margin",
185
- "maxWidth",
186
- "display",
187
- "gap",
188
- "borderRadius",
189
- "boxShadow",
190
- ];
191
-
192
- function pickStyles(el) {
193
- if (!el) return null;
194
- const cs = getComputedStyle(el);
195
- const o = {};
196
- for (const p of props) o[p] = cs[p];
197
- return o;
198
- }
199
-
200
- const landmarks = {
201
- html: pickStyles(document.documentElement),
202
- body: pickStyles(document.body),
203
- h1: pickStyles(document.querySelector("h1")),
204
- header: pickStyles(document.querySelector("header")),
205
- main: pickStyles(document.querySelector("main")),
206
- firstSection: pickStyles(document.querySelector("main section, section")),
207
- };
208
-
209
- return {
210
- title,
211
- likelyBotWall,
212
- textSample: text.slice(0, 24000),
213
- assetInventory: {
214
- images: imgs.filter((i) => i.src),
215
- videos,
216
- backgroundImages: bgUrls,
217
- svgCount: document.querySelectorAll("svg").length,
218
- },
219
- computedLandmarks: landmarks,
220
- };
221
- });
222
- }
223
-
224
- function writeMediaManifest(host, data, limitationsPath) {
225
- const { images, videos, backgroundImages } = data.assetInventory;
226
- const lines = [
227
- "# MEDIA_MANIFEST (Playwright)",
228
- "",
229
- `Host: **${host}**`,
230
- "",
231
- "## Images (`<img>`)",
232
- "",
233
- "| src | alt | natural |",
234
- "|-----|-----|---------|",
235
- ];
236
- for (const i of images.slice(0, 200)) {
237
- const s = i.src.replace(/\|/g, "\\|");
238
- lines.push(`| ${s} | ${(i.alt || "").replace(/\|/g, "\\|")} | ${i.w}×${i.h} |`);
239
- }
240
- if (images.length > 200) lines.push(`\n_…and ${images.length - 200} more_\n`);
241
-
242
- lines.push("", "## Video", "", "| src | poster |", "|-----|--------|");
243
- for (const v of videos) {
244
- lines.push(`| ${(v.src || "").replace(/\|/g, "\\|")} | ${(v.poster || "").replace(/\|/g, "\\|")} |`);
245
- }
246
-
247
- lines.push("", "## Background images (sample)", "", "| url |", "|-----|");
248
- for (const b of backgroundImages.slice(0, 150)) {
249
- lines.push(`| ${b.url.replace(/\|/g, "\\|")} |`);
250
- }
251
-
252
- lines.push("", "---", "", `_Generated by scripts/recon-playwright.mjs. Download with scripts/download-assets.mjs when ready._`);
253
- if (limitationsPath) lines.push("", `See also: **${limitationsPath}**`);
254
-
255
- writeFileSync(join(ROOT, "docs", "research", "MEDIA_MANIFEST.md"), lines.join("\n") + "\n", "utf8");
256
- }
257
-
258
- function writeLimitations(host, url, finalUrl, data) {
259
- const p = join(ROOT, "docs", "research", "EXTRACTION_LIMITATIONS.md");
260
- const lines = [
261
- "# Extraction limitations",
262
- "",
263
- `- **Requested URL:** ${url}`,
264
- `- **Final URL:** ${finalUrl}`,
265
- `- **Host:** ${host}`,
266
- `- **Captured at:** ${new Date().toISOString()}`,
267
- `- **Tool:** Playwright (\`npm run recon\`)`,
268
- "",
269
- ];
270
- if (data.likelyBotWall) {
271
- lines.push(
272
- "## Bot / challenge page suspected",
273
- "",
274
- "Title or DOM matches common WAF/interstitial patterns. **Computed styles and screenshots may not reflect the real marketing page.** Prefer \`npm run recon:headed\` with real Chrome, or open the site manually and export assets.",
275
- ""
276
- );
277
- } else {
278
- lines.push(
279
- "## Notes",
280
- "",
281
- "Snapshot-derived data is from Playwright Chromium, not interactive MCP. Re-run after layout shifts; verify against a real browser for pixel QA.",
282
- ""
283
- );
284
- }
285
- writeFileSync(p, lines.join("\n"), "utf8");
286
- return "docs/research/EXTRACTION_LIMITATIONS.md";
287
- }
288
-
289
- async function main() {
290
- const args = parseArgs(process.argv.slice(2));
291
- const url = args.url || readLaunchframeUrl();
292
- if (!url) {
293
- console.error("No URL: set launchframe.config.json or pass --url https://...");
294
- process.exit(1);
295
- }
296
-
297
- let chromium;
298
- try {
299
- ({ chromium } = await import("playwright"));
300
- } catch {
301
- console.error("Install Playwright: npm i -D playwright && npx playwright install chromium");
302
- process.exit(1);
303
- }
304
-
305
- const host = safeHost(url);
306
- mkdirSync(join(ROOT, "docs", "design-references"), { recursive: true });
307
- mkdirSync(join(ROOT, "docs", "research"), { recursive: true });
308
-
309
- const browser = await chromium.launch({
310
- headless: !args.headed,
311
- channel: args.channel,
312
- });
313
-
314
- const context = await browser.newContext({
315
- userAgent:
316
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
317
- locale: "en-US",
318
- });
319
-
320
- const page = await context.newPage();
321
-
322
- const snapshot = {
323
- fetchedAt: new Date().toISOString(),
324
- requestedUrl: url,
325
- finalUrl: null,
326
- viewports: {},
327
- title: null,
328
- likelyBotWall: false,
329
- assetInventory: null,
330
- computedLandmarks: null,
331
- textSample: null,
332
- };
333
-
334
- try {
335
- await page.goto(url, { waitUntil: "domcontentloaded", timeout: 90_000 });
336
- await new Promise((r) => setTimeout(r, 2500));
337
- snapshot.finalUrl = page.url();
338
-
339
- let mergedInventory = null;
340
- let desktopLandmarks = null;
341
-
342
- for (const w of [1440, 390]) {
343
- await page.setViewportSize({ width: w, height: w === 1440 ? 900 : 844 });
344
- await scrollFullPage(page);
345
-
346
- const shotPath = join(ROOT, "docs", "design-references", `playwright-${host}-${w}px.png`);
347
- await page.screenshot({ path: shotPath, fullPage: true });
348
- snapshot.viewports[w] = { screenshot: `docs/design-references/playwright-${host}-${w}px.png` };
349
-
350
- const data = await gatherPageData(page);
351
-
352
- if (w === 1440) {
353
- snapshot.title = data.title;
354
- snapshot.likelyBotWall = data.likelyBotWall;
355
- mergedInventory = data.assetInventory;
356
- desktopLandmarks = data.computedLandmarks;
357
- snapshot.textSample = data.textSample;
358
- } else {
359
- mergedInventory = mergeAssetInventory(mergedInventory, data.assetInventory);
360
- }
361
- }
362
-
363
- snapshot.assetInventory = mergedInventory;
364
- snapshot.computedLandmarks = desktopLandmarks;
365
- } finally {
366
- await browser.close();
367
- }
368
-
369
- const limRel = writeLimitations(host, url, snapshot.finalUrl, {
370
- likelyBotWall: snapshot.likelyBotWall,
371
- });
372
-
373
- writeFileSync(
374
- join(ROOT, "docs", "research", "computed-snapshot.json"),
375
- JSON.stringify(snapshot, null, 2) + "\n",
376
- "utf8"
377
- );
378
-
379
- if (snapshot.assetInventory) {
380
- writeMediaManifest(host, { assetInventory: snapshot.assetInventory }, limRel);
381
- }
382
-
383
- console.log("Playwright recon finished.");
384
- console.log(` Screenshots: docs/design-references/playwright-${host}-1440px.png (+ 390px)`);
385
- console.log(` Snapshot: docs/research/computed-snapshot.json`);
386
- console.log(` Media list: docs/research/MEDIA_MANIFEST.md`);
387
- console.log(` Limits: ${limRel}`);
388
- if (snapshot.likelyBotWall) {
389
- console.warn("\n⚠ Possible bot wall / challenge page — check EXTRACTION_LIMITATIONS.md and try: npm run recon:headed\n");
390
- }
391
- }
392
-
393
- main().catch((e) => {
394
- console.error(e);
395
- process.exit(1);
396
- });
@@ -1,34 +0,0 @@
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
- }