launchframe 0.2.5 → 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.
@@ -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
- /** Scroll to bottom slowly to trigger lazy-loaded media */
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 step = Math.max(1, Math.floor(window.innerHeight * 0.85));
60
- let y = 0;
61
- const max = document.documentElement.scrollHeight;
62
- while (y < max) {
63
- y = Math.min(y + step, max);
64
- window.scrollTo(0, y);
65
- await delay(200);
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, 5000);
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, 1200),
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
- if (w === 1440) await scrollFullPage(page);
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
- snapshot.assetInventory = data.assetInventory;
288
- snapshot.computedLandmarks = data.computedLandmarks;
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
  }
@@ -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
+ }
@@ -1,36 +0,0 @@
1
- ---
2
- name: marketing-landing-production
3
- description: Elevate an authored or minimalist marketing landing to production quality — layered SVG illustration, pixel-art accents, choreographed motion (Framer Motion tiers A–E), accent tokens, OG/favicon cohesion. Use when the user asks for prod polish, more motion, stronger branding, SVG scenes, or pixel art on `src/app/page.tsx` / `src/components/marketing/**`.
4
- argument-hint: "[optional paths or sections to prioritize]"
5
- user-invocable: true
6
- ---
7
-
8
- # Marketing Landing — Production Polish
9
-
10
- You are upgrading a **marketing landing** so it feels shipped — not “fine for a demo.” Read **`AGENTS.md` → Production polish for marketing landings** first; this skill is the executable checklist.
11
-
12
- ## When to run
13
-
14
- - Clone/rebrand finished but the page still reads **flat** (mostly black text on white + gray placeholders).
15
- - User explicitly wants **more images**, **motion**, **SVG / illustration**, **pixel art**, **brandability**, **production-ready**.
16
- - Feature cards use **off-topic filler art** — replace with scenes tied to copy.
17
-
18
- ## Hard requirements
19
-
20
- 1. **`framer-motion`** — staggered hero load (**tier A**), scroll reveals + nested staggers (**tier B**), at least **one** ambient loop (**tier C**: marquee, typing caret, SVG dash drift, gradient pulse — pick what fits brand).
21
- 2. **Interaction polish (tier D)** — measurable hover/tap on primary surfaces (buttons, cards): subtle lift, shadow ramp, scale ≤ 1.02.
22
- 3. **Decorative depth (tier E, optional)** — light pointer parallax **or** scroll-linked transforms; keep translation ≤ ~8px and rotation ≤ ~2deg.
23
- 4. **`useReducedMotion()`** — disable or simplify looping motion and shorten entrances when the user prefers reduced motion.
24
- 5. **SVG illustration layers** — dedicated React components (prefer `src/components/marketing/art/`); use **`currentColor`** where possible so theme tokens drive strokes/fills.
25
- 6. **Pixel art** — if requested: declare palette (hex table in component comment), crisp scaling (`imageRendering: "pixelated"` for intentional upscale), consistent grid logic.
26
- 7. **Brand spine** — motif repeats once in hero + once in a card or footer glyph cluster; define **accent CSS variables** if the page is only monochrome gray today.
27
- 8. **Artifacts** — write **`docs/research/PRODUCTION_UPLIFT.md`**: bullets for SVG modules added, motion tiers shipped, accent tokens, OG/favicon edits.
28
-
29
- ## Verification
30
-
31
- - `npm run build` passes.
32
- - Lighthouse/visual sanity: no layout shift explosions from animations; loops are subtle at default amplitude.
33
-
34
- ## Out of scope unless asked
35
-
36
- Backend, CMS wiring, analytics, full accessibility audit beyond motion preferences.
@@ -1,31 +0,0 @@
1
- <!-- Mirrors .claude/skills/marketing-landing-production/SKILL.md — edit skill first, then sync this body if drift occurs. -->
2
-
3
- # Marketing Landing — Production Polish
4
-
5
- You are upgrading a **marketing landing** so it feels shipped — not “fine for a demo.” Read **`AGENTS.md` → Production polish for marketing landings** first; treat this command as the executable checklist.
6
-
7
- ## When to run
8
-
9
- - Clone/rebrand finished but the page still reads **flat** (mostly black text on white + gray placeholders).
10
- - User explicitly wants **more images**, **motion**, **SVG / illustration**, **pixel art**, **brandability**, **production-ready**.
11
- - Feature cards use **off-topic filler art** — replace with scenes tied to copy.
12
-
13
- ## Hard requirements
14
-
15
- 1. **`framer-motion`** — staggered hero load (**tier A**), scroll reveals + nested staggers (**tier B**), at least **one** ambient loop (**tier C**: marquee, typing caret, SVG dash drift, gradient pulse — pick what fits brand).
16
- 2. **Interaction polish (tier D)** — measurable hover/tap on primary surfaces (buttons, cards): subtle lift, shadow ramp, scale ≤ 1.02.
17
- 3. **Decorative depth (tier E, optional)** — light pointer parallax **or** scroll-linked transforms; keep translation ≤ ~8px and rotation ≤ ~2deg.
18
- 4. **`useReducedMotion()`** — disable or simplify looping motion and shorten entrances when the user prefers reduced motion.
19
- 5. **SVG illustration layers** — dedicated React components (prefer `src/components/marketing/art/`); use **`currentColor`** where possible so theme tokens drive strokes/fills.
20
- 6. **Pixel art** — if requested: declare palette (hex table in component comment), crisp scaling (`imageRendering: "pixelated"` for intentional upscale), consistent grid logic.
21
- 7. **Brand spine** — motif repeats once in hero + once in a card or footer glyph cluster; define **accent CSS variables** if the page is only monochrome gray today.
22
- 8. **Artifacts** — write **`docs/research/PRODUCTION_UPLIFT.md`**: bullets for SVG modules added, motion tiers shipped, accent tokens, OG/favicon edits.
23
-
24
- ## Verification
25
-
26
- - `npm run build` passes.
27
- - Lighthouse/visual sanity: no layout shift explosions from animations; loops are subtle at default amplitude.
28
-
29
- ## Out of scope unless asked
30
-
31
- Backend, CMS wiring, analytics, full accessibility audit beyond motion preferences.