156 lines
5.1 KiB
JavaScript
156 lines
5.1 KiB
JavaScript
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
import { dirname, join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
const sourcePath = join(repoRoot, "index.html");
|
|
const templateRoot = join(repoRoot, "templates", "pages", "home");
|
|
const blockRoot = join(templateRoot, "blocks");
|
|
const contentPath = join(repoRoot, "content", "pages", "home.json");
|
|
|
|
const html = await readFile(sourcePath, "utf8");
|
|
|
|
const marker = {
|
|
hero: '<header id="hero" class="s h-screen">',
|
|
features: '<section id="features" class="s h-screen py mobile-flip">',
|
|
cViz: '<div class="c-viz">',
|
|
pricingIntro: '<section id="waitlist" class="s">',
|
|
waitlistFiles: '<section id="waitlist" class="s h-screen">',
|
|
demoVideoFolder: '<div data-module="folder-wrap" class="folder-w video-w">',
|
|
footer: '<footer class="s footer">',
|
|
};
|
|
|
|
function findRequired(needle, from = 0) {
|
|
const index = html.indexOf(needle, from);
|
|
if (index === -1) {
|
|
throw new Error(`Could not find marker: ${needle}`);
|
|
}
|
|
return index;
|
|
}
|
|
|
|
const heroStart = findRequired(marker.hero);
|
|
const featuresStart = findRequired(marker.features, heroStart);
|
|
const cVizStart = findRequired(marker.cViz, featuresStart);
|
|
const cVizOpenEnd = cVizStart + marker.cViz.length;
|
|
const pricingIntroStart = findRequired(marker.pricingIntro, cVizOpenEnd);
|
|
const waitlistFilesStart = findRequired(marker.waitlistFiles, pricingIntroStart);
|
|
const demoVideoFolderStart = findRequired(marker.demoVideoFolder, waitlistFilesStart);
|
|
const footerStart = findRequired(marker.footer, demoVideoFolderStart);
|
|
const footerEnd = findRequired("</footer>", footerStart) + "</footer>".length;
|
|
|
|
const shell = [
|
|
html.slice(0, heroStart),
|
|
"{{NODEDC_BLOCKS_BEFORE_CVIZ}}",
|
|
html.slice(cVizStart, cVizOpenEnd),
|
|
"{{NODEDC_BLOCKS_CVIZ}}",
|
|
html.slice(footerEnd),
|
|
].join("");
|
|
|
|
const blocks = [
|
|
{
|
|
region: "beforeCViz",
|
|
id: "hero-waitlist",
|
|
type: "staticHtml",
|
|
template: "hero-waitlist.html",
|
|
adminLabel: "Первый экран и заявка",
|
|
anchor: "hero",
|
|
html: html.slice(heroStart, featuresStart),
|
|
},
|
|
{
|
|
region: "beforeCViz",
|
|
id: "feature-video",
|
|
type: "staticHtml",
|
|
template: "feature-video.html",
|
|
adminLabel: "Демо и базовая логика",
|
|
anchor: "features",
|
|
html: html.slice(featuresStart, cVizStart),
|
|
},
|
|
{
|
|
region: "cViz",
|
|
id: "product-system",
|
|
type: "staticHtml",
|
|
template: "product-system.html",
|
|
adminLabel: "Компоненты, режимы, FAQ и тарифная таблица",
|
|
anchor: null,
|
|
html: html.slice(cVizOpenEnd, pricingIntroStart),
|
|
},
|
|
{
|
|
region: "cViz",
|
|
id: "pricing-intro",
|
|
type: "staticHtml",
|
|
template: "pricing-intro.html",
|
|
adminLabel: "Интро тарифов",
|
|
anchor: "waitlist",
|
|
html: html.slice(pricingIntroStart, waitlistFilesStart),
|
|
},
|
|
{
|
|
region: "cViz",
|
|
id: "waitlist-files",
|
|
type: "staticHtml",
|
|
template: "waitlist-files.html",
|
|
adminLabel: "Форма ожидания и txt-файлы",
|
|
anchor: "waitlist",
|
|
html: html.slice(waitlistFilesStart, demoVideoFolderStart),
|
|
},
|
|
{
|
|
region: "cViz",
|
|
id: "demo-video-folder",
|
|
type: "staticHtml",
|
|
template: "demo-video-folder.html",
|
|
adminLabel: "Иконка демо-видео в доке",
|
|
anchor: null,
|
|
html: html.slice(demoVideoFolderStart, footerStart),
|
|
},
|
|
{
|
|
region: "cViz",
|
|
id: "footer",
|
|
type: "staticHtml",
|
|
template: "footer.html",
|
|
adminLabel: "Футер",
|
|
anchor: null,
|
|
html: html.slice(footerStart, footerEnd),
|
|
},
|
|
];
|
|
|
|
const page = {
|
|
schemaVersion: 1,
|
|
slug: "home",
|
|
output: "index.html",
|
|
title: "NODE.DC",
|
|
seo: {
|
|
title: "NODE.DC — ИИ-платформа для Webflow",
|
|
description:
|
|
"Единая платформа для кастомного кода, MCP и ИИ-агентов в Webflow: контекст проекта, автоматизация, деплой и управляемая разработка.",
|
|
},
|
|
regions: [
|
|
{
|
|
id: "beforeCViz",
|
|
label: "До c-viz wrapper",
|
|
description: "Hero and first product section. These blocks live before the visual wrapper.",
|
|
blocks: blocks
|
|
.filter((block) => block.region === "beforeCViz")
|
|
.map(({ html: _html, ...block }) => ({ ...block, enabled: true })),
|
|
},
|
|
{
|
|
id: "cViz",
|
|
label: "Inside c-viz wrapper",
|
|
description: "Sections coupled to WebGL/color-flip shell. Keep these in this region for now.",
|
|
blocks: blocks
|
|
.filter((block) => block.region === "cViz")
|
|
.map(({ html: _html, ...block }) => ({ ...block, enabled: true })),
|
|
},
|
|
],
|
|
};
|
|
|
|
await mkdir(blockRoot, { recursive: true });
|
|
await mkdir(dirname(contentPath), { recursive: true });
|
|
await writeFile(join(templateRoot, "shell.html"), shell);
|
|
for (const block of blocks) {
|
|
await writeFile(join(blockRoot, block.template), block.html);
|
|
}
|
|
await writeFile(contentPath, `${JSON.stringify(page, null, 2)}\n`);
|
|
|
|
console.log(`Extracted ${blocks.length} blocks from ${sourcePath}`);
|
|
console.log(`Wrote ${join(templateRoot, "shell.html")}`);
|
|
console.log(`Wrote ${contentPath}`);
|