launchframe 0.2.3 → 0.2.5
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/README.md +147 -147
- package/bin/launchframe.mjs +315 -315
- package/package.json +1 -1
- package/template/.amazonq/cli-agents/clone-website.json +1 -1
- package/template/.amazonq/rules/project.md +180 -109
- package/template/.augment/commands/clone-website.md +33 -3
- package/template/.claude/skills/clone-website/SKILL.md +564 -534
- package/template/.claude/skills/marketing-landing-production/SKILL.md +36 -0
- package/template/.clinerules +180 -109
- package/template/.codex/skills/clone-website/SKILL.md +33 -3
- package/template/.continue/commands/clone-website.md +33 -3
- package/template/.continue/rules/project.md +180 -109
- package/template/.cursor/commands/clone-website.md +33 -3
- package/template/.cursor/commands/marketing-landing-production.md +31 -0
- package/template/.cursor/rules/project.mdc +25 -20
- package/template/.gemini/commands/clone-website.toml +33 -3
- package/template/.github/copilot-instructions.md +180 -109
- package/template/.github/skills/clone-website/SKILL.md +33 -3
- package/template/.opencode/commands/clone-website.md +33 -3
- package/template/.windsurf/workflows/clone-website.md +33 -3
- package/template/AGENTS.md +148 -89
- package/template/README.md +121 -120
- package/template/START_HERE.md +15 -15
- package/template/docs/design-references/playwright-example.com-1440px.png +0 -0
- package/template/docs/design-references/playwright-example.com-390px.png +0 -0
- package/template/docs/research/INSPECTION_GUIDE.md +121 -109
- package/template/package.json +63 -60
- package/template/scripts/recon-playwright.mjs +323 -0
- package/template/src/app/globals.css +93 -1
- package/template/src/app/layout.tsx +3 -2
- package/template/src/app/page.tsx +3 -7
|
@@ -0,0 +1,323 @@
|
|
|
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 + 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
|
+
/** Scroll to bottom slowly to trigger lazy-loaded media */
|
|
56
|
+
async function scrollFullPage(page) {
|
|
57
|
+
await page.evaluate(async () => {
|
|
58
|
+
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);
|
|
66
|
+
}
|
|
67
|
+
window.scrollTo(0, 0);
|
|
68
|
+
await delay(300);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function gatherPageData(page) {
|
|
73
|
+
return page.evaluate(() => {
|
|
74
|
+
const title = document.title || "";
|
|
75
|
+
const text = (document.body?.innerText || "").slice(0, 5000);
|
|
76
|
+
const likelyBotWall =
|
|
77
|
+
/just a moment/i.test(title) ||
|
|
78
|
+
/checking your browser/i.test(text) ||
|
|
79
|
+
/cf-browser-verification/i.test(document.documentElement?.innerHTML || "") ||
|
|
80
|
+
document.querySelector("#challenge-running, .cf-browser-verification, #cf-challenge-running") !== null;
|
|
81
|
+
|
|
82
|
+
const imgs = [...document.querySelectorAll("img")].map((img) => ({
|
|
83
|
+
src: img.currentSrc || img.src || "",
|
|
84
|
+
srcset: img.srcset || "",
|
|
85
|
+
alt: img.alt || "",
|
|
86
|
+
w: img.naturalWidth,
|
|
87
|
+
h: img.naturalHeight,
|
|
88
|
+
}));
|
|
89
|
+
|
|
90
|
+
const videos = [...document.querySelectorAll("video")].map((v) => ({
|
|
91
|
+
src: v.currentSrc || v.src || "",
|
|
92
|
+
poster: v.poster || "",
|
|
93
|
+
autoplay: v.autoplay,
|
|
94
|
+
loop: v.loop,
|
|
95
|
+
muted: v.muted,
|
|
96
|
+
}));
|
|
97
|
+
|
|
98
|
+
const bgUrls = [];
|
|
99
|
+
const seen = new Set();
|
|
100
|
+
for (const el of document.querySelectorAll("*")) {
|
|
101
|
+
if (bgUrls.length > 400) break;
|
|
102
|
+
const bg = getComputedStyle(el).backgroundImage;
|
|
103
|
+
if (!bg || bg === "none") continue;
|
|
104
|
+
const m = /url\(["']?([^"')]+)["']?\)/.exec(bg);
|
|
105
|
+
if (m && m[1] && !seen.has(m[1])) {
|
|
106
|
+
seen.add(m[1]);
|
|
107
|
+
bgUrls.push({ url: m[1], tag: el.tagName.toLowerCase() });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const props = [
|
|
112
|
+
"fontSize",
|
|
113
|
+
"fontWeight",
|
|
114
|
+
"fontFamily",
|
|
115
|
+
"lineHeight",
|
|
116
|
+
"letterSpacing",
|
|
117
|
+
"color",
|
|
118
|
+
"backgroundColor",
|
|
119
|
+
"padding",
|
|
120
|
+
"margin",
|
|
121
|
+
"maxWidth",
|
|
122
|
+
"display",
|
|
123
|
+
"gap",
|
|
124
|
+
"borderRadius",
|
|
125
|
+
"boxShadow",
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
function pickStyles(el) {
|
|
129
|
+
if (!el) return null;
|
|
130
|
+
const cs = getComputedStyle(el);
|
|
131
|
+
const o = {};
|
|
132
|
+
for (const p of props) o[p] = cs[p];
|
|
133
|
+
return o;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const landmarks = {
|
|
137
|
+
html: pickStyles(document.documentElement),
|
|
138
|
+
body: pickStyles(document.body),
|
|
139
|
+
h1: pickStyles(document.querySelector("h1")),
|
|
140
|
+
header: pickStyles(document.querySelector("header")),
|
|
141
|
+
main: pickStyles(document.querySelector("main")),
|
|
142
|
+
firstSection: pickStyles(document.querySelector("main section, section")),
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
title,
|
|
147
|
+
likelyBotWall,
|
|
148
|
+
textSample: text.slice(0, 1200),
|
|
149
|
+
assetInventory: {
|
|
150
|
+
images: imgs.filter((i) => i.src),
|
|
151
|
+
videos,
|
|
152
|
+
backgroundImages: bgUrls,
|
|
153
|
+
svgCount: document.querySelectorAll("svg").length,
|
|
154
|
+
},
|
|
155
|
+
computedLandmarks: landmarks,
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function writeMediaManifest(host, data, limitationsPath) {
|
|
161
|
+
const { images, videos, backgroundImages } = data.assetInventory;
|
|
162
|
+
const lines = [
|
|
163
|
+
"# MEDIA_MANIFEST (Playwright)",
|
|
164
|
+
"",
|
|
165
|
+
`Host: **${host}**`,
|
|
166
|
+
"",
|
|
167
|
+
"## Images (`<img>`)",
|
|
168
|
+
"",
|
|
169
|
+
"| src | alt | natural |",
|
|
170
|
+
"|-----|-----|---------|",
|
|
171
|
+
];
|
|
172
|
+
for (const i of images.slice(0, 200)) {
|
|
173
|
+
const s = i.src.replace(/\|/g, "\\|");
|
|
174
|
+
lines.push(`| ${s} | ${(i.alt || "").replace(/\|/g, "\\|")} | ${i.w}×${i.h} |`);
|
|
175
|
+
}
|
|
176
|
+
if (images.length > 200) lines.push(`\n_…and ${images.length - 200} more_\n`);
|
|
177
|
+
|
|
178
|
+
lines.push("", "## Video", "", "| src | poster |", "|-----|--------|");
|
|
179
|
+
for (const v of videos) {
|
|
180
|
+
lines.push(`| ${(v.src || "").replace(/\|/g, "\\|")} | ${(v.poster || "").replace(/\|/g, "\\|")} |`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
lines.push("", "## Background images (sample)", "", "| url |", "|-----|");
|
|
184
|
+
for (const b of backgroundImages.slice(0, 150)) {
|
|
185
|
+
lines.push(`| ${b.url.replace(/\|/g, "\\|")} |`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
lines.push("", "---", "", `_Generated by scripts/recon-playwright.mjs. Download with scripts/download-assets.mjs when ready._`);
|
|
189
|
+
if (limitationsPath) lines.push("", `See also: **${limitationsPath}**`);
|
|
190
|
+
|
|
191
|
+
writeFileSync(join(ROOT, "docs", "research", "MEDIA_MANIFEST.md"), lines.join("\n") + "\n", "utf8");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function writeLimitations(host, url, finalUrl, data) {
|
|
195
|
+
const p = join(ROOT, "docs", "research", "EXTRACTION_LIMITATIONS.md");
|
|
196
|
+
const lines = [
|
|
197
|
+
"# Extraction limitations",
|
|
198
|
+
"",
|
|
199
|
+
`- **Requested URL:** ${url}`,
|
|
200
|
+
`- **Final URL:** ${finalUrl}`,
|
|
201
|
+
`- **Host:** ${host}`,
|
|
202
|
+
`- **Captured at:** ${new Date().toISOString()}`,
|
|
203
|
+
`- **Tool:** Playwright (\`npm run recon\`)`,
|
|
204
|
+
"",
|
|
205
|
+
];
|
|
206
|
+
if (data.likelyBotWall) {
|
|
207
|
+
lines.push(
|
|
208
|
+
"## Bot / challenge page suspected",
|
|
209
|
+
"",
|
|
210
|
+
"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.",
|
|
211
|
+
""
|
|
212
|
+
);
|
|
213
|
+
} else {
|
|
214
|
+
lines.push(
|
|
215
|
+
"## Notes",
|
|
216
|
+
"",
|
|
217
|
+
"Snapshot-derived data is from Playwright Chromium, not interactive MCP. Re-run after layout shifts; verify against a real browser for pixel QA.",
|
|
218
|
+
""
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
writeFileSync(p, lines.join("\n"), "utf8");
|
|
222
|
+
return "docs/research/EXTRACTION_LIMITATIONS.md";
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function main() {
|
|
226
|
+
const args = parseArgs(process.argv.slice(2));
|
|
227
|
+
const url = args.url || readLaunchframeUrl();
|
|
228
|
+
if (!url) {
|
|
229
|
+
console.error("No URL: set launchframe.config.json or pass --url https://...");
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
let chromium;
|
|
234
|
+
try {
|
|
235
|
+
({ chromium } = await import("playwright"));
|
|
236
|
+
} catch {
|
|
237
|
+
console.error("Install Playwright: npm i -D playwright && npx playwright install chromium");
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const host = safeHost(url);
|
|
242
|
+
mkdirSync(join(ROOT, "docs", "design-references"), { recursive: true });
|
|
243
|
+
mkdirSync(join(ROOT, "docs", "research"), { recursive: true });
|
|
244
|
+
|
|
245
|
+
const browser = await chromium.launch({
|
|
246
|
+
headless: !args.headed,
|
|
247
|
+
channel: args.channel,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const context = await browser.newContext({
|
|
251
|
+
userAgent:
|
|
252
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
|
253
|
+
locale: "en-US",
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const page = await context.newPage();
|
|
257
|
+
|
|
258
|
+
const snapshot = {
|
|
259
|
+
fetchedAt: new Date().toISOString(),
|
|
260
|
+
requestedUrl: url,
|
|
261
|
+
finalUrl: null,
|
|
262
|
+
viewports: {},
|
|
263
|
+
title: null,
|
|
264
|
+
likelyBotWall: false,
|
|
265
|
+
assetInventory: null,
|
|
266
|
+
computedLandmarks: null,
|
|
267
|
+
textSample: null,
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 90_000 });
|
|
272
|
+
await new Promise((r) => setTimeout(r, 2500));
|
|
273
|
+
snapshot.finalUrl = page.url();
|
|
274
|
+
|
|
275
|
+
for (const w of [1440, 390]) {
|
|
276
|
+
await page.setViewportSize({ width: w, height: w === 1440 ? 900 : 844 });
|
|
277
|
+
if (w === 1440) await scrollFullPage(page);
|
|
278
|
+
|
|
279
|
+
const shotPath = join(ROOT, "docs", "design-references", `playwright-${host}-${w}px.png`);
|
|
280
|
+
await page.screenshot({ path: shotPath, fullPage: true });
|
|
281
|
+
snapshot.viewports[w] = { screenshot: `docs/design-references/playwright-${host}-${w}px.png` };
|
|
282
|
+
|
|
283
|
+
if (w === 1440) {
|
|
284
|
+
const data = await gatherPageData(page);
|
|
285
|
+
snapshot.title = data.title;
|
|
286
|
+
snapshot.likelyBotWall = data.likelyBotWall;
|
|
287
|
+
snapshot.assetInventory = data.assetInventory;
|
|
288
|
+
snapshot.computedLandmarks = data.computedLandmarks;
|
|
289
|
+
snapshot.textSample = data.textSample;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
} finally {
|
|
293
|
+
await browser.close();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const limRel = writeLimitations(host, url, snapshot.finalUrl, {
|
|
297
|
+
likelyBotWall: snapshot.likelyBotWall,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
writeFileSync(
|
|
301
|
+
join(ROOT, "docs", "research", "computed-snapshot.json"),
|
|
302
|
+
JSON.stringify(snapshot, null, 2) + "\n",
|
|
303
|
+
"utf8"
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
if (snapshot.assetInventory) {
|
|
307
|
+
writeMediaManifest(host, { assetInventory: snapshot.assetInventory }, limRel);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
console.log("Playwright recon finished.");
|
|
311
|
+
console.log(` Screenshots: docs/design-references/playwright-${host}-1440px.png (+ 390px)`);
|
|
312
|
+
console.log(` Snapshot: docs/research/computed-snapshot.json`);
|
|
313
|
+
console.log(` Media list: docs/research/MEDIA_MANIFEST.md`);
|
|
314
|
+
console.log(` Limits: ${limRel}`);
|
|
315
|
+
if (snapshot.likelyBotWall) {
|
|
316
|
+
console.warn("\n⚠ Possible bot wall / challenge page — check EXTRACTION_LIMITATIONS.md and try: npm run recon:headed\n");
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
main().catch((e) => {
|
|
321
|
+
console.error(e);
|
|
322
|
+
process.exit(1);
|
|
323
|
+
});
|
|
@@ -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
|
}
|