SEO zero ready

This commit is contained in:
dcconstructions 2026-06-27 19:20:56 +03:00
parent e9c0dd56c2
commit e1daef3872
76 changed files with 1372 additions and 4364 deletions

View File

@ -1,63 +1,27 @@
# NODE.DC Local Working Copy
# NODE.DC PromoSite
This repository contains the current NODE.DC base site: a structured local copy of the recovered Webflow page, localized to Russian and prepared for the block/CMS refactor.
The rebuild source is `/Users/dcconstructions/site-rescue/ssscript.app`.
## Run
```bash
cd /Users/dcconstructions/Downloads/mnt/NODEDC/NODEDC_SITE
python3 -m http.server 8081 --bind 127.0.0.1
```
Open http://127.0.0.1:8081/
## Admin And Render
The first refactor step keeps the Webflow markup intact, but moves the home page into a block-rendered structure:
- `content/pages/home.json` - ordered page regions and block instances.
- `templates/pages/home/shell.html` - original page shell with block slots.
- `templates/pages/home/blocks/` - exact HTML chunks extracted from the current page.
- `tools/render-home.mjs` - renders `index.html` from the content model and templates.
- `admin/` - local admin UI for block order, enable/disable, duplicate, copy/paste, and JSON edits.
`heroWaitlist` and `featureVideo` already expose typed content fields in the admin. Other blocks are still managed as HTML-template blocks until their content fields are extracted.
Run the local admin:
```bash
cd /Users/dcconstructions/Downloads/mnt/NODEDC/NODEDC_SITE
npm run admin
```
Open http://127.0.0.1:8090/admin/
Render the public page after JSON changes:
```bash
npm run render:home
```
If the current `index.html` must be re-extracted into fresh templates:
```bash
npm run extract:home
```
Static promo site for NODE.DC: an enterprise platform for processes, data, AI agents, workflow automation, Hub, Engine, Ops, and applied business modules.
## Structure
- `index.html`, `beta-apply.html`, `founders.html`, `enterprise.html` - editable local pages.
- `assets/webflow` - Webflow CSS, JS, and image assets.
- `assets/custom` - custom NODE.DC CSS/JS published on Vercel.
- `assets/media` - downloaded videos from ssscript.dev.
- `assets/vendor` - third-party browser scripts.
- `docs/` - refactor notes, block model draft, and admin plan.
- `originals` - untouched files copied from the original wget download.
- `backbase/ssscript.app` - original downloaded app bundle backup.
- `asset-manifest.json` - source URL to local file mapping.
- `tools/build-local-copy.mjs` - rebuild script. It uses `NODEDC_SOURCE_DIR` when provided, otherwise the original local rescue folder.
- `tools/localize-ru.mjs` - Russian localization and NODE.DC brand patch script.
- `content/pages/home.json` - structured content model for the homepage.
- `templates/pages/home/shell.html` - homepage shell and shared head/runtime includes.
- `templates/pages/home/blocks` - block templates used by the renderer and admin.
- `content/block-templates/home.json` - sanitized admin block presets.
- `assets/site` - local runtime CSS, JS, fonts, and base UI images.
- `assets/custom` - NODE.DC custom runtime and overrides.
- `assets/uploads` - product media managed by the site/admin flow.
- `admin` and `tools/admin-server.mjs` - local content admin.
External product/API/documentation links are intentionally left as external URLs.
## Commands
```bash
npm run render:home
npm run admin
```
For a static preview:
```bash
python3 -m http.server 8081 --bind 127.0.0.1
```

View File

@ -795,6 +795,13 @@ body[data-file-picker-active="true"] .editor {
scrollbar-color: rgba(255, 255, 255, 0.25) transparent;
}
body[data-admin-mode="seo"] #add-block,
body[data-admin-mode="seo"] .block-meta-card,
body[data-admin-mode="seo"] .structure-card,
body[data-admin-mode="seo"] .json-details {
display: none;
}
.hidden {
display: none !important;
}
@ -802,7 +809,8 @@ body[data-file-picker-active="true"] .editor {
.settings-card,
.content-panel,
.json-details,
.content-group {
.content-group,
.seo-diagnostics {
border-radius: var(--launcher-radius-card);
background: rgba(255, 255, 255, 0.045);
}
@ -930,6 +938,72 @@ body[data-file-picker-active="true"] .editor {
white-space: nowrap;
}
.seo-diagnostics {
display: grid;
gap: 1rem;
padding: 1rem;
}
.seo-metric-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 0.75rem;
}
.seo-metric {
display: grid;
gap: 0.35rem;
min-width: 0;
border-radius: 1rem;
background: rgba(255, 255, 255, 0.055);
padding: 0.82rem;
}
.seo-metric span {
overflow: hidden;
color: var(--text-muted);
font-size: 0.68rem;
font-weight: 850;
text-overflow: ellipsis;
text-transform: uppercase;
white-space: nowrap;
}
.seo-metric strong {
overflow: hidden;
color: var(--text-primary);
font-size: 0.88rem;
font-weight: 850;
line-height: 1.2;
text-overflow: ellipsis;
white-space: nowrap;
}
.seo-metric[data-status="good"] strong,
.seo-field-meter[data-status="good"] {
color: #bcf4d1;
}
.seo-metric[data-status="warn"] strong,
.seo-field-meter[data-status="warn"] {
color: #f3d28b;
}
.seo-metric[data-status="bad"] strong,
.seo-field-meter[data-status="bad"] {
color: #ffb7b7;
}
.seo-field-meter {
overflow: hidden;
color: var(--text-muted);
font-size: 0.68rem;
font-weight: 720;
text-overflow: ellipsis;
text-transform: none;
white-space: nowrap;
}
.boolean-field {
align-content: start;
}
@ -1313,7 +1387,8 @@ body[data-file-picker-active="true"] .editor {
}
input,
textarea {
textarea,
select {
width: 100%;
border: 0;
border-radius: 1rem;
@ -1323,7 +1398,8 @@ textarea {
}
input:focus,
textarea:focus {
textarea:focus,
select:focus {
background: rgba(255, 255, 255, 0.1);
box-shadow: none;
}
@ -1333,6 +1409,10 @@ textarea {
resize: vertical;
}
select {
appearance: none;
}
.toggle-row {
justify-content: flex-start;
gap: 0.65rem;
@ -2473,6 +2553,10 @@ textarea {
grid-template-columns: 1fr;
}
.seo-metric-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.media-preview-panel {
grid-template-columns: 9rem minmax(0, 1fr);
grid-template-rows: auto auto;
@ -2500,7 +2584,7 @@ textarea {
.nodedc-expanded-nav-group {
display: grid;
width: 100%;
grid-template-columns: repeat(3, minmax(0, 1fr));
grid-template-columns: repeat(2, minmax(0, 1fr));
overflow: visible;
}
@ -2519,7 +2603,8 @@ textarea {
}
.field-grid,
.group-grid {
.group-grid,
.seo-metric-grid {
grid-template-columns: 1fr;
}

View File

@ -34,7 +34,12 @@ function readLastMediaDirectory() {
return String(readAdminLayoutState().lastMediaDirectory || "").replace(/^\/+/, "");
}
function readAdminMode() {
return readAdminLayoutState().mode === "seo" ? "seo" : "content";
}
const state = {
mode: readAdminMode(),
page: null,
templates: [],
selected: null,
@ -69,7 +74,78 @@ const state = {
const SECTIONS_REGION_ID = "sections";
const SECTIONS_REGION_LABEL = "Секции";
const PAGE_SEO_DEFAULTS = {
title: "NODE.DC - платформа для процессов, данных и ИИ-агентов",
description:
"NODE.DC объединяет Hub, Engine, Ops, прикладные модули и сквозного ассистента для задач, workflow, данных, ролей, интеграций и инженерных моделей.",
canonicalUrl: "https://nodedc.dctouch.ru/",
robots: "index,follow,max-image-preview:large",
siteName: "NODE.DC",
locale: "ru_RU",
ogImage: "https://nodedc.dctouch.ru/assets/site/images/69b6acdde3123ef078e2b7f3_og-img.jpg",
imageAlt: "NODE.DC - платформа для процессов, данных и ИИ-агентов",
organizationName: "DCTOUCH",
organizationUrl: "https://dctouch.ru/",
applicationCategory: "BusinessApplication",
};
const BLOCK_SEO_DEFAULTS = {
targetCluster: "",
searchIntent: "",
primaryQuery: "",
secondaryQueries: "",
title: "",
description: "",
coverageNotes: "",
indexHint: "section",
};
const PAGE_SEO_FIELDS = [
{ path: "seo.title", label: "Title страницы", kind: "text", group: "Сниппет", min: 35, max: 70 },
{ path: "seo.description", label: "Description страницы", kind: "textarea", group: "Сниппет", min: 110, max: 170 },
{ path: "seo.canonicalUrl", label: "Canonical URL", kind: "url", group: "Индексация" },
{
path: "seo.robots",
label: "Robots meta",
kind: "select",
group: "Индексация",
options: ["index,follow,max-image-preview:large", "noindex,nofollow", "index,follow"],
},
{ path: "seo.siteName", label: "Site name", kind: "text", group: "Open Graph" },
{ path: "seo.locale", label: "Locale", kind: "text", group: "Open Graph" },
{ path: "seo.ogImage", label: "OG image", kind: "url", group: "Open Graph" },
{ path: "seo.imageAlt", label: "OG image alt", kind: "textarea", group: "Open Graph", min: 20, max: 120 },
{ path: "seo.organizationName", label: "Organization", kind: "text", group: "Schema.org" },
{ path: "seo.organizationUrl", label: "Organization URL", kind: "url", group: "Schema.org" },
{ path: "seo.applicationCategory", label: "Application category", kind: "text", group: "Schema.org" },
];
const BLOCK_SEO_FIELDS = [
{ path: "seo.targetCluster", label: "Кластер", kind: "text", group: "Семантика" },
{ path: "seo.searchIntent", label: "Интент", kind: "textarea", group: "Семантика", min: 20, max: 220 },
{ path: "seo.primaryQuery", label: "Основной запрос", kind: "text", group: "Семантика" },
{ path: "seo.secondaryQueries", label: "Смежные запросы", kind: "textarea", group: "Семантика" },
{ path: "seo.title", label: "SEO-заголовок блока", kind: "text", group: "Сниппет блока", min: 35, max: 80 },
{ path: "seo.description", label: "SEO-описание блока", kind: "textarea", group: "Сниппет блока", min: 90, max: 180 },
{
path: "seo.indexHint",
label: "Роль в индексации",
kind: "select",
group: "Индексация",
options: ["section", "supporting", "conversion", "noindex"],
},
{ path: "seo.coverageNotes", label: "Заметки по охвату", kind: "textarea", group: "Аналитика", min: 20, max: 500 },
];
const el = {
modeContent: document.querySelector("#admin-mode-content"),
modeSeo: document.querySelector("#admin-mode-seo"),
sidebarTitle: document.querySelector("#sidebar-title"),
workspaceRole: document.querySelector("#workspace-role"),
editorHeading: document.querySelector("#editor-heading"),
panelKicker: document.querySelector("#content-panel-kicker"),
panelTitle: document.querySelector("#content-panel-title"),
panelNote: document.querySelector("#content-panel-note"),
regions: document.querySelector("#regions"),
addBlock: document.querySelector("#add-block"),
reload: document.querySelector("#reload"),
@ -143,7 +219,7 @@ const STATIC_FALLBACK = {
enabled: true,
content: {
navigation: {
logoSrc: "./assets/webflow/images/69ad964d951f9d17cc5a919e_logo-app.svg",
logoSrc: "./assets/site/images/69ad964d951f9d17cc5a919e_logo-app.svg",
logoAlt: "главная",
brandLabel: "NODE.DC",
links: [
@ -152,7 +228,7 @@ const STATIC_FALLBACK = {
{
enabled: true,
label: "Документация",
href: "https://docs.ssscript.app/",
href: "https://nodedc.dctouch.ru/",
target: "",
linkMode: "href",
linkTarget: "_blank",
@ -191,9 +267,9 @@ const STATIC_FALLBACK = {
href: "javascript:void(0)",
linkTarget: "",
targetId: "app-data",
iconSrc: "./assets/webflow/images/69ad964d951f9d17cc5a919e_logo-app.svg",
iconSrc: "./assets/site/images/69ad964d951f9d17cc5a919e_logo-app.svg",
iconAlt: "Иконка NODE.DC",
className: "dock-icon ssscript",
className: "dock-icon nodedc-app",
iconClass: "logo",
dividerBefore: false,
},
@ -203,7 +279,7 @@ const STATIC_FALLBACK = {
href: "javascript:void(0)",
linkTarget: "",
targetId: "",
iconSrc: "./assets/webflow/images/69a9ae818a76c773dc3cf7bc_trash.avif",
iconSrc: "./assets/site/images/69a9ae818a76c773dc3cf7bc_trash.avif",
iconAlt: "Белая иконка корзины.",
className: "dock-icon no-bg",
iconClass: "dock-app-img trash",
@ -214,11 +290,11 @@ const STATIC_FALLBACK = {
},
assets: {
webglModelSrc: "./assets/custom/icon.glb",
logoPng: "./assets/custom/logo.png",
logoPng: "./assets/uploads/images/Logo/dclogo_white_alpha-mqus5if2.png",
logoPngAlt: "Логотип NODE.DC",
appIconSrc: "./assets/webflow/images/69ad964d951f9d17cc5a919e_logo-app.svg",
appIconSrc: "./assets/site/images/69ad964d951f9d17cc5a919e_logo-app.svg",
appIconAlt: "Иконка NODE.DC",
appWordmarkSrc: "./assets/webflow/images/69ad96a28b06b7552a339137_logtype.svg?v=node-dc",
appWordmarkSrc: "./assets/site/images/69ad96a28b06b7552a339137_logtype.svg?v=node-dc",
appWordmarkAlt: "Логотип NODE.DC",
faviconSvg: "./assets/uploads/favicon/icon-adaptive.svg",
faviconIco: "./assets/uploads/favicon/favicon.ico",
@ -272,7 +348,7 @@ const STATIC_FALLBACK = {
targetId: "",
iconSrc: "",
iconAlt: "",
className: "dock-icon ssscript",
className: "dock-icon nodedc-app",
iconClass: "",
dividerBefore: false,
},
@ -322,7 +398,7 @@ const GROUP_COPY = {
Слайдер: "Видео-превью и подписи карточек внутри горизонтального слайдера.",
Скролл: "Скорость локальной прокрутки внутри блока: меньше значение — спокойнее шаг, больше — быстрее.",
"Один клик": "Подблок про быстрое добавление и настройку компонентов.",
MCP: "Подблок про MCP-сервер и контекст Webflow.",
MCP: "Подблок про MCP-сервер и контекст NODE.DC.",
"Окно FAQ": "Название окна FAQ и служебные подписи mail-интерфейса.",
Отправитель: "Профиль отправителя в FAQ: аватар, имя и подписи письма.",
FAQ: "Вопросы и ответы mail-интерфейса. Каждый пункт можно добавить, удалить и переставить.",
@ -354,7 +430,7 @@ const MEDIA_ROOT_FILTERS = [
["assets", "Assets"],
["assets/uploads", "Uploads"],
["assets/media", "Media"],
["assets/webflow/images", "Webflow"],
["assets/site/images", "NODE.DC"],
["assets/custom", "Custom"],
];
const FINDER_TRASH_ICON_SVG =
@ -459,6 +535,276 @@ function setByPath(source, path, nextValue) {
target[lastKey] = nextValue;
}
function ensurePageSeo() {
if (!state.page) return;
state.page.seo = {
...PAGE_SEO_DEFAULTS,
...(state.page.seo || {}),
};
}
function ensureBlockSeo(block) {
if (!block || isStaticSelection()) return;
block.seo = {
...BLOCK_SEO_DEFAULTS,
...(block.seo || {}),
};
}
function ensureSeoModel() {
ensurePageSeo();
for (const region of state.page?.regions || []) {
for (const block of region.blocks || []) {
block.seo = {
...BLOCK_SEO_DEFAULTS,
...(block.seo || {}),
};
}
}
}
function setAdminMode(nextMode) {
const mode = nextMode === "seo" ? "seo" : "content";
state.mode = mode;
updateAdminLayoutState({ mode });
if (state.page) {
renderAll();
} else {
updateAdminModeSurface();
}
}
function updateAdminModeSurface() {
const isSeo = state.mode === "seo";
document.body.dataset.adminMode = state.mode;
if (el.modeContent) el.modeContent.dataset.active = String(!isSeo);
if (el.modeSeo) el.modeSeo.dataset.active = String(isSeo);
if (el.sidebarTitle) el.sidebarTitle.textContent = isSeo ? "SEO" : "Администрирование";
if (el.workspaceRole) el.workspaceRole.textContent = isSeo ? "SEO-режим сайта" : "Администратор сайта";
if (el.editorHeading) el.editorHeading.textContent = isSeo ? "SEO сайта" : "Настройки сайта";
if (el.panelKicker) el.panelKicker.textContent = isSeo ? "SEO" : "Контент";
if (el.panelTitle) el.panelTitle.textContent = isSeo ? "Семантика и сниппеты" : "Редактируемые поля";
if (el.panelNote) {
el.panelNote.textContent = isSeo
? "Поля готовят страницу и секции к семантической настройке и дальнейшей автоматизации."
: "Поля сгруппированы по смысловым подблокам верстки.";
}
}
function editorJsonPayload(block) {
if (state.mode === "seo" && isStaticSelection()) {
return state.page?.seo || {};
}
return block || {};
}
function groupedSeoFields(fields) {
const groups = new Map();
for (const seoField of fields) {
const groupName = seoField.group || "SEO";
if (!groups.has(groupName)) groups.set(groupName, []);
groups.get(groupName).push(seoField);
}
return groups;
}
function compactText(value) {
if (value == null) return "";
if (Array.isArray(value)) return value.map(compactText).join(" ");
if (typeof value === "object") return Object.values(value).map(compactText).join(" ");
return String(value)
.replace(/<[^>]*>/g, " ")
.replace(/https?:\/\/\S+/g, " ")
.replace(/\s+/g, " ")
.trim();
}
function seoLengthStatus(value, min, max) {
const length = String(value || "").trim().length;
if (!min && !max) return { status: "info", text: length ? `${length} симв.` : "пусто" };
if (!length) return { status: "bad", text: `пусто · цель ${min || 0}-${max || "∞"}` };
if ((min && length < min) || (max && length > max)) {
return { status: "warn", text: `${length} симв. · цель ${min || 0}-${max || "∞"}` };
}
return { status: "good", text: `${length} симв.` };
}
function createSeoMetric(label, value, status = "info") {
const item = document.createElement("div");
const caption = document.createElement("span");
const number = document.createElement("strong");
item.className = "seo-metric";
item.dataset.status = status;
caption.textContent = label;
number.textContent = value;
item.append(caption, number);
return item;
}
function blockTextStats(block) {
const text = compactText(block?.content || "");
const words = text ? text.split(/\s+/).filter(Boolean).length : 0;
return { text, words, chars: text.length };
}
function renderSeoDiagnostics({ targetModel, fields, isStatic }) {
const diagnostics = document.createElement("section");
const head = document.createElement("div");
const title = document.createElement("h3");
const desc = document.createElement("p");
const metrics = document.createElement("div");
diagnostics.className = "seo-diagnostics";
head.className = "group-head";
title.textContent = isStatic ? "Диагностика страницы" : "Диагностика блока";
desc.className = "group-desc";
desc.textContent = isStatic
? "Базовые поля, которые попадают в head, Open Graph, sitemap и structured data."
: "Черновой слой для семантики блока. Позже его сможет заполнять внешний SEO-модуль.";
head.append(title, desc);
metrics.className = "seo-metric-grid";
if (isStatic) {
const titleStatus = seoLengthStatus(getByPath(targetModel, "seo.title"), 35, 70);
const descriptionStatus = seoLengthStatus(getByPath(targetModel, "seo.description"), 110, 170);
const canonical = String(getByPath(targetModel, "seo.canonicalUrl") || "");
const ogImage = String(getByPath(targetModel, "seo.ogImage") || "");
metrics.append(
createSeoMetric("Title", titleStatus.text, titleStatus.status),
createSeoMetric("Description", descriptionStatus.text, descriptionStatus.status),
createSeoMetric("Canonical", canonical.startsWith("https://") ? "абсолютный" : "проверить", canonical.startsWith("https://") ? "good" : "warn"),
createSeoMetric("OG image", ogImage ? "задан" : "пусто", ogImage ? "good" : "bad"),
);
} else {
const stats = blockTextStats(targetModel);
const primaryQuery = String(getByPath(targetModel, "seo.primaryQuery") || "").trim();
const secondary = String(getByPath(targetModel, "seo.secondaryQueries") || "")
.split(/\n|,/)
.map((item) => item.trim())
.filter(Boolean);
const titleStatus = seoLengthStatus(getByPath(targetModel, "seo.title"), 35, 80);
metrics.append(
createSeoMetric("Текст блока", `${stats.words} слов`, stats.words >= 40 ? "good" : "warn"),
createSeoMetric("Основной запрос", primaryQuery ? "задан" : "пусто", primaryQuery ? "good" : "bad"),
createSeoMetric("Смежные запросы", `${secondary.length}`, secondary.length ? "good" : "warn"),
createSeoMetric("SEO-заголовок", titleStatus.text, titleStatus.status),
);
}
diagnostics.append(head, metrics);
return diagnostics;
}
function updateSeoFieldMeter(meter, input, config) {
if (!meter) return;
const lengthStatus = seoLengthStatus(fieldInputValue(input), config.min, config.max);
meter.dataset.status = lengthStatus.status;
meter.textContent = lengthStatus.text;
}
function renderSeoInput({ targetModel, seoField, grid }) {
const label = document.createElement("label");
const labelRow = document.createElement("span");
const labelText = document.createElement("span");
const kind = document.createElement("span");
const path = document.createElement("span");
const isLong = seoField.kind === "textarea";
const input = seoField.kind === "select" ? document.createElement("select") : isLong ? document.createElement("textarea") : document.createElement("input");
const value = getByPath(targetModel, seoField.path) ?? "";
const meter = seoField.min || seoField.max ? document.createElement("span") : null;
label.className = `field-control seo-field${isLong ? " wide" : ""}`;
labelRow.className = "field-label-row";
labelText.className = "field-label-text";
labelText.textContent = seoField.label || seoField.path;
kind.className = "field-kind";
kind.textContent = seoField.kind || "text";
path.className = "field-path";
path.textContent = seoField.path;
labelRow.append(labelText, kind);
if (seoField.kind === "select") {
const values = new Set([...(seoField.options || []), value].filter(Boolean));
for (const optionValue of values) {
const option = document.createElement("option");
option.value = optionValue;
option.textContent = optionValue;
input.append(option);
}
} else {
if (!isLong) {
input.autocomplete = "off";
input.type = seoField.kind === "url" ? "url" : "text";
}
}
input.dataset.seoPath = seoField.path;
input.value = value;
input.placeholder = seoField.placeholder || "";
updateSeoFieldMeter(meter, input, seoField);
input.addEventListener(seoField.kind === "select" ? "change" : "input", () => {
setByPath(targetModel, seoField.path, fieldInputValue(input));
updateSeoFieldMeter(meter, input, seoField);
el.json.value = JSON.stringify(editorJsonPayload(activeBlock()), null, 2);
markDirty("SEO-поля обновлены локально. Нажмите «Сохранить модель» или «Обновить index.html».");
});
label.append(labelRow, input);
if (meter) {
meter.className = "seo-field-meter";
label.append(meter);
}
label.append(path);
grid.append(label);
}
function renderSeoFields(block) {
const isStatic = isStaticSelection();
const targetModel = isStatic ? state.page : block;
const fields = isStatic ? PAGE_SEO_FIELDS : BLOCK_SEO_FIELDS;
if (isStatic) ensurePageSeo();
else ensureBlockSeo(block);
el.contentFields.innerHTML = "";
el.contentFields.append(renderSeoDiagnostics({ targetModel, fields, isStatic }));
for (const [groupName, groupFields] of groupedSeoFields(fields)) {
const group = document.createElement("section");
const head = document.createElement("div");
const titleWrap = document.createElement("div");
const title = document.createElement("h3");
const desc = document.createElement("p");
const grid = document.createElement("div");
group.className = "content-group seo-group";
head.className = "group-head";
title.textContent = groupName;
desc.className = "group-desc";
desc.textContent = isStatic ? "Поля уровня страницы и поискового сниппета." : "Поля уровня секции для будущей семантической карты.";
titleWrap.append(title, desc);
head.append(titleWrap);
grid.className = "group-grid";
for (const seoField of groupFields) {
renderSeoInput({ targetModel, seoField, grid });
}
group.append(head, grid);
el.contentFields.append(group);
}
}
const LINK_WINDOW_TARGETS = new Set(["_blank", "_self", "_parent", "_top"]);
function isWindowTarget(value) {
@ -541,7 +887,7 @@ function createDefaultDockItems(dock = {}, assets = {}) {
targetId: dock.appTarget || "app-data",
iconSrc: assets.appIconSrc || STATIC_FALLBACK.content.assets.appIconSrc,
iconAlt: assets.appIconAlt || STATIC_FALLBACK.content.assets.appIconAlt,
className: "dock-icon ssscript",
className: "dock-icon nodedc-app",
iconClass: "logo",
dividerBefore: false,
},
@ -551,7 +897,7 @@ function createDefaultDockItems(dock = {}, assets = {}) {
href: "javascript:void(0)",
linkTarget: "",
targetId: "",
iconSrc: "./assets/webflow/images/69a9ae818a76c773dc3cf7bc_trash.avif",
iconSrc: "./assets/site/images/69a9ae818a76c773dc3cf7bc_trash.avif",
iconAlt: dock.trashAlt || "Белая иконка корзины.",
className: "dock-icon no-bg",
iconClass: "dock-app-img trash",
@ -578,7 +924,7 @@ function normalizeDock(dock, assets = {}) {
targetId: item.targetId ?? fallback.targetId ?? "",
iconSrc: hasOwn("iconSrc") ? item.iconSrc : fallback.iconSrc,
iconAlt: hasOwn("iconAlt") ? item.iconAlt : item.alt || fallback.iconAlt,
className: item.className || fallback.className || "dock-icon ssscript",
className: item.className || fallback.className || "dock-icon nodedc-app",
iconClass: hasOwn("iconClass") ? item.iconClass : fallback.iconClass || "logo",
dividerBefore: Boolean(item.dividerBefore),
};
@ -1241,6 +1587,10 @@ function addBlockFromTemplate(templateKey) {
block.id = uniqueBlockId(block.id);
block.enabled = true;
block.scopeIds = true;
block.seo = {
...BLOCK_SEO_DEFAULTS,
...(block.seo || {}),
};
region.blocks.push(block);
state.selected = { kind: "block", regionIndex: template.regionIndex, blockIndex: region.blocks.length - 1 };
closeTemplateModal();
@ -1268,23 +1618,29 @@ function renderEditorError(error) {
}
function renderEditor() {
updateAdminModeSurface();
const region = activeRegion();
const block = activeBlock();
const isStatic = isStaticSelection();
const isSeo = state.mode === "seo";
if (!block) {
el.empty.classList.remove("hidden");
el.form.classList.add("hidden");
el.selectedRegion.textContent = "NODE.DC / Выберите блок";
el.selectedTitle.textContent = "Контентный слой";
el.selectedTitle.textContent = isSeo ? "SEO-слой" : "Контентный слой";
return;
}
el.empty.classList.add("hidden");
el.form.classList.remove("hidden");
el.form.dataset.static = String(isStatic);
el.selectedRegion.textContent = `NODE.DC / ${region.label || region.id}`;
el.selectedTitle.textContent = block.adminLabel || block.id;
el.form.dataset.mode = state.mode;
el.selectedRegion.textContent = isSeo
? `NODE.DC / SEO / ${isStatic ? "Страница" : region.label || region.id}`
: `NODE.DC / ${region.label || region.id}`;
el.selectedTitle.textContent = isSeo ? `SEO: ${isStatic ? "страница" : block.adminLabel || block.id}` : block.adminLabel || block.id;
el.id.value = block.id || "";
el.label.value = block.adminLabel || "";
el.template.value = block.template || block.type || "";
@ -1294,15 +1650,19 @@ function renderEditor() {
el.anchor.disabled = isStatic;
el.enabled.disabled = isStatic;
el.enabled.closest(".toggle-row").hidden = isStatic;
setStructuralControlsDisabled(isStatic);
setStructuralControlsDisabled(isStatic || isSeo);
try {
renderContentFields(block);
if (isSeo) {
renderSeoFields(block);
} else {
renderContentFields(block);
}
} catch (error) {
renderEditorError(error);
}
el.json.value = JSON.stringify(block, null, 2);
el.json.value = JSON.stringify(editorJsonPayload(block), null, 2);
}
function renderAll() {
@ -3703,7 +4063,9 @@ function syncBlockFromJson() {
try {
const parsed = JSON.parse(el.json.value);
if (isStaticSelection()) {
if (state.mode === "seo" && isStaticSelection()) {
state.page.seo = parsed;
} else if (isStaticSelection()) {
state.page.staticElements = parsed;
} else {
const region = activeRegion();
@ -3732,6 +4094,7 @@ async function loadPage() {
state.templates = await templatesResponse.json();
ensureStaticElements();
ensureSectionsRegion();
ensureSeoModel();
state.selected = { kind: "static" };
renderAll();
setDirty(false);
@ -3845,6 +4208,9 @@ for (const input of [el.id, el.label, el.anchor, el.enabled]) {
input.addEventListener("change", syncBlockFromFields);
}
el.modeContent?.addEventListener("click", () => setAdminMode("content"));
el.modeSeo?.addEventListener("click", () => setAdminMode("seo"));
el.json.addEventListener("blur", () => {
if (state.filePickerActive || state.mediaUploadActive) {
recoverEditorSurface();

View File

@ -18,17 +18,14 @@
</div>
<div class="nodedc-expanded-toolbar-center">
<span class="nodedc-expanded-workspace-button" aria-hidden="true">
<img src="../assets/custom/logo.png" alt="" class="nodedc-expanded-workspace-avatar" />
<img src="../assets/uploads/images/Logo/dclogo_white_alpha-mqus5if2.png" alt="" class="nodedc-expanded-workspace-avatar" />
</span>
<nav class="nodedc-expanded-nav-group" aria-label="Навигация админки">
<a class="nodedc-expanded-nav-button" href="/" target="_blank" data-active="false">
<span>Витрина</span>
</a>
<button class="nodedc-expanded-nav-button" type="button" data-active="true">
<button id="admin-mode-content" class="nodedc-expanded-nav-button" type="button" data-active="true">
<span>Администрирование</span>
</button>
<button class="nodedc-expanded-nav-button" type="button" data-active="false">
<span>Платформа</span>
<button id="admin-mode-seo" class="nodedc-expanded-nav-button" type="button" data-active="false">
<span>SEO</span>
</button>
</nav>
</div>
@ -53,14 +50,14 @@
<div class="sidebar-head">
<div>
<div class="section-kicker">NODE.DC</div>
<h1>Администрирование</h1>
<h1 id="sidebar-title">Администрирование</h1>
</div>
<button id="add-block" class="admin-panel-close add-block-button" type="button" aria-label="Добавить блок">+</button>
<div class="workspace-card admin-panel-client-select">
<div class="workspace-logo admin-panel-client-select__icon">DC</div>
<div>
<div class="workspace-name">NODE.DC</div>
<div class="workspace-role">Администратор сайта</div>
<div id="workspace-role" class="workspace-role">Администратор сайта</div>
</div>
</div>
</div>
@ -70,7 +67,7 @@
<section class="editor">
<header class="editor-head">
<div>
<h1>Настройки сайта</h1>
<h1 id="editor-heading">Настройки сайта</h1>
<div id="selected-region" class="breadcrumb">NODE.DC / Выберите блок</div>
<h2 id="selected-title">Контентный слой</h2>
</div>
@ -160,10 +157,10 @@
<section class="content-panel">
<div class="panel-head">
<div>
<div class="section-kicker">Контент</div>
<h3>Редактируемые поля</h3>
<div id="content-panel-kicker" class="section-kicker">Контент</div>
<h3 id="content-panel-title">Редактируемые поля</h3>
</div>
<span class="panel-note">Поля сгруппированы по смысловым подблокам верстки.</span>
<span id="content-panel-note" class="panel-note">Поля сгруппированы по смысловым подблокам верстки.</span>
</div>
<div id="content-fields" class="content-fields"></div>
</section>

View File

@ -1,6 +1,6 @@
{
"generatedAt": "2026-06-25T17:31:52.625Z",
"sourceDir": "/Users/dcconstructions/site-rescue/ssscript.app",
"generatedAt": "2026-06-27T15:32:57.156Z",
"sourceDir": "NODE.DC PromoSite sanitized asset manifest",
"targetDir": "/Users/dcconstructions/Downloads/mnt/NODEDC/NODEDC_SITE",
"pages": [
"index.html",
@ -11,22 +11,22 @@
"assets": [
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/69b6acdde3123ef078e2b7f3_og-img.jpg",
"localPath": "assets/webflow/images/69b6acdde3123ef078e2b7f3_og-img.jpg"
"localPath": "assets/site/images/69b6acdde3123ef078e2b7f3_og-img.jpg"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/css/ssscript.shared.8838f69df.min.css",
"localPath": "assets/webflow/css/ssscript.shared.8838f69df.min.css"
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/css/nodedc.shared.8838f69df.min.css",
"localPath": "assets/site/css/nodedc.shared.8838f69df.min.css"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/69b83bc24411350dcdb86781_favicon.png",
"localPath": "assets/webflow/images/69b83bc24411350dcdb86781_favicon.png"
"localPath": "assets/site/images/69b83bc24411350dcdb86781_favicon.png"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/69b83bc6ad9e108637556ec5_webclip.png",
"localPath": "assets/webflow/images/69b83bc6ad9e108637556ec5_webclip.png"
"localPath": "assets/site/images/69b83bc6ad9e108637556ec5_webclip.png"
},
{
"url": "https://ssscript-website.vercel.app/out.css",
"url": "/out.css",
"localPath": "assets/custom/css/out.css"
},
{
@ -34,211 +34,195 @@
"localPath": "assets/vendor/umami-script.js"
},
{
"url": "https://ssscript-website.vercel.app/app.js",
"url": "/app.js",
"localPath": "assets/custom/js/app.js"
},
{
"url": "https://ssscript-website.vercel.app/app.css",
"url": "/app.css",
"localPath": "assets/custom/css/app.css"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/69ad964d951f9d17cc5a919e_logo-app.svg",
"localPath": "assets/webflow/images/69ad964d951f9d17cc5a919e_logo-app.svg"
"localPath": "assets/site/images/69ad964d951f9d17cc5a919e_logo-app.svg"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/69ad96a28b06b7552a339137_logtype.svg",
"localPath": "assets/webflow/images/69ad96a28b06b7552a339137_logtype.svg"
"localPath": "assets/site/images/69ad96a28b06b7552a339137_logtype.svg"
},
{
"url": "https://ssscript.dev/ssscript-website/publish-flow.mp4",
"url": "https://nodedc.dev/nodedc-website/publish-flow.mp4",
"localPath": "assets/media/publish-flow.mp4"
},
{
"url": "https://ssscript.dev/ssscript-website/various-modes.mp4",
"url": "https://nodedc.dev/nodedc-website/various-modes.mp4",
"localPath": "assets/media/various-modes.mp4"
},
{
"url": "https://ssscript.dev/ssscript-website/project-flow.mp4",
"url": "https://nodedc.dev/nodedc-website/project-flow.mp4",
"localPath": "assets/media/project-flow.mp4"
},
{
"url": "https://ssscript.dev/ssscript-website/ai-dock-mode.mp4",
"url": "https://nodedc.dev/nodedc-website/ai-dock-mode.mp4",
"localPath": "assets/media/ai-dock-mode.mp4"
},
{
"url": "https://ssscript.dev/ssscript-website/account.mp4",
"url": "https://nodedc.dev/nodedc-website/account.mp4",
"localPath": "assets/media/account.mp4"
},
{
"url": "https://ssscript.dev/ssscript-website/code-edits.mp4",
"url": "https://nodedc.dev/nodedc-website/code-edits.mp4",
"localPath": "assets/media/code-edits.mp4"
},
{
"url": "https://ssscript.dev/ssscript-website/dev-server.mp4",
"url": "https://nodedc.dev/nodedc-website/dev-server.mp4",
"localPath": "assets/media/dev-server.mp4"
},
{
"url": "https://ssscript.dev/ssscript-website/webflow-mcp.mp4",
"localPath": "assets/media/webflow-mcp.mp4"
"url": "https://nodedc.dev/nodedc-website/mcp-gateway.mp4",
"localPath": "assets/media/mcp-gateway.mp4"
},
{
"url": "https://cdn.prod.website-files.com/69b4294bd9af8bc5d029f550/69d3dc9256192179afb4cfdc_Frame%201.avif",
"localPath": "assets/webflow/images/69d3dc9256192179afb4cfdc_Frame 1.avif"
"localPath": "assets/site/images/69d3dc9256192179afb4cfdc_Frame 1.avif"
},
{
"url": "https://cdn.prod.website-files.com/69b4294bd9af8bc5d029f550/69d3dc9256192179afb4cfdc_Frame%201-p-500.avif",
"localPath": "assets/webflow/images/69d3dc9256192179afb4cfdc_Frame 1-p-500.avif"
"localPath": "assets/site/images/69d3dc9256192179afb4cfdc_Frame 1-p-500.avif"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/69aab66f030013f2c7177d78_mov-file.avif",
"localPath": "assets/webflow/images/69aab66f030013f2c7177d78_mov-file.avif"
"localPath": "assets/site/images/69aab66f030013f2c7177d78_mov-file.avif"
},
{
"url": "https://cdn.prod.website-files.com/69b4294bd9af8bc5d029f550/69d3fba72116e87a8d145059_Frame%203174.avif",
"localPath": "assets/webflow/images/69d3fba72116e87a8d145059_Frame 3174.avif"
"localPath": "assets/site/images/69d3fba72116e87a8d145059_Frame 3174.avif"
},
{
"url": "https://cdn.prod.website-files.com/69b4294bd9af8bc5d029f550/69d3fba72116e87a8d145059_Frame%203174-p-500.avif",
"localPath": "assets/webflow/images/69d3fba72116e87a8d145059_Frame 3174-p-500.avif"
"localPath": "assets/site/images/69d3fba72116e87a8d145059_Frame 3174-p-500.avif"
},
{
"url": "https://cdn.prod.website-files.com/69b4294bd9af8bc5d029f550/69d3fac0d22182f930e32ec6_Frame%203175.avif",
"localPath": "assets/webflow/images/69d3fac0d22182f930e32ec6_Frame 3175.avif"
"localPath": "assets/site/images/69d3fac0d22182f930e32ec6_Frame 3175.avif"
},
{
"url": "https://cdn.prod.website-files.com/69b4294bd9af8bc5d029f550/69d3fac0d22182f930e32ec6_Frame%203175-p-500.avif",
"localPath": "assets/webflow/images/69d3fac0d22182f930e32ec6_Frame 3175-p-500.avif"
"localPath": "assets/site/images/69d3fac0d22182f930e32ec6_Frame 3175-p-500.avif"
},
{
"url": "https://cdn.prod.website-files.com/69b4294bd9af8bc5d029f550/69d3dc83a417451438caec72_Frame%203173.avif",
"localPath": "assets/webflow/images/69d3dc83a417451438caec72_Frame 3173.avif"
"localPath": "assets/site/images/69d3dc83a417451438caec72_Frame 3173.avif"
},
{
"url": "https://cdn.prod.website-files.com/69b4294bd9af8bc5d029f550/69d3dc83a417451438caec72_Frame%203173-p-500.avif",
"localPath": "assets/webflow/images/69d3dc83a417451438caec72_Frame 3173-p-500.avif"
"localPath": "assets/site/images/69d3dc83a417451438caec72_Frame 3173-p-500.avif"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/69a9fb87bc56142371745926_txt-file.avif",
"localPath": "assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif"
"localPath": "assets/site/images/69a9fb87bc56142371745926_txt-file.avif"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/69a9fb87bc56142371745926_txt-file-p-500.avif",
"localPath": "assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif"
"localPath": "assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/69a9ae818a76c773dc3cf7bc_trash.avif",
"localPath": "assets/webflow/images/69a9ae818a76c773dc3cf7bc_trash.avif"
"localPath": "assets/site/images/69a9ae818a76c773dc3cf7bc_trash.avif"
},
{
"url": "https://d3e54v103j8qbb.cloudfront.net/js/jquery-3.5.1.min.dc5e7f18c8.js?site=69a96c45de5009e6ba9d7123",
"localPath": "assets/vendor/jquery-3.5.1.min.dc5e7f18c8.js"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/js/ssscript.schunk.7321a5097fb66f41.js",
"localPath": "assets/webflow/js/ssscript.schunk.7321a5097fb66f41.js"
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/js/nodedc.schunk.7321a5097fb66f41.js",
"localPath": "assets/site/js/nodedc.schunk.7321a5097fb66f41.js"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/js/ssscript.a0aa6ca1.38bff8ac21973156.js",
"localPath": "assets/webflow/js/ssscript.a0aa6ca1.38bff8ac21973156.js"
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/js/nodedc.a0aa6ca1.38bff8ac21973156.js",
"localPath": "assets/site/js/nodedc.a0aa6ca1.38bff8ac21973156.js"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/js/ssscript.751e0867.148dc658e77a3916.js",
"localPath": "assets/webflow/js/ssscript.751e0867.148dc658e77a3916.js"
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/js/nodedc.751e0867.148dc658e77a3916.js",
"localPath": "assets/site/js/nodedc.751e0867.148dc658e77a3916.js"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/69ad9c60665a3d2b6fc590a7_InstrumentSans-SemiBoldItalic.woff2",
"localPath": "assets/webflow/images/69ad9c60665a3d2b6fc590a7_InstrumentSans-SemiBoldItalic.woff2"
"localPath": "assets/site/images/69ad9c60665a3d2b6fc590a7_InstrumentSans-SemiBoldItalic.woff2"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/69ad9c60fbcd5a800591e421_InstrumentSans-SemiBold.woff2",
"localPath": "assets/webflow/images/69ad9c60fbcd5a800591e421_InstrumentSans-SemiBold.woff2"
"localPath": "assets/site/images/69ad9c60fbcd5a800591e421_InstrumentSans-SemiBold.woff2"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/69ad9c602a855fbb013d5c50_InstrumentSans-Regular.woff2",
"localPath": "assets/webflow/images/69ad9c602a855fbb013d5c50_InstrumentSans-Regular.woff2"
"localPath": "assets/site/images/69ad9c602a855fbb013d5c50_InstrumentSans-Regular.woff2"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/69ad9c603cc9de956857dcdc_InstrumentSans-BoldItalic.woff2",
"localPath": "assets/webflow/images/69ad9c603cc9de956857dcdc_InstrumentSans-BoldItalic.woff2"
"localPath": "assets/site/images/69ad9c603cc9de956857dcdc_InstrumentSans-BoldItalic.woff2"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/69ad9c605d5b0cb77878327b_InstrumentSans-Bold.woff2",
"localPath": "assets/webflow/images/69ad9c605d5b0cb77878327b_InstrumentSans-Bold.woff2"
"localPath": "assets/site/images/69ad9c605d5b0cb77878327b_InstrumentSans-Bold.woff2"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/69ad9c604d256969a81247d8_InstrumentSans-MediumItalic.woff2",
"localPath": "assets/webflow/images/69ad9c604d256969a81247d8_InstrumentSans-MediumItalic.woff2"
"localPath": "assets/site/images/69ad9c604d256969a81247d8_InstrumentSans-MediumItalic.woff2"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/69ad9c60452994fbb27f3183_InstrumentSans-Italic.woff2",
"localPath": "assets/webflow/images/69ad9c60452994fbb27f3183_InstrumentSans-Italic.woff2"
"localPath": "assets/site/images/69ad9c60452994fbb27f3183_InstrumentSans-Italic.woff2"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/69ad9c60f0f31535d2eaa0bd_InstrumentSans-Medium.woff2",
"localPath": "assets/webflow/images/69ad9c60f0f31535d2eaa0bd_InstrumentSans-Medium.woff2"
"localPath": "assets/site/images/69ad9c60f0f31535d2eaa0bd_InstrumentSans-Medium.woff2"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/69d2db06ed4081cf9bdacdc4_federic0.avif",
"localPath": "assets/webflow/images/69d2db06ed4081cf9bdacdc4_federic0.avif"
},
{
"url": "https://ssscript-website.vercel.app/public/icon.glb",
"url": "/public/icon.glb",
"localPath": "assets/custom/icon.glb"
},
{
"url": "https://ssscript-website.vercel.app/public/logo.png",
"url": "/public/logo.png",
"localPath": "assets/custom/logo.png"
},
{
"url": "https://d3e54v103j8qbb.cloudfront.net/img/webflow-badge-icon-d2.89e12c322e.svg",
"localPath": "assets/vendor/webflow-badge-icon-d2.89e12c322e.svg"
},
{
"url": "https://d3e54v103j8qbb.cloudfront.net/img/webflow-badge-text-d2.c82cec3b78.svg",
"localPath": "assets/vendor/webflow-badge-text-d2.c82cec3b78.svg"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/images/69ad9c60665a3d2b6fc590a7_InstrumentSans-SemiBoldItalic.woff2",
"localPath": "assets/webflow/images/69ad9c60665a3d2b6fc590a7_InstrumentSans-SemiBoldItalic.woff2"
"localPath": "assets/site/images/69ad9c60665a3d2b6fc590a7_InstrumentSans-SemiBoldItalic.woff2"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/images/69ad9c60fbcd5a800591e421_InstrumentSans-SemiBold.woff2",
"localPath": "assets/webflow/images/69ad9c60fbcd5a800591e421_InstrumentSans-SemiBold.woff2"
"localPath": "assets/site/images/69ad9c60fbcd5a800591e421_InstrumentSans-SemiBold.woff2"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/images/69ad9c602a855fbb013d5c50_InstrumentSans-Regular.woff2",
"localPath": "assets/webflow/images/69ad9c602a855fbb013d5c50_InstrumentSans-Regular.woff2"
"localPath": "assets/site/images/69ad9c602a855fbb013d5c50_InstrumentSans-Regular.woff2"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/images/69ad9c603cc9de956857dcdc_InstrumentSans-BoldItalic.woff2",
"localPath": "assets/webflow/images/69ad9c603cc9de956857dcdc_InstrumentSans-BoldItalic.woff2"
"localPath": "assets/site/images/69ad9c603cc9de956857dcdc_InstrumentSans-BoldItalic.woff2"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/images/69ad9c605d5b0cb77878327b_InstrumentSans-Bold.woff2",
"localPath": "assets/webflow/images/69ad9c605d5b0cb77878327b_InstrumentSans-Bold.woff2"
"localPath": "assets/site/images/69ad9c605d5b0cb77878327b_InstrumentSans-Bold.woff2"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/images/69ad9c604d256969a81247d8_InstrumentSans-MediumItalic.woff2",
"localPath": "assets/webflow/images/69ad9c604d256969a81247d8_InstrumentSans-MediumItalic.woff2"
"localPath": "assets/site/images/69ad9c604d256969a81247d8_InstrumentSans-MediumItalic.woff2"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/images/69ad9c60452994fbb27f3183_InstrumentSans-Italic.woff2",
"localPath": "assets/webflow/images/69ad9c60452994fbb27f3183_InstrumentSans-Italic.woff2"
"localPath": "assets/site/images/69ad9c60452994fbb27f3183_InstrumentSans-Italic.woff2"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/images/69ad9c60f0f31535d2eaa0bd_InstrumentSans-Medium.woff2",
"localPath": "assets/webflow/images/69ad9c60f0f31535d2eaa0bd_InstrumentSans-Medium.woff2"
"localPath": "assets/site/images/69ad9c60f0f31535d2eaa0bd_InstrumentSans-Medium.woff2"
},
{
"url": "https://cdn.prod.website-files.com/69a96c45de5009e6ba9d7123/images/69d2db06ed4081cf9bdacdc4_federic0.avif",
"localPath": "assets/webflow/images/69d2db06ed4081cf9bdacdc4_federic0.avif"
},
{
"url": "https://ssscript-website.vercel.app/assets/custom/icon.glb",
"url": "/assets/custom/icon.glb",
"localPath": "assets/custom/icon.glb"
},
{
"url": "https://ssscript-website.vercel.app/assets/custom/logo.png",
"url": "/assets/custom/logo.png",
"localPath": "assets/custom/logo.png"
}
],

View File

@ -4,5 +4,5 @@
.project-composition .project-composition-copy{position:relative;z-index:2}.project-composition .project-composition-copy-bottom{margin-top:2.5rem}.project-composition .project-composition-copy-bottom .main-h{margin-bottom:0}@media screen and (max-width:767px){.project-composition .project-composition-hgroup{flex-flow:row;gap:1.25rem}.project-composition .project-composition-hgroup.flip{flex-flow:row-reverse}.project-composition .project-composition-hgroup .main-h.h2{font-size:2.35rem}.project-composition .project-composition-hgroup .h-txt{max-width:22ch}.project-composition .project-composition-copy-bottom{margin-top:2rem}}@media screen and (max-width:479px){.project-composition .project-composition-copy{width:86%}.project-composition .project-composition-hgroup{gap:1rem}.project-composition .project-composition-hgroup .main-h.h2{font-size:2rem}.project-composition .project-composition-hgroup .h-txt{max-width:17ch;font-size:.86rem;line-height:1.28em}}
.pricing-table-section .pricing-table-shell{padding-top:12svh;padding-bottom:12svh}@media screen and (max-width:767px){.pricing-table-section .pricing-table-shell{padding-top:8svh;padding-bottom:8svh}}
[data-module=notification-w][data-notification-stack-ready]{height:auto;max-height:calc(100svh - 3.2rem);overflow:visible;scrollbar-width:none;overscroll-behavior:contain}[data-module=notification-w][data-notification-stack-ready]::-webkit-scrollbar{display:none}[data-module=notification-w][data-notification-stack-ready]:not(:hover):not([data-notification-expanded=true]){height:var(--notification-collapsed-height,6.6rem);max-height:var(--notification-collapsed-height,6.6rem)}[data-module=notification-w][data-notification-stack-ready]:is(:hover,[data-notification-expanded=true]){overflow-x:visible;overflow-y:auto;padding-bottom:.2rem}[data-module=notification-w][data-notification-stack-ready]>[data-module=notification]{position:relative;z-index:var(--notification-z,1);transform:translateY(var(--notification-stack-y,0));transition:transform .3s cubic-bezier(.22,1,.36,1)}[data-module=notification-w][data-notification-stack-ready]:is(:hover,[data-notification-expanded=true])>[data-module=notification]{transform:translateY(0)}
.dock-icon.ssscript{overflow:hidden;background-color:transparent;background-image:none}.dock-icon.ssscript>img{display:block;width:100%;height:100%;max-width:none;object-fit:cover;border-radius:inherit}
[data-module=dock]{overflow:visible}[data-module=dock] .dock-icon{position:relative;overflow:visible}[data-module=dock] .dock-icon.ssscript{overflow:visible}[data-module=dock] .dock-icon::after{content:attr(data-dock-title);position:absolute;z-index:30;left:50%;bottom:calc(100% + .64rem);max-width:14rem;transform:translate(-50%,.28rem) scale(.96);transform-origin:50% 100%;opacity:0;pointer-events:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border:1px solid rgba(255,255,255,.12);border-radius:.72rem;background:linear-gradient(180deg,rgba(255,255,255,.07),rgba(255,255,255,.018)),rgba(34,34,38,.86);box-shadow:inset 0 1px 0 rgba(255,255,255,.08),0 20px 60px rgba(0,0,0,.42);-webkit-backdrop-filter:blur(22px) saturate(1.15);backdrop-filter:blur(22px) saturate(1.15);color:rgba(255,255,255,.92);padding:.42rem .68rem;font-size:.68rem;font-weight:760;line-height:1;letter-spacing:0;transition:opacity .16s ease,transform .16s ease}[data-module=dock] .dock-icon:is(:hover,:focus-visible){z-index:20}[data-module=dock] .dock-icon:is(:hover,:focus-visible)::after{opacity:1;transform:translate(-50%,0) scale(1)}[data-module=dock] .dock-icon[data-dock-title=""]::after{display:none}
.dock-icon.nodedc-app{overflow:hidden;background-color:transparent;background-image:none}.dock-icon.nodedc-app>img{display:block;width:100%;height:100%;max-width:none;object-fit:cover;border-radius:inherit}
[data-module=dock]{overflow:visible}[data-module=dock] .dock-icon{position:relative;overflow:visible}[data-module=dock] .dock-icon.nodedc-app{overflow:visible}[data-module=dock] .dock-icon::after{content:attr(data-dock-title);position:absolute;z-index:30;left:50%;bottom:calc(100% + .64rem);max-width:14rem;transform:translate(-50%,.28rem) scale(.96);transform-origin:50% 100%;opacity:0;pointer-events:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border:1px solid rgba(255,255,255,.12);border-radius:.72rem;background:linear-gradient(180deg,rgba(255,255,255,.07),rgba(255,255,255,.018)),rgba(34,34,38,.86);box-shadow:inset 0 1px 0 rgba(255,255,255,.08),0 20px 60px rgba(0,0,0,.42);-webkit-backdrop-filter:blur(22px) saturate(1.15);backdrop-filter:blur(22px) saturate(1.15);color:rgba(255,255,255,.92);padding:.42rem .68rem;font-size:.68rem;font-weight:760;line-height:1;letter-spacing:0;transition:opacity .16s ease,transform .16s ease}[data-module=dock] .dock-icon:is(:hover,:focus-visible){z-index:20}[data-module=dock] .dock-icon:is(:hover,:focus-visible)::after{opacity:1;transform:translate(-50%,0) scale(1)}[data-module=dock] .dock-icon[data-dock-title=""]::after{display:none}

View File

@ -4,5 +4,5 @@
.project-composition .project-composition-copy{position:relative;z-index:2}.project-composition .project-composition-copy-bottom{margin-top:2.5rem}.project-composition .project-composition-copy-bottom .main-h{margin-bottom:0}@media screen and (max-width:767px){.project-composition .project-composition-hgroup{flex-flow:row;gap:1.25rem}.project-composition .project-composition-hgroup.flip{flex-flow:row-reverse}.project-composition .project-composition-hgroup .main-h.h2{font-size:2.35rem}.project-composition .project-composition-hgroup .h-txt{max-width:22ch}.project-composition .project-composition-copy-bottom{margin-top:2rem}}@media screen and (max-width:479px){.project-composition .project-composition-copy{width:86%}.project-composition .project-composition-hgroup{gap:1rem}.project-composition .project-composition-hgroup .main-h.h2{font-size:2rem}.project-composition .project-composition-hgroup .h-txt{max-width:17ch;font-size:.86rem;line-height:1.28em}}
.pricing-table-section .pricing-table-shell{padding-top:12svh;padding-bottom:12svh}@media screen and (max-width:767px){.pricing-table-section .pricing-table-shell{padding-top:8svh;padding-bottom:8svh}}
[data-module=notification-w][data-notification-stack-ready]{height:auto;max-height:calc(100svh - 3.2rem);overflow:visible;scrollbar-width:none;overscroll-behavior:contain}[data-module=notification-w][data-notification-stack-ready]::-webkit-scrollbar{display:none}[data-module=notification-w][data-notification-stack-ready]:not(:hover):not([data-notification-expanded=true]){height:var(--notification-collapsed-height,6.6rem);max-height:var(--notification-collapsed-height,6.6rem)}[data-module=notification-w][data-notification-stack-ready]:is(:hover,[data-notification-expanded=true]){overflow-x:visible;overflow-y:auto;padding-bottom:.2rem}[data-module=notification-w][data-notification-stack-ready]>[data-module=notification]{position:relative;z-index:var(--notification-z,1);transform:translateY(var(--notification-stack-y,0));transition:transform .3s cubic-bezier(.22,1,.36,1)}[data-module=notification-w][data-notification-stack-ready]:is(:hover,[data-notification-expanded=true])>[data-module=notification]{transform:translateY(0)}
.dock-icon.ssscript{overflow:hidden;background-color:transparent;background-image:none}.dock-icon.ssscript>img{display:block;width:100%;height:100%;max-width:none;object-fit:cover;border-radius:inherit}
[data-module=dock]{overflow:visible}[data-module=dock] .dock-icon{position:relative;overflow:visible}[data-module=dock] .dock-icon.ssscript{overflow:visible}[data-module=dock] .dock-icon::after{content:attr(data-dock-title);position:absolute;z-index:30;left:50%;bottom:calc(100% + .64rem);max-width:14rem;transform:translate(-50%,.28rem) scale(.96);transform-origin:50% 100%;opacity:0;pointer-events:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border:1px solid rgba(255,255,255,.12);border-radius:.72rem;background:linear-gradient(180deg,rgba(255,255,255,.07),rgba(255,255,255,.018)),rgba(34,34,38,.86);box-shadow:inset 0 1px 0 rgba(255,255,255,.08),0 20px 60px rgba(0,0,0,.42);-webkit-backdrop-filter:blur(22px) saturate(1.15);backdrop-filter:blur(22px) saturate(1.15);color:rgba(255,255,255,.92);padding:.42rem .68rem;font-size:.68rem;font-weight:760;line-height:1;letter-spacing:0;transition:opacity .16s ease,transform .16s ease}[data-module=dock] .dock-icon:is(:hover,:focus-visible){z-index:20}[data-module=dock] .dock-icon:is(:hover,:focus-visible)::after{opacity:1;transform:translate(-50%,0) scale(1)}[data-module=dock] .dock-icon[data-dock-title=""]::after{display:none}
.dock-icon.nodedc-app{overflow:hidden;background-color:transparent;background-image:none}.dock-icon.nodedc-app>img{display:block;width:100%;height:100%;max-width:none;object-fit:cover;border-radius:inherit}
[data-module=dock]{overflow:visible}[data-module=dock] .dock-icon{position:relative;overflow:visible}[data-module=dock] .dock-icon.nodedc-app{overflow:visible}[data-module=dock] .dock-icon::after{content:attr(data-dock-title);position:absolute;z-index:30;left:50%;bottom:calc(100% + .64rem);max-width:14rem;transform:translate(-50%,.28rem) scale(.96);transform-origin:50% 100%;opacity:0;pointer-events:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border:1px solid rgba(255,255,255,.12);border-radius:.72rem;background:linear-gradient(180deg,rgba(255,255,255,.07),rgba(255,255,255,.018)),rgba(34,34,38,.86);box-shadow:inset 0 1px 0 rgba(255,255,255,.08),0 20px 60px rgba(0,0,0,.42);-webkit-backdrop-filter:blur(22px) saturate(1.15);backdrop-filter:blur(22px) saturate(1.15);color:rgba(255,255,255,.92);padding:.42rem .68rem;font-size:.68rem;font-weight:760;line-height:1;letter-spacing:0;transition:opacity .16s ease,transform .16s ease}[data-module=dock] .dock-icon:is(:hover,:focus-visible){z-index:20}[data-module=dock] .dock-icon:is(:hover,:focus-visible)::after{opacity:1;transform:translate(-50%,0) scale(1)}[data-module=dock] .dock-icon[data-dock-title=""]::after{display:none}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 333 B

After

Width:  |  Height:  |  Size: 333 B

View File

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 196 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 776 KiB

View File

@ -1,3 +0,0 @@
<svg width="26" height="16" viewBox="0 0 26 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.65 0L17.4654 16H9.77778L13.203 9.36892H13.0493C10.2235 13.0372 6.00738 15.452 0 16V9.46068C0 9.46068 3.84306 9.23369 6.10229 6.85844H0V0.000126383H6.85832V5.64099L7.01225 5.64036L9.8148 0.000126383H15.0016V5.60523L15.1555 5.60497L18.0632 0H25.65Z" fill="#146EF5"/>
</svg>

Before

Width:  |  Height:  |  Size: 421 B

View File

@ -1,15 +0,0 @@
<svg width="118" height="12" viewBox="0 0 118 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.525024 0.896484H3.05432L6.00776 8.26622L8.89905 0.896484H11.3531V11H9.47327V3.79949L6.6637 11H5.18713L2.3844 3.92077V11H0.525024V0.896484Z" fill="#080808"/>
<path d="M16.152 11.1367C15.4912 11.1367 14.901 10.9704 14.3815 10.6377C13.8665 10.305 13.4609 9.84017 13.1647 9.24316C12.8684 8.6416 12.7226 7.94889 12.7272 7.16504C12.7272 6.38118 12.8776 5.69076 13.1783 5.09375C13.4791 4.49674 13.8893 4.03646 14.4088 3.71289C14.9329 3.38477 15.5276 3.22298 16.193 3.22754C16.6305 3.22754 17.0361 3.30957 17.4098 3.47363C17.7835 3.63314 18.0934 3.85417 18.3395 4.13672C18.5901 4.41471 18.7542 4.71777 18.8317 5.0459L18.4967 5.03223V3.37109H20.3698V11H18.5172L18.524 9.52344L18.8522 9.33203C18.7428 9.66927 18.5559 9.97461 18.2916 10.248C18.0273 10.5215 17.7083 10.738 17.3346 10.8975C16.9609 11.057 16.5667 11.1367 16.152 11.1367ZM16.5621 9.55078C16.9495 9.55078 17.2936 9.45052 17.5944 9.25C17.8951 9.04948 18.1298 8.76921 18.2985 8.40918C18.4671 8.04915 18.5537 7.63444 18.5582 7.16504C18.5582 6.7002 18.4739 6.28776 18.3053 5.92773C18.1367 5.56771 17.8997 5.28971 17.5944 5.09375C17.2936 4.89779 16.9495 4.7998 16.5621 4.7998C16.1793 4.7998 15.8444 4.90007 15.5573 5.10059C15.2701 5.29655 15.0491 5.57454 14.8942 5.93457C14.7392 6.2946 14.6617 6.70703 14.6617 7.17188C14.6617 7.63672 14.7392 8.04915 14.8942 8.40918C15.0537 8.76465 15.277 9.04492 15.5641 9.25C15.8512 9.45052 16.1839 9.55078 16.5621 9.55078Z" fill="#080808"/>
<path d="M26.3033 10.9043C25.9251 11.0592 25.5332 11.1367 25.1276 11.1367C24.4668 11.1367 23.8766 10.9727 23.3571 10.6445C22.8375 10.3118 22.4319 9.84701 22.1403 9.25C21.8531 8.64844 21.7096 7.95573 21.7096 7.17188C21.7096 6.38346 21.8577 5.69076 22.1539 5.09375C22.4502 4.49219 22.858 4.02962 23.3776 3.70605C23.9017 3.38249 24.4964 3.22298 25.1617 3.22754C25.5765 3.22754 25.9729 3.30729 26.3512 3.4668C26.7295 3.62174 27.0485 3.84049 27.3082 4.12305C27.5726 4.4056 27.7434 4.72233 27.8209 5.07324L27.4928 5.06641V0.46582H29.3658V11H27.5133L27.5201 9.30469L27.8483 9.28418C27.7526 9.63965 27.5657 9.95866 27.2877 10.2412C27.0143 10.5238 26.6862 10.7448 26.3033 10.9043ZM24.5465 9.24316C24.8382 9.44368 25.1754 9.54395 25.5582 9.54395C25.9319 9.54395 26.2669 9.44368 26.5631 9.24316C26.8639 9.03809 27.1009 8.75553 27.274 8.39551C27.4472 8.03548 27.5361 7.6276 27.5406 7.17188C27.5406 6.70703 27.4563 6.2946 27.2877 5.93457C27.1191 5.56999 26.8844 5.29199 26.5836 5.10059C26.2828 4.90462 25.941 4.80664 25.5582 4.80664C25.1709 4.80664 24.8313 4.90462 24.5397 5.10059C24.248 5.29655 24.0247 5.57454 23.8698 5.93457C23.7148 6.2946 23.6373 6.70703 23.6373 7.17188C23.6373 7.63672 23.7148 8.04915 23.8698 8.40918C24.0293 8.76465 24.2548 9.04264 24.5465 9.24316Z" fill="#080808"/>
<path d="M34.5612 11.1709C33.8046 11.1709 33.1347 11.0068 32.5514 10.6787C31.9726 10.3506 31.5214 9.88574 31.1979 9.28418C30.8743 8.68262 30.7125 7.98763 30.7125 7.19922C30.7125 6.43815 30.8766 5.75684 31.2047 5.15527C31.5328 4.54915 31.9863 4.07747 32.5651 3.74023C33.1438 3.40299 33.7978 3.23438 34.527 3.23438C35.3017 3.23438 35.9853 3.40527 36.5778 3.74707C37.1748 4.08887 37.6282 4.59473 37.9381 5.26465C38.248 5.93001 38.3688 6.72526 38.3004 7.65039H32.6207H31.4987L32.6431 6.42385L36.8785 6.41309L36.3864 6.61816C36.3772 6.24447 36.2952 5.91178 36.1403 5.62012C35.9899 5.32845 35.778 5.10286 35.5045 4.94336C35.2311 4.7793 34.9166 4.69727 34.5612 4.69727C34.1875 4.69727 33.8525 4.78613 33.5563 4.96387C33.2646 5.1416 33.0345 5.38997 32.8658 5.70898C32.7523 5.9298 32.678 6.16809 32.6431 6.42385C32.6275 6.53758 32.6198 6.65476 32.6198 6.77539V7.53418C32.6193 7.57334 32.6196 7.61207 32.6207 7.65039C32.6309 8.03129 32.7126 8.37081 32.8658 8.66895C33.0345 8.99251 33.2669 9.24316 33.5631 9.4209C33.8639 9.59863 34.2057 9.6875 34.5885 9.6875C34.8665 9.6875 35.1217 9.63965 35.3541 9.54395C35.5911 9.44368 35.7893 9.30924 35.9489 9.14062C36.1129 8.96745 36.2291 8.76693 36.2975 8.53906H38.2184C38.1181 9.06315 37.9017 9.52344 37.569 9.91992C37.2408 10.3164 36.817 10.624 36.2975 10.8428C35.7825 11.0615 35.2037 11.1709 34.5612 11.1709Z" fill="#080808"/>
<path d="M42.9283 3.37109H44.8014V11H42.9283V3.37109ZM42.8942 0.370117H44.8698V2.28418H42.8942V0.370117Z" fill="#080808"/>
<path d="M48.3356 11H46.4694V3.37109H48.2946V4.93652L47.9938 5.05957C48.0804 4.69499 48.2467 4.3737 48.4928 4.0957C48.7389 3.81315 49.042 3.59668 49.402 3.44629C49.762 3.29134 50.1448 3.21615 50.5504 3.2207C51.0882 3.2207 51.5599 3.33691 51.9655 3.56934C52.3711 3.7972 52.6832 4.1276 52.902 4.56055C53.1253 4.99349 53.2369 5.50163 53.2369 6.08496V11H51.3639V6.46777C51.3639 6.13053 51.3069 5.84115 51.193 5.59961C51.0836 5.35352 50.9218 5.16895 50.7076 5.0459C50.498 4.91829 50.2474 4.85449 49.9557 4.85449C49.6503 4.85449 49.3746 4.92969 49.1285 5.08008C48.887 5.22591 48.6933 5.43555 48.5475 5.70898C48.4062 5.97786 48.3356 6.28548 48.3356 6.63184V11Z" fill="#080808"/>
<path d="M57.5162 0.896484H59.5875L61.509 8.05083L63.5319 0.896484H65.3502L67.4935 7.94671L69.3356 0.896484H71.2838L68.399 11H66.526L63.5797 1.63477H65.0973L62.274 11H60.442L57.5162 0.896484Z" fill="#080808"/>
<path d="M75.153 11.1709C74.3964 11.1709 73.7265 11.0068 73.1432 10.6787C72.5644 10.3506 72.1132 9.88574 71.7897 9.28418C71.4661 8.68262 71.3043 7.98763 71.3043 7.19922C71.3043 6.43815 71.4684 5.75684 71.7965 5.15527C72.1246 4.54915 72.5781 4.07747 73.1569 3.74023C73.7356 3.40299 74.3896 3.23438 75.1188 3.23438C75.8935 3.23438 76.5771 3.40527 77.1696 3.74707C77.7666 4.08887 78.22 4.59473 78.5299 5.26465C78.8398 5.93001 78.9606 6.72526 78.8922 7.65039H72.0905V6.42676L77.4703 6.41309L76.9781 6.61816C76.969 6.24447 76.887 5.91178 76.7321 5.62012C76.5817 5.32845 76.3698 5.10286 76.0963 4.94336C75.8229 4.7793 75.5084 4.69727 75.153 4.69727C74.7793 4.69727 74.4443 4.78613 74.1481 4.96387C73.8564 5.1416 73.6263 5.38997 73.4576 5.70898C73.2936 6.02799 73.2115 6.38346 73.2115 6.77539V7.53418C73.207 7.96257 73.289 8.34082 73.4576 8.66895C73.6263 8.99251 73.8587 9.24316 74.1549 9.4209C74.4557 9.59863 74.7975 9.6875 75.1803 9.6875C75.4583 9.6875 75.7135 9.63965 75.9459 9.54395C76.1829 9.44368 76.3811 9.30924 76.5406 9.14062C76.7047 8.96745 76.8209 8.76693 76.8893 8.53906H78.8102C78.7099 9.06315 78.4934 9.52344 78.1608 9.91992C77.8326 10.3164 77.4088 10.624 76.8893 10.8428C76.3743 11.0615 75.7955 11.1709 75.153 11.1709Z" fill="#080808"/>
<path d="M84.4293 11.1367C84.0237 11.1367 83.6295 11.0592 83.2467 10.9043C82.8684 10.7448 82.5403 10.5238 82.2623 10.2412C81.9889 9.95866 81.8043 9.63965 81.7086 9.28418L82.0367 9.30469L82.0436 11H80.191V0.46582H82.0641V5.06641L81.736 5.07324C81.8134 4.72233 81.9821 4.4056 82.2418 4.12305C82.5061 3.84049 82.8274 3.62174 83.2057 3.4668C83.5839 3.30729 83.9804 3.22754 84.3951 3.22754C85.0605 3.22298 85.653 3.38249 86.1725 3.70605C86.6966 4.02962 87.1067 4.49219 87.403 5.09375C87.6992 5.69076 87.8473 6.38346 87.8473 7.17188C87.8473 7.95573 87.7015 8.64844 87.4098 9.25C87.1227 9.84701 86.7194 10.3118 86.1998 10.6445C85.6803 10.9727 85.0901 11.1367 84.4293 11.1367ZM83.9987 9.54395C84.3815 9.54395 84.7187 9.44368 85.0104 9.24316C85.302 9.04264 85.5253 8.76465 85.6803 8.40918C85.8398 8.04915 85.9196 7.63672 85.9196 7.17188C85.9196 6.70703 85.8421 6.2946 85.6871 5.93457C85.5322 5.57454 85.3089 5.29655 85.0172 5.10059C84.7255 4.90462 84.386 4.80664 83.9987 4.80664C83.6158 4.80664 83.274 4.90462 82.9733 5.10059C82.6725 5.29199 82.4378 5.56999 82.2692 5.93457C82.1005 6.2946 82.0162 6.70703 82.0162 7.17188C82.0208 7.6276 82.1097 8.03548 82.2828 8.39551C82.456 8.75553 82.6907 9.03809 82.9869 9.24316C83.2877 9.44368 83.625 9.54395 83.9987 9.54395Z" fill="#080808"/>
<path d="M89.7408 2.85156C89.7408 2.34115 89.8571 1.89225 90.0895 1.50488C90.3265 1.11296 90.6569 0.812174 91.0807 0.602539C91.5045 0.388346 91.999 0.278971 92.5641 0.274414C92.6962 0.274414 92.8193 0.28125 92.9332 0.294922C93.0517 0.308594 93.1497 0.326823 93.2272 0.349609V1.92188L93.193 1.9082C93.1337 1.88997 93.0631 1.8763 92.9811 1.86719C92.9036 1.85352 92.8284 1.8444 92.7555 1.83984C92.5048 1.8444 92.2929 1.89225 92.1198 1.9834C91.9511 2.06999 91.8235 2.19987 91.7369 2.37305C91.6503 2.54622 91.6071 2.75358 91.6071 2.99512V11H89.7408V2.85156ZM88.5035 3.37109H93.1246V4.88867H88.5035V3.37109Z" fill="#080808"/>
<path d="M96.2076 11H94.3414V0.452148H96.2076V11ZM94.3414 0.452148H96.2076V1.93555H94.3414V0.452148ZM94.3414 9.5166H96.2076V11H94.3414V9.5166Z" fill="#080808"/>
<path d="M101.403 11.1641C100.642 11.1641 99.9697 10.9977 99.3864 10.665C98.8076 10.3324 98.3564 9.86523 98.0328 9.26367C97.7093 8.66211 97.5475 7.97396 97.5475 7.19922C97.5475 6.41536 97.7093 5.72266 98.0328 5.12109C98.3564 4.51497 98.8076 4.04557 99.3864 3.71289C99.9697 3.38021 100.642 3.21387 101.403 3.21387C102.164 3.21387 102.836 3.38021 103.42 3.71289C104.007 4.04557 104.463 4.51497 104.787 5.12109C105.11 5.72266 105.274 6.41536 105.279 7.19922C105.274 7.97852 105.11 8.66895 104.787 9.27051C104.463 9.86751 104.007 10.3324 103.42 10.665C102.836 10.9977 102.164 11.1641 101.403 11.1641ZM101.403 9.57812C101.795 9.57812 102.139 9.47786 102.435 9.27734C102.731 9.07682 102.957 8.79655 103.112 8.43652C103.267 8.0765 103.344 7.66406 103.344 7.19922C103.344 6.72982 103.267 6.3151 103.112 5.95508C102.957 5.59049 102.731 5.30794 102.435 5.10742C102.139 4.9069 101.795 4.80664 101.403 4.80664C101.011 4.80664 100.669 4.9069 100.378 5.10742C100.086 5.30794 99.8603 5.59049 99.7008 5.95508C99.5459 6.3151 99.4684 6.72982 99.4684 7.19922C99.4684 7.66862 99.5481 8.08333 99.7076 8.44336C99.8671 8.79883 100.09 9.07682 100.378 9.27734C100.669 9.47786 101.011 9.57812 101.403 9.57812Z" fill="#080808"/>
<path d="M105.737 3.37109H107.767L109.715 10.5215H108.574L110.611 3.37109H112.45L114.576 10.5762L113.332 10.583L115.294 3.37109H117.173L114.883 11H113.038L110.802 4.11621H111.964L109.88 11H108.02L105.737 3.37109Z" fill="#080808"/>
</svg>

Before

Width:  |  Height:  |  Size: 9.7 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -89,7 +89,7 @@
"targetId": "app-data",
"iconSrc": "./assets/uploads/images/static-elements/icon_hub-mqwd0sn0.png",
"iconAlt": "Иконка NODE.DC",
"className": "dock-icon ssscript",
"className": "dock-icon nodedc-app",
"iconClass": "logo",
"dividerBefore": false
},
@ -101,7 +101,7 @@
"targetId": "",
"iconSrc": "./assets/uploads/images/static-elements/icon_engine-mqwd2fut.png",
"iconAlt": "Иконка приложения",
"className": "dock-icon ssscript",
"className": "dock-icon nodedc-app",
"iconClass": "logo",
"dividerBefore": false
},
@ -113,7 +113,7 @@
"targetId": "",
"iconSrc": "./assets/uploads/images/static-elements/icon_ops-mqwdtmpk.png",
"iconAlt": "Иконка приложения",
"className": "dock-icon ssscript",
"className": "dock-icon nodedc-app",
"iconClass": "logo",
"dividerBefore": false
},
@ -125,17 +125,17 @@
"targetId": "",
"iconSrc": "./assets/uploads/images/static-elements/icon_bim-mqwedhun.png",
"iconAlt": "Иконка приложения",
"className": "dock-icon ssscript",
"className": "dock-icon nodedc-app",
"iconClass": "logo",
"dividerBefore": false
},
{
"enabled": true,
"title": "Корзина",
"href": "javascript:void(0)",
"href": "#",
"linkTarget": "",
"targetId": "",
"iconSrc": "./assets/webflow/images/69a9ae818a76c773dc3cf7bc_trash.avif",
"iconSrc": "./assets/site/images/69a9ae818a76c773dc3cf7bc_trash.avif",
"iconAlt": "Белая иконка корзины.",
"className": "dock-icon no-bg",
"iconClass": "dock-app-img trash",
@ -149,7 +149,7 @@
"logoPngAlt": "Логотип NODE.DC",
"appIconSrc": "./assets/uploads/images/Logo/dclogo_white_alpha-mqus5if2.png",
"appIconAlt": "Иконка NODE.DC",
"appWordmarkSrc": "./assets/webflow/images/69ad96a28b06b7552a339137_logtype.svg?v=node-dc",
"appWordmarkSrc": "./assets/site/images/69ad96a28b06b7552a339137_logtype.svg?v=node-dc",
"appWordmarkAlt": "Логотип NODE.DC",
"faviconSvg": "./assets/uploads/favicon/icon-adaptive.svg",
"faviconIco": "./assets/uploads/favicon/favicon.ico",
@ -270,7 +270,7 @@
"targetId": "",
"iconSrc": "",
"iconAlt": "",
"className": "dock-icon ssscript",
"className": "dock-icon nodedc-app",
"iconClass": "",
"dividerBefore": false
},
@ -434,8 +434,17 @@
]
},
"seo": {
"title": "NODE.DC — ИИ-платформа для Webflow",
"description": "Единая платформа для кастомного кода, MCP и ИИ-агентов в Webflow: контекст проекта, автоматизация, деплой и управляемая разработка."
"title": "NODE.DC - платформа для процессов, данных и ИИ-агентов",
"description": "NODE.DC объединяет Hub, Engine, Ops, прикладные модули и сквозного ассистента для задач, workflow, данных, ролей, интеграций и инженерных моделей.",
"canonicalUrl": "https://nodedc.dctouch.ru/",
"robots": "index,follow,max-image-preview:large",
"siteName": "NODE.DC",
"locale": "ru_RU",
"ogImage": "https://nodedc.dctouch.ru/assets/site/images/69b6acdde3123ef078e2b7f3_og-img.jpg",
"imageAlt": "NODE.DC - платформа для процессов, данных и ИИ-агентов",
"organizationName": "DCTOUCH",
"organizationUrl": "https://dctouch.ru/",
"applicationCategory": "BusinessApplication"
},
"regions": [
{
@ -487,12 +496,12 @@
"failureHeading": "Не получилось",
"failureTextHtml": "Что-то пошло не так. <br/>Попробуйте ещё раз чуть позже.<br/>"
},
"legalHtml": "<a href=\"https://docs.google.com/document/d/1vME2w8ZB88HoH-JyvkkiL7exrcMfWhkERfFRlnIeZFA/edit?usp=sharing\" target=\"_blank\">Политика конфиденциальности</a> • <a href=\"https://docs.google.com/document/d/1vME2w8ZB88HoH-JyvkkiL7exrcMfWhkERfFRlnIeZFA/edit?usp=sharing\" target=\"_blank\">Политика cookie</a><br/>© — Все права защищены<br/>2026",
"legalHtml": "<a href=\"https://www.dctouch.ru/\" target=\"_blank\" rel=\"noopener noreferrer\">DCTOUCH</a><br/>© NODE.DC, 2026",
"accessCard": {
"enabled": true,
"iconSrc": "./assets/uploads/images/Logo/dclogo_white_alpha-mqus5if2.png",
"iconAlt": "Иконка NODE.DC",
"wordmarkSrc": "./assets/webflow/images/69ad96a28b06b7552a339137_logtype.svg?v=node-dc",
"wordmarkSrc": "./assets/site/images/69ad96a28b06b7552a339137_logtype.svg?v=node-dc",
"wordmarkAlt": "Логотип NODE.DC",
"release": "Selfhost платформа ",
"heading": "Запросить DEMO",
@ -733,7 +742,17 @@
"group": "Юридический блок"
}
],
"enabled": true
"enabled": true,
"seo": {
"targetCluster": "",
"searchIntent": "",
"primaryQuery": "",
"secondaryQueries": "",
"title": "",
"description": "",
"coverageNotes": "",
"indexHint": "section"
}
},
{
"region": "cViz",
@ -857,7 +876,17 @@
]
}
],
"enabled": true
"enabled": true,
"seo": {
"targetCluster": "",
"searchIntent": "",
"primaryQuery": "",
"secondaryQueries": "",
"title": "",
"description": "",
"coverageNotes": "",
"indexHint": "section"
}
},
{
"region": "beforeCViz",
@ -873,7 +902,7 @@
"muted": "HUB",
"main": "точка входа в платформу "
},
"introHtml": "Универсальный инструмент для <span class=\"darker-70\">ИИ-агентов</span> в вашем Webflow-проекте.",
"introHtml": "Универсальный инструмент для <span class=\"darker-70\">ИИ-агентов</span> в вашем NODE.DC-контуре.",
"features": [
{
"eyebrow": "Каталог приложений",
@ -999,7 +1028,17 @@
"group": "Пункты"
}
],
"enabled": true
"enabled": true,
"seo": {
"targetCluster": "",
"searchIntent": "",
"primaryQuery": "",
"secondaryQueries": "",
"title": "",
"description": "",
"coverageNotes": "",
"indexHint": "section"
}
},
{
"region": "cViz",
@ -1139,7 +1178,17 @@
]
}
],
"enabled": true
"enabled": true,
"seo": {
"targetCluster": "",
"searchIntent": "",
"primaryQuery": "",
"secondaryQueries": "",
"title": "",
"description": "",
"coverageNotes": "",
"indexHint": "section"
}
},
{
"region": "beforeCViz",
@ -1282,7 +1331,17 @@
}
],
"enabled": true,
"scopeIds": true
"scopeIds": true,
"seo": {
"targetCluster": "",
"searchIntent": "",
"primaryQuery": "",
"secondaryQueries": "",
"title": "",
"description": "",
"coverageNotes": "",
"indexHint": "section"
}
},
{
"region": "cViz",
@ -1423,7 +1482,17 @@
}
],
"enabled": true,
"scopeIds": true
"scopeIds": true,
"seo": {
"targetCluster": "",
"searchIntent": "",
"primaryQuery": "",
"secondaryQueries": "",
"title": "",
"description": "",
"coverageNotes": "",
"indexHint": "section"
}
},
{
"region": "beforeCViz",
@ -1566,7 +1635,17 @@
}
],
"enabled": true,
"scopeIds": true
"scopeIds": true,
"seo": {
"targetCluster": "",
"searchIntent": "",
"primaryQuery": "",
"secondaryQueries": "",
"title": "",
"description": "",
"coverageNotes": "",
"indexHint": "section"
}
},
{
"region": "cViz",
@ -1707,7 +1786,17 @@
}
],
"enabled": true,
"scopeIds": true
"scopeIds": true,
"seo": {
"targetCluster": "",
"searchIntent": "",
"primaryQuery": "",
"secondaryQueries": "",
"title": "",
"description": "",
"coverageNotes": "",
"indexHint": "section"
}
},
{
"region": "cViz",
@ -1724,6 +1813,11 @@
},
"introHtml": "Прикладные модули закрывают функции подразделений: 1C, закупки, тендеры, юридический контур, цифровые двойники, инженерные модели и интеграции.",
"sliderItems": [
{
"enabled": true,
"videoSrc": "./assets/uploads/media/component-library-copy-copy-copy/nodedc-drone-mqwhqb84.png",
"title": "Инструменты работы с беспилотной техникой"
},
{
"videoSrc": "./assets/uploads/media/component-library-copy-copy-copy/ndc_1c-mqvamdlq.jpg",
"title": "1С Ассистент",
@ -1848,7 +1942,17 @@
}
],
"enabled": true,
"scopeIds": true
"scopeIds": true,
"seo": {
"targetCluster": "",
"searchIntent": "",
"primaryQuery": "",
"secondaryQueries": "",
"title": "",
"description": "",
"coverageNotes": "",
"indexHint": "section"
}
},
{
"region": "cViz",
@ -2075,7 +2179,17 @@
]
}
],
"enabled": true
"enabled": true,
"seo": {
"targetCluster": "",
"searchIntent": "",
"primaryQuery": "",
"secondaryQueries": "",
"title": "",
"description": "",
"coverageNotes": "",
"indexHint": "section"
}
},
{
"region": "cViz",
@ -2148,7 +2262,17 @@
"group": "CTA"
}
],
"enabled": true
"enabled": true,
"seo": {
"targetCluster": "",
"searchIntent": "",
"primaryQuery": "",
"secondaryQueries": "",
"title": "",
"description": "",
"coverageNotes": "",
"indexHint": "section"
}
},
{
"region": "cViz",
@ -2219,7 +2343,17 @@
"group": "Центральный текст"
}
],
"enabled": true
"enabled": true,
"seo": {
"targetCluster": "",
"searchIntent": "",
"primaryQuery": "",
"secondaryQueries": "",
"title": "",
"description": "",
"coverageNotes": "",
"indexHint": "section"
}
},
{
"region": "cViz",
@ -2703,7 +2837,17 @@
}
}
],
"enabled": false
"enabled": false,
"seo": {
"targetCluster": "",
"searchIntent": "",
"primaryQuery": "",
"secondaryQueries": "",
"title": "",
"description": "",
"coverageNotes": "",
"indexHint": "section"
}
},
{
"region": "cViz",
@ -2718,10 +2862,10 @@
{
"id": "one-click-publish-png",
"windowTitle": "публикация-в-один-клик.png",
"imageSrc": "./assets/webflow/images/69d3dc9256192179afb4cfdc_Frame%201.avif",
"imageSrcset": "./assets/webflow/images/69d3dc9256192179afb4cfdc_Frame%201-p-500.avif 500w, ./assets/webflow/images/69d3dc9256192179afb4cfdc_Frame%201.avif 1200w",
"imageSrc": "./assets/site/images/69d3dc9256192179afb4cfdc_Frame%201.avif",
"imageSrcset": "./assets/site/images/69d3dc9256192179afb4cfdc_Frame%201-p-500.avif 500w, ./assets/site/images/69d3dc9256192179afb4cfdc_Frame%201.avif 1200w",
"imageAlt": "",
"iconSrc": "./assets/webflow/images/69aab66f030013f2c7177d78_mov-file.avif",
"iconSrc": "./assets/site/images/69aab66f030013f2c7177d78_mov-file.avif",
"iconAlt": "Иконка MOV-файла.",
"folderLabel": "публикация-в-один-клик.png",
"enabled": true
@ -2729,10 +2873,10 @@
{
"id": "complete-code-editor-webp",
"windowTitle": "полноценный-редактор-кода.webp",
"imageSrc": "./assets/webflow/images/69d3fba72116e87a8d145059_Frame%203174.avif",
"imageSrcset": "./assets/webflow/images/69d3fba72116e87a8d145059_Frame%203174-p-500.avif 500w, ./assets/webflow/images/69d3fba72116e87a8d145059_Frame%203174.avif 1200w",
"imageSrc": "./assets/site/images/69d3fba72116e87a8d145059_Frame%203174.avif",
"imageSrcset": "./assets/site/images/69d3fba72116e87a8d145059_Frame%203174-p-500.avif 500w, ./assets/site/images/69d3fba72116e87a8d145059_Frame%203174.avif 1200w",
"imageAlt": "",
"iconSrc": "./assets/webflow/images/69aab66f030013f2c7177d78_mov-file.avif",
"iconSrc": "./assets/site/images/69aab66f030013f2c7177d78_mov-file.avif",
"iconAlt": "Иконка MOV-файла.",
"folderLabel": "полноценный-редактор-кода.webp",
"enabled": true
@ -2740,32 +2884,32 @@
{
"id": "ai-dock-mode-jpg",
"windowTitle": "ИИ-док-режим.jpg",
"imageSrc": "./assets/webflow/images/69d3fac0d22182f930e32ec6_Frame%203175.avif",
"imageSrcset": "./assets/webflow/images/69d3fac0d22182f930e32ec6_Frame%203175-p-500.avif 500w, ./assets/webflow/images/69d3fac0d22182f930e32ec6_Frame%203175.avif 1200w",
"imageSrc": "./assets/site/images/69d3fac0d22182f930e32ec6_Frame%203175.avif",
"imageSrcset": "./assets/site/images/69d3fac0d22182f930e32ec6_Frame%203175-p-500.avif 500w, ./assets/site/images/69d3fac0d22182f930e32ec6_Frame%203175.avif 1200w",
"imageAlt": "",
"iconSrc": "./assets/webflow/images/69aab66f030013f2c7177d78_mov-file.avif",
"iconSrc": "./assets/site/images/69aab66f030013f2c7177d78_mov-file.avif",
"iconAlt": "Иконка MOV-файла.",
"folderLabel": "ИИ-док-режим.jpg",
"enabled": true
},
{
"id": "deep-webflow-connection-webp",
"windowTitle": "глубокая-интеграция-webflow.webp",
"imageSrc": "./assets/webflow/images/69d3dc83a417451438caec72_Frame%203173.avif",
"imageSrcset": "./assets/webflow/images/69d3dc83a417451438caec72_Frame%203173-p-500.avif 500w, ./assets/webflow/images/69d3dc83a417451438caec72_Frame%203173.avif 1200w",
"id": "platform-integration-webp",
"windowTitle": "глубокая-интеграция-platform-map.webp",
"imageSrc": "./assets/site/images/69d3dc83a417451438caec72_Frame%203173.avif",
"imageSrcset": "./assets/site/images/69d3dc83a417451438caec72_Frame%203173-p-500.avif 500w, ./assets/site/images/69d3dc83a417451438caec72_Frame%203173.avif 1200w",
"imageAlt": "",
"iconSrc": "./assets/webflow/images/69aab66f030013f2c7177d78_mov-file.avif",
"iconSrc": "./assets/site/images/69aab66f030013f2c7177d78_mov-file.avif",
"iconAlt": "Иконка MOV-файла.",
"folderLabel": "глубокая-интеграция-webflow.webp",
"folderLabel": "глубокая-интеграция-platform-map.webp",
"enabled": true
},
{
"id": "install-setup-and-run",
"windowTitle": "установка-настройка-запуск.jpg",
"imageSrc": "./assets/webflow/images/69d3dc9256192179afb4cfdc_Frame%201.avif",
"imageSrcset": "./assets/webflow/images/69d3dc9256192179afb4cfdc_Frame%201-p-500.avif 500w, ./assets/webflow/images/69d3dc9256192179afb4cfdc_Frame%201.avif 1200w",
"imageSrc": "./assets/site/images/69d3dc9256192179afb4cfdc_Frame%201.avif",
"imageSrcset": "./assets/site/images/69d3dc9256192179afb4cfdc_Frame%201-p-500.avif 500w, ./assets/site/images/69d3dc9256192179afb4cfdc_Frame%201.avif 1200w",
"imageAlt": "",
"iconSrc": "./assets/webflow/images/69aab66f030013f2c7177d78_mov-file.avif",
"iconSrc": "./assets/site/images/69aab66f030013f2c7177d78_mov-file.avif",
"iconAlt": "Иконка MOV-файла.",
"folderLabel": "установка-настройка-запуск.jpg",
"enabled": true
@ -2832,16 +2976,26 @@
"itemTemplate": {
"id": "new-media-item",
"windowTitle": "новый-файл.png",
"imageSrc": "./assets/webflow/images/69d3dc9256192179afb4cfdc_Frame%201.avif",
"imageSrcset": "./assets/webflow/images/69d3dc9256192179afb4cfdc_Frame%201-p-500.avif 500w, ./assets/webflow/images/69d3dc9256192179afb4cfdc_Frame%201.avif 1200w",
"imageSrc": "./assets/site/images/69d3dc9256192179afb4cfdc_Frame%201.avif",
"imageSrcset": "./assets/site/images/69d3dc9256192179afb4cfdc_Frame%201-p-500.avif 500w, ./assets/site/images/69d3dc9256192179afb4cfdc_Frame%201.avif 1200w",
"imageAlt": "",
"iconSrc": "./assets/webflow/images/69aab66f030013f2c7177d78_mov-file.avif",
"iconSrc": "./assets/site/images/69aab66f030013f2c7177d78_mov-file.avif",
"iconAlt": "Иконка MOV-файла.",
"folderLabel": "новый-файл.png",
"enabled": true
}
}
]
],
"seo": {
"targetCluster": "",
"searchIntent": "",
"primaryQuery": "",
"secondaryQueries": "",
"title": "",
"description": "",
"coverageNotes": "",
"indexHint": "section"
}
},
{
"region": "cViz",
@ -2871,8 +3025,8 @@
"id": "hub-access-1782000000000",
"name": "Hub Access",
"bodyHtml": "<p>Единая точка входа: каталог приложений, рабочие пространства, роли, группы и права доступа.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -2880,8 +3034,8 @@
"id": "engine-workflows-1782000000001",
"name": "Engine Workflows",
"bodyHtml": "<p>Нодовые workflow, обработчики данных, интеграционная логика и сборка прикладных контуров.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -2889,8 +3043,8 @@
"id": "ops-cards-1782000000002",
"name": "Ops Cards",
"bodyHtml": "<p>Операционные карточки, статусы, согласования, отчеты агентов и результаты выполнения задач.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -2898,8 +3052,8 @@
"id": "1c-assistant-1782000000003",
"name": "1C Assistant",
"bodyHtml": "<p>Связка с 1C, разбор документов, поиск данных, сценарии согласований и передача результата в Ops.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -2907,8 +3061,8 @@
"id": "tender-agent-1782000000004",
"name": "Tender Agent",
"bodyHtml": "<p>Тендерные подборки, проверка требований, документы, дедлайны и агентные отчеты по процедурам.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -2916,8 +3070,8 @@
"id": "digital-twin-1782000000005",
"name": "Digital Twin",
"bodyHtml": "<p>Цифровые модели объектов, версии, проектный контекст, инженерные данные и связанные рабочие процессы.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -2925,8 +3079,8 @@
"id": "model-viewer-1782000000006",
"name": "Model Viewer",
"bodyHtml": "<p>Просмотр BIM, STEP, STL, 3DGS, облаков точек и инженерных материалов внутри единого контура.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -2934,8 +3088,8 @@
"id": "engineering-core-1782000000007",
"name": "Engineering Core",
"bodyHtml": "<p>Обработка технических форматов, связка моделей с задачами, версиями, отчетами и согласованиями.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -2943,8 +3097,8 @@
"id": "mcp-gateway-1782000000008",
"name": "MCP Gateway",
"bodyHtml": "<p>Контекстный шлюз для агентов: проектные данные, инструменты, кодовая база и внешние сервисы.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -2952,8 +3106,8 @@
"id": "api-contracts-1782000000009",
"name": "API Contracts",
"bodyHtml": "<p>Контракты API, схемы данных, webhooks, события, интеграции и контроль совместимости между модулями.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -2961,8 +3115,8 @@
"id": "kubernetes-1782000000010",
"name": "Kubernetes",
"bodyHtml": "<p>Оркестрация сервисов, изоляция окружений, scaling policies, probes, secrets и управляемый rollout.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -2970,8 +3124,8 @@
"id": "cloudflare-edge-1782000000011",
"name": "Cloudflare Edge",
"bodyHtml": "<p>CDN, WAF, R2-хранилище, edge routing, cache policies и защита публичного периметра.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -2979,8 +3133,8 @@
"id": "zero-trust-1782000000012",
"name": "Zero Trust",
"bodyHtml": "<p>Доступ по принципу least privilege: SSO, MFA, device posture, audit trail и сегментация контуров.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -2988,8 +3142,8 @@
"id": "security-matrix-1782000000013",
"name": "Security Matrix",
"bodyHtml": "<p>Ролевая матрица, группы, политики доступа, ограничения функций и контроль административных действий.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -2997,8 +3151,8 @@
"id": "rbac-policies-1782000000014",
"name": "RBAC Policies",
"bodyHtml": "<p>Права на приложения, рабочие области, действия агентов, документы, отчеты и инженерные данные.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -3006,8 +3160,8 @@
"id": "audit-log-1782000000015",
"name": "Audit Log",
"bodyHtml": "<p>Журнал действий пользователей, агентов и сервисов: изменения, статусы, запросы, публикации и доступы.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -3015,8 +3169,8 @@
"id": "secrets-vault-1782000000016",
"name": "Secrets Vault",
"bodyHtml": "<p>Хранение токенов, ключей API, webhook-секретов и конфигураций интеграций в защищенном контуре.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -3024,8 +3178,8 @@
"id": "postgres-cluster-1782000000017",
"name": "Postgres Cluster",
"bodyHtml": "<p>Основная транзакционная база для проектов, ролей, задач, статусов, событий и конфигураций платформы.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -3033,8 +3187,8 @@
"id": "redis-queue-1782000000018",
"name": "Redis Queue",
"bodyHtml": "<p>Очереди фоновых задач, статусы выполнения, debounce событий, кэширование и быстрые агентные операции.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -3042,8 +3196,8 @@
"id": "vector-index-1782000000019",
"name": "Vector Index",
"bodyHtml": "<p>Индексация проектных материалов, документации, отчетов и контекста для ассистента и поиска.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -3051,8 +3205,8 @@
"id": "local-models-1782000000020",
"name": "Local Models",
"bodyHtml": "<p>Контур локальных моделей для приватных сценариев, инженерных данных и корпоративных ограничений.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -3060,8 +3214,8 @@
"id": "model-routing-1782000000021",
"name": "Model Routing",
"bodyHtml": "<p>Маршрутизация задач между облачными и локальными моделями по типу запроса, правам и стоимости.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -3069,8 +3223,8 @@
"id": "agent-runtime-1782000000022",
"name": "Agent Runtime",
"bodyHtml": "<p>Запуск ассистентов, выполнение действий, контроль инструментов, журналирование и передача результата в Ops.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -3078,8 +3232,8 @@
"id": "observability-1782000000023",
"name": "Observability",
"bodyHtml": "<p>Метрики, логи, трассировка, health checks, алерты и диагностика сервисов платформы.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -3087,8 +3241,8 @@
"id": "backups-1782000000024",
"name": "Backups",
"bodyHtml": "<p>Резервные копии данных, конфигураций, файлов, моделей, журналов и сценариев восстановления.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -3096,8 +3250,8 @@
"id": "ci-cd-1782000000025",
"name": "CI CD",
"bodyHtml": "<p>Сборка, проверки, preview-окружения, controlled rollout, rollback и публикация клиентских модулей.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -3105,8 +3259,8 @@
"id": "terraform-1782000000026",
"name": "Terraform",
"bodyHtml": "<p>Описание инфраструктуры кодом: окружения, сети, хранилища, политики, сервисы и переменные.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -3114,8 +3268,8 @@
"id": "object-storage-1782000000027",
"name": "Object Storage",
"bodyHtml": "<p>Хранилище файлов, изображений, моделей, отчетов, txt-материалов и артефактов рабочих процессов.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -3123,8 +3277,8 @@
"id": "bim-pipeline-1782000000028",
"name": "BIM Pipeline",
"bodyHtml": "<p>Загрузка BIM-моделей, версии, разметка, связи с задачами и публикация в рабочие пространства.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -3132,8 +3286,8 @@
"id": "step-import-1782000000029",
"name": "STEP Import",
"bodyHtml": "<p>Импорт STEP-моделей, технические материалы, карточки проверки и привязка к инженерным задачам.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -3141,8 +3295,8 @@
"id": "stl-review-1782000000030",
"name": "STL Review",
"bodyHtml": "<p>Проверка STL-геометрии, заметки, версии, визуальный контроль и подготовка производственных материалов.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -3150,8 +3304,8 @@
"id": "3dgs-assets-1782000000031",
"name": "3DGS Assets",
"bodyHtml": "<p>Gaussian Splatting сцены, визуальные реконструкции объектов и связь с проектным контекстом.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -3159,8 +3313,8 @@
"id": "las-laz-clouds-1782000000032",
"name": "LAS LAZ Clouds",
"bodyHtml": "<p>Облака точек, съемка, координатные данные, версии и инженерная привязка к задачам.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -3168,8 +3322,8 @@
"id": "webhooks-1782000000033",
"name": "Webhooks",
"bodyHtml": "<p>Событийные сценарии: входящие запросы, уведомления, автоматические действия и передача статусов.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -3177,8 +3331,8 @@
"id": "docs-portal-1782000000034",
"name": "Docs Portal",
"bodyHtml": "<p>Документация, регламенты, инструкции, технические заметки и материалы для команд внедрения.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
},
@ -3186,8 +3340,8 @@
"id": "stack-map-1782000000035",
"name": "Stack Map",
"bodyHtml": "<p>Карта платформенного стека: Hub, Engine, Ops, агенты, данные, интеграции, безопасность и инфраструктура.</p>",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла",
"enabled": true
}
@ -3196,7 +3350,7 @@
"enabled": true,
"iconSrc": "./assets/uploads/images/hero-waitlist/dclogo_white_alpha-mquue8ar.png",
"iconAlt": "Иконка NODE.DC",
"wordmarkSrc": "./assets/webflow/images/69ad96a28b06b7552a339137_logtype.svg?v=node-dc",
"wordmarkSrc": "./assets/site/images/69ad96a28b06b7552a339137_logtype.svg?v=node-dc",
"wordmarkAlt": "Логотип NODE.DC",
"release": "Selfhost платформа ",
"heading": "Запросить DEMO",
@ -3204,7 +3358,7 @@
},
"legal": {
"enabled": true,
"html": "<a href=\"https://docs.google.com/document/d/1vME2w8ZB88HoH-JyvkkiL7exrcMfWhkERfFRlnIeZFA/edit?usp=sharing\" target=\"_blank\">Политика конфиденциальности</a> • <a href=\"https://docs.google.com/document/d/1vME2w8ZB88HoH-JyvkkiL7exrcMfWhkERfFRlnIeZFA/edit?usp=sharing\" target=\"_blank\">Политика cookie</a><br/>© — Все права защищены<br/>2026"
"html": "<a href=\"https://www.dctouch.ru/\" target=\"_blank\" rel=\"noopener noreferrer\">DCTOUCH</a><br/>© NODE.DC, 2026"
},
"filesEnabled": true
},
@ -3259,12 +3413,22 @@
"id": "new-file",
"name": "Новый файл",
"bodyHtml": "Текст файла",
"iconSrc": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/webflow/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/webflow/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconSrc": "./assets/site/images/69a9fb87bc56142371745926_txt-file.avif",
"iconSrcset": "./assets/site/images/69a9fb87bc56142371745926_txt-file-p-500.avif 500w, ./assets/site/images/69a9fb87bc56142371745926_txt-file.avif 1024w",
"iconAlt": "Иконка TXT-файла"
}
}
]
],
"seo": {
"targetCluster": "",
"searchIntent": "",
"primaryQuery": "",
"secondaryQueries": "",
"title": "",
"description": "",
"coverageNotes": "",
"indexHint": "section"
}
},
{
"region": "cViz",
@ -3317,7 +3481,17 @@
"renderAs": "text",
"group": "Dock"
}
]
],
"seo": {
"targetCluster": "",
"searchIntent": "",
"primaryQuery": "",
"secondaryQueries": "",
"title": "",
"description": "",
"coverageNotes": "",
"indexHint": "section"
}
},
{
"region": "cViz",
@ -3331,14 +3505,14 @@
"footer": {
"appIconSrc": "./assets/uploads/images/Logo/dclogo_white_alpha-mqus5if2.png",
"appIconAlt": "Иконка NODE.DC",
"wordmarkSrc": "./assets/webflow/images/69ad96a28b06b7552a339137_logtype.svg?v=node-dc",
"wordmarkSrc": "./assets/site/images/69ad96a28b06b7552a339137_logtype.svg?v=node-dc",
"wordmarkAlt": "Логотип NODE.DC",
"copyrightHtml": "© 2026<a href=\"https://www.dctouch.ru/\" target=\"_blank\"></a>  <a href=\"https://www.dctouch.ru//\" target=\"_blank\">NODE.DC</a>",
"copyrightHtml": "© 2026 — NODE.DC",
"navTitle": "Навигация",
"navLinks": [
{
"label": "Документация",
"href": "https://docs.ssscript.app/",
"href": "https://nodedc.dctouch.ru/",
"target": "",
"enabled": false,
"linkMode": "href",
@ -3397,7 +3571,7 @@
},
{
"label": "GitHub",
"href": "https://www.federic.ooo/s/github",
"href": "https://github.com/dcconstructions",
"target": "",
"enabled": false,
"linkMode": "href",
@ -3405,14 +3579,14 @@
},
{
"label": "Портфолио",
"href": "https://federic.ooo/",
"href": "https://www.dctouch.ru/",
"target": "",
"enabled": false,
"linkMode": "href",
"linkTarget": "_blank"
}
],
"policyHtml": "<a href=\"https://docs.google.com/document/d/1vME2w8ZB88HoH-JyvkkiL7exrcMfWhkERfFRlnIeZFA/edit?usp=sharing\" target=\"_blank\">Политика конфиденциальности</a> &amp; <a href=\"https://docs.google.com/document/d/1vME2w8ZB88HoH-JyvkkiL7exrcMfWhkERfFRlnIeZFA/edit?usp=sharing\" target=\"_blank\">Условия использования</a>",
"policyHtml": "<a href=\"https://www.dctouch.ru/\" target=\"_blank\" rel=\"noopener noreferrer\">DCTOUCH</a>",
"policyEnabled": false
}
},
@ -3539,7 +3713,17 @@
"renderAs": "html",
"group": "Политики"
}
]
],
"seo": {
"targetCluster": "",
"searchIntent": "",
"primaryQuery": "",
"secondaryQueries": "",
"title": "",
"description": "",
"coverageNotes": "",
"indexHint": "section"
}
}
]
}

View File

@ -1,174 +1,12 @@
# Home Page Block Schema Draft
# NODE.DC Home Block Schema
This is the first content model draft for the NODE.DC landing page. It is intentionally content-only. Class names, animation hooks, WebGL targets, and responsive layout remain in renderer code.
The homepage is rendered from `content/pages/home.json` through `tools/render-home.mjs`.
## Page
Core areas:
```json
{
"slug": "home",
"title": "NODE.DC",
"seo": {
"title": "NODE.DC — ИИ-платформа для Webflow",
"description": "..."
},
"globals": {
"notification": {},
"navigation": {},
"dock": {}
},
"blocks": []
}
```
## Common Block Fields
Every block should include:
```json
{
"id": "stable-block-id",
"type": "heroWaitlist",
"enabled": true,
"anchor": "hero",
"adminLabel": "Первый экран"
}
```
## Block Field Drafts
### `heroWaitlist`
```json
{
"type": "heroWaitlist",
"leftFeatures": [
{
"titleLines": ["Ваш Webflow", "ИИ-инструменты"],
"text": "Несколько агентов для проекта Webflow..."
}
],
"appCard": {
"release": "Бета-релиз: июль 2026",
"heading": "Ранний доступ",
"description": "Кастомный код, MCP и ИИ-агенты...",
"form": {
"messagePlaceholder": "Сообщение (необязательно)",
"namePlaceholder": "Ваше имя*",
"emailPlaceholder": "Email для регистрации*",
"submitLabel": "Встать в лист ожидания"
}
}
}
```
### `featureVideo`
```json
{
"type": "featureVideo",
"headingLines": ["Всё в одном", "приложение."],
"intro": "Универсальный инструмент для ИИ-агентов...",
"features": [
{
"eyebrow": "Контекст проекта",
"text": "Подключитесь к MCP..."
}
],
"window": {
"title": "демо-видео.mp4",
"video": "./assets/media/publish-flow.mp4"
}
}
```
### `sliderGallery`
Use for component library and AI modes when they share the same render structure.
```json
{
"type": "sliderGallery",
"variant": "componentLibrary",
"headingLines": ["Агентная", "Компонентная", "библиотека."],
"intro": "Готовая к работе с агентами...",
"items": [
{
"title": "Публикация",
"video": "./assets/media/publish-flow.mp4",
"poster": null
}
]
}
```
### `faq`
```json
{
"type": "faq",
"headingLines": ["Частые", "вопросы", "и ответы"],
"intro": "Подробный обзор возможностей...",
"cta": {
"label": "Полная документация",
"href": "https://docs.ssscript.app/"
},
"items": [
{
"question": "Где размещаются мои файлы?",
"answer": "После сборки файлы загружаются..."
}
]
}
```
### `pricingTable`
```json
{
"type": "pricingTable",
"windowTitle": "тарифы.app",
"plans": [
{
"key": "beta",
"name": "Бета",
"price": "Бесплатно",
"period": "1 месяц",
"cta": {
"label": "Подать заявку в бету",
"href": "beta-apply.html",
"disabled": false
}
}
],
"rows": [
{
"label": "Старт",
"values": {
"beta": "Июнь 2026",
"founder": "Июнь 2026",
"basic": "Июнь 2026",
"advanced": "Июнь 2026"
}
}
],
"notes": []
}
```
### `fileGallery`
```json
{
"type": "fileGallery",
"items": [
{
"title": "публикация-в-один-клик.png",
"kind": "imageWindow",
"image": "./assets/webflow/images/example.avif",
"icon": "mov"
}
]
}
```
- `seo`: page title and description.
- `staticElements`: navigation, dock, assets, and notifications.
- `regions[].blocks`: ordered homepage blocks.
- `content/block-templates/home.json`: sanitized admin presets for creating blocks.
Current product vocabulary is NODE.DC only: Hub, Engine, Ops, AI agents, workflow automation, enterprise data, integrations, 1C assistant, tender/procurement modules, engineering data, BIM/STEP/STL, security, and self-hosted deployment.

View File

@ -1,217 +1,5 @@
# NODE.DC Block Refactor And Admin Plan
# NODE.DC Admin Notes
## Goal
The current admin flow edits the structured homepage model and renders `index.html` with `npm run render:home`.
Turn the current Webflow-exported page into a managed block-based source without changing the visual result.
The first version of the admin should edit only the content layer: text, links, media references, repeated items, and block order. Motion, WebGL, dock behavior, notification behavior, and responsive layout stay in code.
## Non-Negotiables
- Visual parity with the current page on desktop, tablet, and mobile.
- Existing responsive behavior must survive the refactor.
- Existing interactive shell must survive: top bar, dock, floating app windows, WebGL canvas, notifications, sliders, forms.
- Content blocks must be reorderable, duplicatable, and toggleable.
- No direct HTML editing in the admin.
- Optimization must be measured before and after each major step.
## Current Baseline
Measured on June 25, 2026 with Lighthouse against local servers:
| Target | Performance | Accessibility | Best Practices | SEO | FCP | LCP | TBT | Total byte weight |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| `127.0.0.1:8081` working copy | 43 | 91 | 100 | 92 | 2.6s | 14.4s | 1330ms | 24,423 KiB |
| `127.0.0.1:8080` original copy | 60 | 91 | 100 | 92 | 4.3s | 10.1s | 180ms | 17,273 KiB |
Main risk areas:
- `assets/media` is 53 MB.
- Main page has 17 `<video>` tags.
- Largest transferred assets in the working copy are videos.
- `assets/custom/js/app.js` is about 295 KB transferred locally.
- Current local copy loads heavier than the original because CDN compression/range/caching behavior is not equivalent to the local Python server.
Video file compression is outside this refactor scope. The media files can be compressed separately. The refactor is responsible for loading strategy only: offscreen videos should not be eagerly initialized or downloaded when the user does not need them yet.
## Performance Budget
Minimum target after block refactor:
- No regression against the current visual page at desktop/tablet/mobile.
- Lighthouse performance should not drop below the current working baseline during intermediate steps.
- Target after optimization pass: performance 75+ locally, then re-check in production-like hosting.
- LCP target: under 4s in production-like conditions.
- TBT target: under 300ms.
- Initial page must not eagerly initialize every video/module when offscreen.
## Recommended Architecture
Use a two-step migration.
### Phase 1: Static Block Engine
Before connecting a CMS, build a local block renderer:
- `content/pages/home.json` stores page blocks.
- `src/blocks/*` or `templates/blocks/*` render each block type.
- Renderer outputs static HTML or a local app route.
- Current CSS/assets/runtime are reused until visual parity is proven.
Reason: this isolates the risky part. We prove the content model and responsive rendering before adding admin complexity.
### Phase 2: Admin/CMS
Use Payload CMS for the first real admin because its block field maps directly to this requirement:
- one page has an ordered `blocks` array;
- each block has its own fields;
- admin can edit fields without touching markup;
- custom admin controls can later add duplicate/copy/paste affordances.
Admin UX target:
- left sidebar: ordered block list;
- right panel: selected block editor;
- controls: enable/disable, duplicate, copy, paste, move up/down, drag reorder;
- preview link opens the rendered page;
- fields are scoped to content only.
## Block Types
Initial home page block types:
1. `heroWaitlist`
- headings
- intro copy
- side feature cards
- form labels/placeholders/buttons
- app card logo/title/release note
2. `featureVideo`
- heading
- intro copy
- feature list
- video source
- window title
3. `componentLibrary`
- heading
- intro copy
- slider items
- media list
4. `featureCluster`
- heading groups
- rich text groups
- optional center 3D/visual target config
5. `modesGallery`
- heading
- intro copy
- slider/gallery items
6. `faq`
- heading
- intro copy
- CTA link
- questions array
7. `pricingIntro`
- eyebrow
- heading lines
- intro text blocks
8. `pricingTable`
- plans
- rows
- CTA labels/links
- notes
9. `fileGallery`
- media cards
- window titles
- icon labels
10. `waitlistMessages`
- message files
- names
- text contents
11. `footer`
- brand
- copyright
- navigation links
- contact links
- policy links
Global non-block content:
- navigation
- dock
- notification timeline
- WebGL canvas shell
- shared app/window chrome
- forms runtime/API behavior
## Refactor Order
1. Create content model and block schema.
2. Extract current page content into JSON.
3. Render one block at a time from JSON while keeping CSS/classes intact.
4. After each block extraction, compare screenshots at:
- desktop: 1440x900 or current browser size;
- tablet: 1024x768;
- mobile: 390x844.
5. Add Payload CMS only after static JSON rendering is visually stable.
6. Optimize media and runtime after block rendering is stable.
## Phase 1 Implementation Status
Started on June 25, 2026.
Implemented:
- `content/pages/home.json` contains the first ordered block model for the home page.
- `templates/pages/home/shell.html` contains the current Webflow shell with two render slots.
- `templates/pages/home/blocks/` contains exact HTML block chunks extracted from the current localized page.
- `tools/render-home.mjs` renders `index.html` from the JSON model and templates.
- `tools/extract-home-blocks.mjs` can regenerate the first template split from the current `index.html`.
- `tools/admin-server.mjs` and `admin/` provide a local block editor.
- `heroWaitlist` and `featureVideo` expose typed content fields in JSON and the local admin.
- Block templates support `{{text:path}}`, `{{html:path}}`, and `{{attr:path}}` tokens rendered by `tools/render-home.mjs`.
Current guarantee:
- Rendering through `npm run render:home` has byte-for-byte parity with the pre-refactor `index.html`.
Current limitation:
- Only the first two home blocks have typed fields so far. The remaining blocks are still HTML-template blocks. The admin can manage order, enabled state, duplication, copy/paste, IDs, labels, anchors, typed fields where available, and raw block JSON.
- The `c-viz` wrapper is preserved as a region boundary because it is coupled to WebGL/color-flip behavior. Blocks inside that region should stay inside it until the runtime is better understood.
## Optimization Passes
Do after structural parity, not before:
- Make offscreen videos lazy: no eager load for hidden sliders/windows.
- Add posters for videos.
- Keep video source fields editable so compressed replacements can be dropped in without markup changes.
- Prefer CDN/prod-like compression for performance testing.
- Split heavy runtime modules if possible.
- Delay WebGL/model initialization until it is needed.
- Avoid duplicate hidden DOM copies where possible.
- Keep forms and API code out of the first render path when not visible.
## Verification Checklist
For each completed block:
- Desktop screenshot matches current design.
- Tablet screenshot matches current design.
- Mobile screenshot matches current design.
- No text overlap in Russian.
- Block can be disabled without breaking the rest of the page.
- Block can be duplicated and rendered twice.
- Block can move order in the JSON model.
- Lighthouse metrics are recorded after the milestone.
Sanitary rule: admin presets and generated public pages must not contain legacy product names, old external documentation links, or old API origins. Any new SEO/content fields should use the NODE.DC ontology only.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

3
robots.txt Normal file
View File

@ -0,0 +1,3 @@
User-agent: *
Allow: /
Sitemap: https://nodedc.dctouch.ru/sitemap.xml

8
sitemap.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://nodedc.dctouch.ru/</loc>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
</urlset>

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
<section id="{{attr:anchor}}" class="s h-screen">{{#if content.accessCard.enabled}}<div class="app-w"><div id="app-data" data-module="app" class="app-item data"><div data-draggable-area="" class="app-head preview"><div class="app-head-trigs"><button data-close="" aria-label="закрыть окно" class="circle opaque"><div class="x-icon"><div class="line full x-icon rot"></div><div class="line full x-icon"></div></div></button><div class="circle opaque"></div><div class="circle opaque"></div></div></div><div class="flex-v"><div class="flex-v _w-full"><img src="{{attr:content.accessCard.iconSrc}}" loading="lazy" data-module="mouse-follow" alt="{{attr:content.accessCard.iconAlt}}" class="logo app"/><div class="logo-lockup-w"><img src="{{attr:content.accessCard.wordmarkSrc}}" loading="eager" alt="{{attr:content.accessCard.wordmarkAlt}}" class="logo-size"/><div class="notice-txt sm">{{text:content.accessCard.release}}</div></div></div><h3 data-module="text-wave" class="data-he">{{text:content.accessCard.heading}}</h3><h2 class="data-he h2">{{text:content.accessCard.description}}</h2><div class="form-element"><div data-module="form" class="form-w w-form"><form id="wf-form-sign-up" name="wf-form-sign-up" data-name="sign-up" method="get" data-form="" class="form" data-wf-page-id="69b2dc3d81980aaf43601640" data-wf-element-id="4c5736fb-5001-9c71-eaa8-239119d387ce"><textarea placeholder="{{attr:content.form.messagePlaceholder}}" maxlength="5000" id="Message" name="Message" data-name="Message" class="form-field taller w-input"></textarea><input class="form-field w-input" maxlength="256" name="name" data-name="Имя" placeholder="{{attr:content.form.namePlaceholder}}" type="text" id="Name" required=""/><div class="form-item"><input class="form-field w-input" maxlength="256" name="Email" data-name="Email" placeholder="{{attr:content.form.emailPlaceholder}}" type="email" id="Email" required=""/><button data-module="btn" class="form-btn"><div data-module="alpha" class="btn-bg"><div class="clip-12"><div>{{text:content.form.submitLabel}}</div></div></div></button></div></form><div data-success="" class="form-success"><h2 class="data-he sm">{{text:content.form.successHeading}}</h2><h2 class="data-he h2 xs">{{html:content.form.successTextHtml}}</h2><a data-module="btn" href="beta-apply.html" class="form-btn static w-inline-block"><div class="btn-bg gray"><div class="clip-12"><div>{{text:content.form.betaCtaLabel}}</div></div></div></a><h2 class="data-he h2 xs">{{html:content.form.founderIntroHtml}}</h2><a data-module="btn" href="founders.html" class="form-btn static w-inline-block"><div class="btn-bg"><div class="clip-12"><div>{{html:content.form.founderCtaHtml}}</div></div></div></a><link rel="prefetch" href="founders.html"/><div data-url="https://web.ssscript.app/api/founders-spots-remaining" data-module="founders-spots" class="pricing-sm darker-70 is--right">{{text:content.form.spotsLabel}}</div></div></div><div data-failure="" class="form-fail"><h2 class="data-he sm">{{text:content.form.failureHeading}}</h2><h2 class="data-he h2 xs">{{html:content.form.failureTextHtml}}</h2></div></div>{{#if content.legal.enabled}}<div class="notice-txt">{{html:content.legal.html}}</div>{{/if}}</div></div></div>{{/if}}{{#if content.filesEnabled}}<div class="waitlist-coll"><div class="folder-w txts w-dyn-list"><div role="list" class="folder-w txts-windows w-dyn-items">{{#each content.files}}{{#if enabled}}<div role="listitem" class="app-item-dot w-dyn-item"><div id="{{attr:id}}" data-module="app" data-start="closed" class="app-item txt"><div data-draggable-area="" class="app-head"><div class="app-head-trigs"><button data-close="" class="circle red"></button><div class="circle yellow"></div><div class="circle green"></div></div><div class="app-name-w"><img src="{{attr:iconSrc}}" loading="lazy" sizes="(max-width: 1024px) 100vw, 1024px" srcset="{{attr:iconSrcset}}" alt="{{attr:iconAlt}}" class="app-icon"/><div class="app-name-txt">{{text:name}}</div></div></div><div class="text-editable"><div data-module="editable-text" class="txt-text-block editable">{{html:bodyHtml}}</div></div></div></div>{{/if}}{{/each}}</div></div><div class="collection-list-wrapper-3 w-dyn-list"><div data-form="duplicate" role="list" class="folders txts w-dyn-items">{{#each content.files}}{{#if enabled}}<div role="listitem" class="collection-item-2 w-dyn-item"><div tabindex="0" data-target="{{attr:id}}" data-module="apple-icon" class="folder"><img class="image" src="{{attr:iconSrc}}" alt="{{attr:iconAlt}}" sizes="(max-width: 1024px) 100vw, 1024px" data-icon="" loading="eager" srcset="{{attr:iconSrcset}}"/><div data-txt="" class="text-ho"><div class="folder-title">{{text:name}}</div><div class="folder-title lower">.txt</div></div></div></div>{{/if}}{{/each}}</div></div></div>{{/if}}</section>
<section id="{{attr:anchor}}" class="s h-screen">{{#if content.accessCard.enabled}}<div class="app-w"><div id="app-data" data-module="app" class="app-item data"><div data-draggable-area="" class="app-head preview"><div class="app-head-trigs"><button data-close="" aria-label="закрыть окно" class="circle opaque"><div class="x-icon"><div class="line full x-icon rot"></div><div class="line full x-icon"></div></div></button><div class="circle opaque"></div><div class="circle opaque"></div></div></div><div class="flex-v"><div class="flex-v _w-full"><img src="{{attr:content.accessCard.iconSrc}}" loading="lazy" data-module="mouse-follow" alt="{{attr:content.accessCard.iconAlt}}" class="logo app"/><div class="logo-lockup-w"><img src="{{attr:content.accessCard.wordmarkSrc}}" loading="eager" alt="{{attr:content.accessCard.wordmarkAlt}}" class="logo-size"/><div class="notice-txt sm">{{text:content.accessCard.release}}</div></div></div><h3 data-module="text-wave" class="data-he">{{text:content.accessCard.heading}}</h3><h2 class="data-he h2">{{text:content.accessCard.description}}</h2><div class="form-element"><div data-module="form" class="form-w w-form"><form id="ndc-form-sign-up" name="ndc-form-sign-up" data-name="sign-up" method="get" data-form="" class="form" data-ndc-page-id="69b2dc3d81980aaf43601640" data-ndc-element-id="4c5736fb-5001-9c71-eaa8-239119d387ce"><textarea placeholder="{{attr:content.form.messagePlaceholder}}" maxlength="5000" id="Message" name="Message" data-name="Message" class="form-field taller w-input"></textarea><input class="form-field w-input" maxlength="256" name="name" data-name="Имя" placeholder="{{attr:content.form.namePlaceholder}}" type="text" id="Name" required=""/><div class="form-item"><input class="form-field w-input" maxlength="256" name="Email" data-name="Email" placeholder="{{attr:content.form.emailPlaceholder}}" type="email" id="Email" required=""/><button data-module="btn" class="form-btn"><div data-module="alpha" class="btn-bg"><div class="clip-12"><div>{{text:content.form.submitLabel}}</div></div></div></button></div></form><div data-success="" class="form-success"><h2 class="data-he sm">{{text:content.form.successHeading}}</h2><h2 class="data-he h2 xs">{{html:content.form.successTextHtml}}</h2><a data-module="btn" href="beta-apply.html" class="form-btn static w-inline-block"><div class="btn-bg gray"><div class="clip-12"><div>{{text:content.form.betaCtaLabel}}</div></div></div></a><h2 class="data-he h2 xs">{{html:content.form.founderIntroHtml}}</h2><a data-module="btn" href="founders.html" class="form-btn static w-inline-block"><div class="btn-bg"><div class="clip-12"><div>{{html:content.form.founderCtaHtml}}</div></div></div></a><link rel="prefetch" href="founders.html"/><div data-url="https://nodedc.dctouch.ru/api/founders-spots-remaining" data-module="founders-spots" class="pricing-sm darker-70 is--right">{{text:content.form.spotsLabel}}</div></div></div><div data-failure="" class="form-fail"><h2 class="data-he sm">{{text:content.form.failureHeading}}</h2><h2 class="data-he h2 xs">{{html:content.form.failureTextHtml}}</h2></div></div>{{#if content.legal.enabled}}<div class="notice-txt">{{html:content.legal.html}}</div>{{/if}}</div></div></div>{{/if}}{{#if content.filesEnabled}}<div class="waitlist-coll"><div class="folder-w txts w-dyn-list"><div role="list" class="folder-w txts-windows w-dyn-items">{{#each content.files}}{{#if enabled}}<div role="listitem" class="app-item-dot w-dyn-item"><div id="{{attr:id}}" data-module="app" data-start="closed" class="app-item txt"><div data-draggable-area="" class="app-head"><div class="app-head-trigs"><button data-close="" class="circle red"></button><div class="circle yellow"></div><div class="circle green"></div></div><div class="app-name-w"><img src="{{attr:iconSrc}}" loading="lazy" sizes="(max-width: 1024px) 100vw, 1024px" srcset="{{attr:iconSrcset}}" alt="{{attr:iconAlt}}" class="app-icon"/><div class="app-name-txt">{{text:name}}</div></div></div><div class="text-editable"><div data-module="editable-text" class="txt-text-block editable">{{html:bodyHtml}}</div></div></div></div>{{/if}}{{/each}}</div></div><div class="collection-list-wrapper-3 w-dyn-list"><div data-form="duplicate" role="list" class="folders txts w-dyn-items">{{#each content.files}}{{#if enabled}}<div role="listitem" class="collection-item-2 w-dyn-item"><div tabindex="0" data-target="{{attr:id}}" data-module="apple-icon" class="folder"><img class="image" src="{{attr:iconSrc}}" alt="{{attr:iconAlt}}" sizes="(max-width: 1024px) 100vw, 1024px" data-icon="" loading="eager" srcset="{{attr:iconSrcset}}"/><div data-txt="" class="text-ho"><div class="folder-title">{{text:name}}</div><div class="folder-title lower">.txt</div></div></div></div>{{/if}}{{/each}}</div></div></div>{{/if}}</section>

View File

@ -1 +1 @@
<header id="hero" class="s h-screen"><div class="s-cont shorter"><div class="h-full between"><hgroup id="w-node-_42aec6af-ef19-ac2b-f604-ecf49ea735c6-43601640" data-module="hg" data-group="hero" class="hgroup main"><h1 class="main-h"><span class="darker-70">{{text:content.heading.muted}}</span><span>{{text:content.heading.main}}</span></h1><div class="h-txt">{{html:content.introHtml}}</div></hgroup><hgroup id="w-node-ddd50e0c-bdcb-417b-7dc2-7db828a06f46-43601640" data-module="hg" data-group="hero" class="to-left"><h2 class="main-h sm"><span class="darker-70">{{text:content.cards.0.muted}}</span><span>{{text:content.cards.0.main}}</span></h2><div class="h-txt">{{html:content.cards.0.bodyHtml}}</div></hgroup></div><div class="h-full between lower"><hgroup data-module="hg" data-group="hero"><h2 class="main-h sm"><span class="darker-70">{{text:content.cards.1.muted}}</span><span>{{text:content.cards.1.main}}</span></h2><div class="h-txt">{{html:content.cards.1.bodyHtml}}</div></hgroup><hgroup id="w-node-f82ddc17-0624-f069-9963-dda808a65750-43601640" data-module="hg" data-group="hero" class="to-right"><h2 class="main-h sm"><span class="darker-70">{{text:content.cards.2.muted}}</span><span>{{text:content.cards.2.main}}</span></h2><div class="h-txt">{{html:content.cards.2.bodyHtml}}</div></hgroup></div></div>{{#if content.accessCard.enabled}}<div class="app-w"><div id="app-data" data-module="app" class="app-item data"><div data-draggable-area="" class="app-head preview"><div class="app-head-trigs"><button data-close="" aria-label="закрыть окно" class="circle opaque"><div class="x-icon"><div class="line full x-icon rot"></div><div class="line full x-icon"></div></div></button><div class="circle opaque"></div><div class="circle opaque"></div></div></div><div class="flex-v"><div class="flex-v _w-full"><img src="{{attr:content.accessCard.iconSrc}}" loading="lazy" data-module="mouse-follow" alt="{{attr:content.accessCard.iconAlt}}" class="logo app"/><div class="logo-lockup-w"><img src="{{attr:content.accessCard.wordmarkSrc}}" loading="eager" alt="{{attr:content.accessCard.wordmarkAlt}}" class="logo-size"/><div class="notice-txt sm">{{text:content.accessCard.release}}</div></div></div><h3 data-module="text-wave" class="data-he">{{text:content.accessCard.heading}}</h3><h2 class="data-he h2">{{text:content.accessCard.description}}</h2><div class="form-element"><div data-module="form" class="form-w w-form"><form id="wf-form-sign-up" name="wf-form-sign-up" data-name="sign-up" method="get" data-form="" class="form" data-wf-page-id="69b2dc3d81980aaf43601640" data-wf-element-id="4c5736fb-5001-9c71-eaa8-239119d387ce"><textarea placeholder="{{attr:content.form.messagePlaceholder}}" maxlength="5000" id="Message" name="Message" data-name="Message" class="form-field taller w-input"></textarea><input class="form-field w-input" maxlength="256" name="name" data-name="Имя" placeholder="{{attr:content.form.namePlaceholder}}" type="text" id="Name" required=""/><div class="form-item"><input class="form-field w-input" maxlength="256" name="Email" data-name="Email" placeholder="{{attr:content.form.emailPlaceholder}}" type="email" id="Email" required=""/><button data-module="btn" class="form-btn"><div data-module="alpha" class="btn-bg"><div class="clip-12"><div>{{text:content.form.submitLabel}}</div></div></div></button></div></form><div data-success="" class="form-success"><h2 class="data-he sm">{{text:content.form.successHeading}}</h2><h2 class="data-he h2 xs">{{html:content.form.successTextHtml}}</h2><a data-module="btn" href="beta-apply.html" class="form-btn static w-inline-block"><div class="btn-bg gray"><div class="clip-12"><div>{{text:content.form.betaCtaLabel}}</div></div></div></a><h2 class="data-he h2 xs">{{html:content.form.founderIntroHtml}}</h2><a data-module="btn" href="founders.html" class="form-btn static w-inline-block"><div class="btn-bg"><div class="clip-12"><div>{{html:content.form.founderCtaHtml}}</div></div></div></a><link rel="prefetch" href="founders.html"/><div data-url="https://web.ssscript.app/api/founders-spots-remaining" data-module="founders-spots" class="pricing-sm darker-70 is--right">{{text:content.form.spotsLabel}}</div></div></div><div data-failure="" class="form-fail"><h2 class="data-he sm">{{text:content.form.failureHeading}}</h2><h2 class="data-he h2 xs">{{html:content.form.failureTextHtml}}</h2></div></div><div class="notice-txt">{{html:content.legalHtml}}</div></div></div></div>{{/if}}</header>
<header id="hero" class="s h-screen"><div class="s-cont shorter"><div class="h-full between"><hgroup id="ndc-node-_42aec6af-ef19-ac2b-f604-ecf49ea735c6-43601640" data-module="hg" data-group="hero" class="hgroup main"><h1 class="main-h"><span class="darker-70">{{text:content.heading.muted}}</span><span>{{text:content.heading.main}}</span></h1><div class="h-txt">{{html:content.introHtml}}</div></hgroup><hgroup id="ndc-node-ddd50e0c-bdcb-417b-7dc2-7db828a06f46-43601640" data-module="hg" data-group="hero" class="to-left"><h2 class="main-h sm"><span class="darker-70">{{text:content.cards.0.muted}}</span><span>{{text:content.cards.0.main}}</span></h2><div class="h-txt">{{html:content.cards.0.bodyHtml}}</div></hgroup></div><div class="h-full between lower"><hgroup data-module="hg" data-group="hero"><h2 class="main-h sm"><span class="darker-70">{{text:content.cards.1.muted}}</span><span>{{text:content.cards.1.main}}</span></h2><div class="h-txt">{{html:content.cards.1.bodyHtml}}</div></hgroup><hgroup id="ndc-node-f82ddc17-0624-f069-9963-dda808a65750-43601640" data-module="hg" data-group="hero" class="to-right"><h2 class="main-h sm"><span class="darker-70">{{text:content.cards.2.muted}}</span><span>{{text:content.cards.2.main}}</span></h2><div class="h-txt">{{html:content.cards.2.bodyHtml}}</div></hgroup></div></div>{{#if content.accessCard.enabled}}<div class="app-w"><div id="app-data" data-module="app" class="app-item data"><div data-draggable-area="" class="app-head preview"><div class="app-head-trigs"><button data-close="" aria-label="закрыть окно" class="circle opaque"><div class="x-icon"><div class="line full x-icon rot"></div><div class="line full x-icon"></div></div></button><div class="circle opaque"></div><div class="circle opaque"></div></div></div><div class="flex-v"><div class="flex-v _w-full"><img src="{{attr:content.accessCard.iconSrc}}" loading="lazy" data-module="mouse-follow" alt="{{attr:content.accessCard.iconAlt}}" class="logo app"/><div class="logo-lockup-w"><img src="{{attr:content.accessCard.wordmarkSrc}}" loading="eager" alt="{{attr:content.accessCard.wordmarkAlt}}" class="logo-size"/><div class="notice-txt sm">{{text:content.accessCard.release}}</div></div></div><h3 data-module="text-wave" class="data-he">{{text:content.accessCard.heading}}</h3><h2 class="data-he h2">{{text:content.accessCard.description}}</h2><div class="form-element"><div data-module="form" class="form-w w-form"><form id="ndc-form-sign-up" name="ndc-form-sign-up" data-name="sign-up" method="get" data-form="" class="form" data-ndc-page-id="69b2dc3d81980aaf43601640" data-ndc-element-id="4c5736fb-5001-9c71-eaa8-239119d387ce"><textarea placeholder="{{attr:content.form.messagePlaceholder}}" maxlength="5000" id="Message" name="Message" data-name="Message" class="form-field taller w-input"></textarea><input class="form-field w-input" maxlength="256" name="name" data-name="Имя" placeholder="{{attr:content.form.namePlaceholder}}" type="text" id="Name" required=""/><div class="form-item"><input class="form-field w-input" maxlength="256" name="Email" data-name="Email" placeholder="{{attr:content.form.emailPlaceholder}}" type="email" id="Email" required=""/><button data-module="btn" class="form-btn"><div data-module="alpha" class="btn-bg"><div class="clip-12"><div>{{text:content.form.submitLabel}}</div></div></div></button></div></form><div data-success="" class="form-success"><h2 class="data-he sm">{{text:content.form.successHeading}}</h2><h2 class="data-he h2 xs">{{html:content.form.successTextHtml}}</h2><a data-module="btn" href="beta-apply.html" class="form-btn static w-inline-block"><div class="btn-bg gray"><div class="clip-12"><div>{{text:content.form.betaCtaLabel}}</div></div></div></a><h2 class="data-he h2 xs">{{html:content.form.founderIntroHtml}}</h2><a data-module="btn" href="founders.html" class="form-btn static w-inline-block"><div class="btn-bg"><div class="clip-12"><div>{{html:content.form.founderCtaHtml}}</div></div></div></a><link rel="prefetch" href="founders.html"/><div data-url="https://nodedc.dctouch.ru/api/founders-spots-remaining" data-module="founders-spots" class="pricing-sm darker-70 is--right">{{text:content.form.spotsLabel}}</div></div></div><div data-failure="" class="form-fail"><h2 class="data-he sm">{{text:content.form.failureHeading}}</h2><h2 class="data-he h2 xs">{{html:content.form.failureTextHtml}}</h2></div></div><div class="notice-txt">{{html:content.legalHtml}}</div></div></div></div>{{/if}}</header>

File diff suppressed because one or more lines are too long

View File

@ -47,10 +47,10 @@ const mediaRoots = [
writable: true,
},
{
id: "webflow-images",
label: "Webflow images",
rootPath: join(repoRoot, "assets", "webflow", "images"),
urlPrefix: "./assets/webflow/images",
id: "site-images",
label: "Site images",
rootPath: join(repoRoot, "assets", "site", "images"),
urlPrefix: "./assets/site/images",
writable: true,
},
{

View File

@ -1,334 +1,3 @@
import { mkdir, readFile, writeFile, copyFile } from "node:fs/promises";
import { existsSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const sourceDir = process.env.NODEDC_SOURCE_DIR || "/Users/dcconstructions/site-rescue/ssscript.app";
const targetDir = repoRoot;
const pages = ["index.html", "beta-apply.html", "founders.html", "enterprise.html"];
const assetHosts = new Set([
"cdn.prod.website-files.com",
"d3e54v103j8qbb.cloudfront.net",
"ssscript-website.vercel.app",
"ssscript.dev",
"cloud.umami.is",
]);
const assetExtensions = new Set([
".avif",
".css",
".glb",
".gif",
".jpg",
".jpeg",
".js",
".mp4",
".otf",
".png",
".svg",
".ttf",
".webm",
".webp",
".woff",
".woff2",
]);
const assetMap = new Map();
const queue = [];
const failures = [];
function cleanUrl(raw) {
let value = raw.trim();
value = value.replace(/&amp;/g, "&");
value = value.replace(/[),.;]+$/g, "");
if (value.startsWith("//")) value = `https:${value}`;
return value;
}
function extensionFor(url) {
return path.extname(decodeURIComponent(url.pathname)).toLowerCase();
}
function isAssetUrl(url) {
if (!assetHosts.has(url.hostname)) return false;
if (url.href.includes("%7B") || url.href.includes("%7D") || url.href.includes("${")) return false;
if (url.hostname === "cdn.prod.website-files.com" && url.pathname.includes("/vendor/webflow-badge-")) return false;
if (url.hostname === "cloud.umami.is") return url.pathname === "/script.js";
return assetExtensions.has(extensionFor(url));
}
function localAssetPath(url) {
const decodedPath = decodeURIComponent(url.pathname);
const basename = path.basename(decodedPath);
const ext = path.extname(basename).toLowerCase();
if (url.hostname === "cdn.prod.website-files.com") {
if (decodedPath.includes("/css/")) return path.join("assets", "webflow", "css", basename);
if (decodedPath.includes("/js/")) return path.join("assets", "webflow", "js", basename);
return path.join("assets", "webflow", "images", basename);
}
if (url.hostname === "d3e54v103j8qbb.cloudfront.net") {
return path.join("assets", "vendor", basename);
}
if (url.hostname === "ssscript-website.vercel.app") {
if (ext === ".css") return path.join("assets", "custom", "css", basename);
if (ext === ".js") return path.join("assets", "custom", "js", basename);
return path.join("assets", "custom", basename);
}
if (url.hostname === "ssscript.dev") {
return path.join("assets", "media", basename);
}
if (url.hostname === "cloud.umami.is") {
return path.join("assets", "vendor", "umami-script.js");
}
return path.join("assets", url.hostname, basename);
}
function addAsset(rawUrl, baseUrl = undefined) {
let url;
try {
url = new URL(cleanUrl(rawUrl), baseUrl);
} catch {
return null;
}
if (!isAssetUrl(url)) return null;
const canonical = url.href;
if (!assetMap.has(canonical)) {
const localPath = localAssetPath(url);
assetMap.set(canonical, localPath);
queue.push({ url, localPath });
}
return assetMap.get(canonical);
}
function extractUrls(text) {
const urls = [];
const absoluteUrlPattern = /(?:https?:)?\/\/[^\s"'<>\\]+/g;
const cssUrlPattern = /url\((['"]?)([^'")]+)\1\)/g;
const relativeAssetPattern =
/(["'`])((?:\.{1,2}\/|\/)[^"'`?#)]+\.(?:avif|css|gif|glb|jpe?g|js|mp4|otf|png|svg|ttf|webm|webp|woff2?)(?:[?#][^"'`]*)?)\1/g;
for (const match of text.matchAll(absoluteUrlPattern)) {
urls.push(match[0]);
}
for (const match of text.matchAll(cssUrlPattern)) {
urls.push(match[2]);
}
for (const match of text.matchAll(relativeAssetPattern)) {
urls.push(match[2]);
}
return urls;
}
function rel(fromFile, toFile) {
const relative = path.relative(path.dirname(fromFile), toFile).replaceAll(path.sep, "/");
const webPath = relative.split("/").map(encodeURIComponent).join("/");
return webPath.startsWith(".") ? webPath : `./${webPath}`;
}
function pageRel(localPath) {
return `./${localPath.split(path.sep).join("/").split("/").map(encodeURIComponent).join("/")}`;
}
function rewriteUrls(text, fromLocalPath, baseUrl = undefined) {
let output = text;
const isJs = fromLocalPath.endsWith(".js");
const replacements = [...assetMap.entries()].map(([remote, local]) => {
const remoteUrl = new URL(remote);
return {
remote,
local,
relative: isJs ? pageRel(local) : rel(path.join(targetDir, fromLocalPath), path.join(targetDir, local)),
noQuery: `${remoteUrl.origin}${remoteUrl.pathname}`,
};
});
for (const item of replacements) {
output = output.split(item.remote).join(item.relative);
output = output.split(item.remote.replaceAll("&", "&amp;")).join(item.relative);
output = output.split(item.noQuery).join(item.relative);
output = output.split(item.noQuery.replaceAll("&", "&amp;")).join(item.relative);
}
if (baseUrl) {
output = output.replace(/url\((['"]?)([^'")]+)\1\)/g, (full, quote, value) => {
const local = addAsset(value, baseUrl);
if (!local) return full;
const localRef = isJs ? pageRel(local) : rel(path.join(targetDir, fromLocalPath), path.join(targetDir, local));
return `url(${quote}${localRef}${quote})`;
});
output = output.replace(
/(["'`])((?:\.{1,2}\/|\/)[^"'`?#)]+\.(?:avif|css|gif|glb|jpe?g|js|mp4|otf|png|svg|ttf|webm|webp|woff2?)(?:[?#][^"'`]*)?)\1/g,
(full, quote, value) => {
const local = addAsset(value, baseUrl);
if (!local) return full;
const localRef = isJs ? pageRel(local) : rel(path.join(targetDir, fromLocalPath), path.join(targetDir, local));
return `${quote}${localRef}${quote}`;
}
);
}
if (fromLocalPath.endsWith(".html")) {
output = output
.replace(/<link href="https:\/\/cdn\.prod\.website-files\.com" rel="preconnect"[^>]*>/g, "")
.replace(/<link rel="preconnect" href="https:\/\/ssscript\.app\/cdn\.prod\.website-files\.com"[^>]*>/g, "")
.replaceAll("https://localhost:6545/app.js", "./assets/custom/js/app.js");
if (!output.includes("window.__API_ORIGIN__")) {
output = output.replace(
"<script>\n(function(d,h,host){",
'<script>\nwindow.__API_ORIGIN__="https://ssscript-website.vercel.app";\n(function(d,h,host){'
);
}
}
output = output
.replace(/\s+integrity="[^"]*"/g, "")
.replace(/\s+crossorigin="anonymous"/g, "")
.replace(/\s+crossOrigin="anonymous"/g, "");
return output;
}
async function downloadAsset(item) {
const outPath = path.join(targetDir, item.localPath);
await mkdir(path.dirname(outPath), { recursive: true });
const ext = extensionFor(item.url);
if (existsSync(outPath) && ext !== ".css" && ext !== ".js") return;
const response = await fetch(item.url.href, {
redirect: "follow",
headers: {
"User-Agent": "Mozilla/5.0 NodeDcLocalizer",
},
});
if (!response.ok) {
failures.push(`${response.status} ${item.url.href}`);
return;
}
const buffer = Buffer.from(await response.arrayBuffer());
await writeFile(outPath, buffer);
}
async function main() {
await mkdir(targetDir, { recursive: true });
await mkdir(path.join(targetDir, "originals"), { recursive: true });
const pageSources = new Map();
for (const page of pages) {
const sourcePath = path.join(sourceDir, page);
const source = await readFile(sourcePath, "utf8");
pageSources.set(page, source);
await copyFile(sourcePath, path.join(targetDir, "originals", page));
const origPath = path.join(sourceDir, page.replace(/\.html$/, ".orig"));
if (existsSync(origPath)) {
await copyFile(origPath, path.join(targetDir, "originals", path.basename(origPath)));
}
for (const rawUrl of extractUrls(source)) addAsset(rawUrl);
}
for (let index = 0; index < queue.length; index += 1) {
const item = queue[index];
await downloadAsset(item);
const ext = extensionFor(item.url);
if (ext === ".css" || ext === ".js") {
const localPath = path.join(targetDir, item.localPath);
if (!existsSync(localPath)) continue;
const source = await readFile(localPath, "utf8");
for (const rawUrl of extractUrls(source)) addAsset(rawUrl, item.url.href);
}
}
for (const [remote, local] of assetMap.entries()) {
const url = new URL(remote);
const ext = extensionFor(url);
if (ext !== ".css" && ext !== ".js") continue;
const localPath = path.join(targetDir, local);
if (!existsSync(localPath)) continue;
const source = await readFile(localPath, "utf8");
const rewritten = rewriteUrls(source, local, remote);
await writeFile(localPath, rewritten);
}
for (const [page, source] of pageSources.entries()) {
const rewritten = rewriteUrls(source, page);
await writeFile(path.join(targetDir, page), rewritten);
}
const manifest = {
generatedAt: new Date().toISOString(),
sourceDir,
targetDir,
pages,
assets: [...assetMap.entries()].map(([url, localPath]) => ({ url, localPath })),
failures,
};
await writeFile(path.join(targetDir, "asset-manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`);
await writeFile(
path.join(targetDir, "README.md"),
`# NODE.DC Local Working Copy
This repository contains the current NODE.DC base site: a structured local copy of the recovered Webflow page, localized to Russian and prepared for the block/CMS refactor.
The rebuild source is \`${sourceDir}\`.
## Run
\`\`\`bash
cd ${targetDir}
python3 -m http.server 8081 --bind 127.0.0.1
\`\`\`
Open http://127.0.0.1:8081/
## Structure
- \`index.html\`, \`beta-apply.html\`, \`founders.html\`, \`enterprise.html\` - editable local pages.
- \`assets/webflow\` - Webflow CSS, JS, and image assets.
- \`assets/custom\` - custom NODE.DC CSS/JS published on Vercel.
- \`assets/media\` - downloaded videos from ssscript.dev.
- \`assets/vendor\` - third-party browser scripts.
- \`docs/\` - refactor notes, block model draft, and admin plan.
- \`originals\` - untouched files copied from the original wget download.
- \`asset-manifest.json\` - source URL to local file mapping.
- \`tools/build-local-copy.mjs\` - rebuild script. It uses \`NODEDC_SOURCE_DIR\` when provided, otherwise the original local rescue folder.
- \`tools/localize-ru.mjs\` - Russian localization and NODE.DC brand patch script.
External product/API/documentation links are intentionally left as external URLs.
`
);
console.log(`Pages: ${pages.length}`);
console.log(`Assets mapped: ${assetMap.size}`);
console.log(`Failures: ${failures.length}`);
for (const failure of failures) console.log(`FAILED ${failure}`);
}
main().catch((error) => {
console.error(error);
process.exit(1);
});
throw new Error(
"Legacy site import is disabled. NODE.DC PromoSite now uses content/pages/home.json, templates/pages/home, and npm run render:home.",
);

View File

@ -1,941 +1,3 @@
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");
let html = await readFile(sourcePath, "utf8");
html = html.replace(/\n*<script id="node-dc-static-config"(?: type="application\/json")?>[\s\S]*?<\/script>\n*/g, "\n");
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 productSystemHtml = html.slice(cVizOpenEnd, pricingIntroStart);
const productSectionStarts = [...productSystemHtml.matchAll(/<section\b/g)].map((match) => match.index);
if (productSectionStarts.length < 5) {
throw new Error(`Expected at least 5 product-system sections, found ${productSectionStarts.length}`);
}
const productSections = productSectionStarts.map((start, index) => {
const end = productSectionStarts[index + 1] ?? productSystemHtml.length;
return productSystemHtml.slice(start, end);
});
let 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: "theses-request-demo.html",
adminLabel: "ТЕЗИСЫ + ЗАПРОС ДЕМО",
anchor: "hero",
html: html.slice(heroStart, featuresStart),
},
{
region: "beforeCViz",
id: "feature-video",
type: "staticHtml",
template: "project-video-first.html",
adminLabel: "PROJECT VIDEO - FIRST",
anchor: "features",
html: html.slice(featuresStart, cVizStart),
},
{
region: "cViz",
id: "component-library",
type: "staticHtml",
template: "project-video-detail.html",
adminLabel: "PROJECT VIDEO - DETAIL",
anchor: null,
html: productSections[0],
},
{
region: "cViz",
id: "ai-modes",
type: "staticHtml",
template: "project-composition.html",
adminLabel: "СОСТАВ ПРОЕКТА",
anchor: null,
html: productSections[1],
},
{
region: "cViz",
id: "faq",
type: "staticHtml",
template: "faq.html",
adminLabel: "Частые вопросы и ответы",
anchor: null,
html: productSections[2],
},
{
region: "cViz",
id: "publish-cta",
type: "staticHtml",
template: "publish-cta.html",
adminLabel: "Финальный CTA перед тарифами",
anchor: "hero",
html: productSections[3],
},
{
region: "cViz",
id: "pricing-table",
type: "staticHtml",
template: "pricing-table.html",
adminLabel: "Тарифная таблица",
anchor: "pricing-wrap",
html: productSections[4],
},
{
region: "cViz",
id: "pricing-intro",
type: "staticHtml",
template: "pricing-intro.html",
adminLabel: "Интро тарифов",
anchor: "waitlist",
html: html.slice(pricingIntroStart, waitlistFilesStart),
},
{
region: "cViz",
id: "request-demo-txt-files",
type: "staticHtml",
template: "request-demo-txt-files.html",
adminLabel: "Запрос DEMO и 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),
},
];
function getByPath(source, path) {
return path.split(".").reduce((value, key) => value?.[key], source);
}
function inferFieldGroup(path) {
if (path.startsWith("content.navigation.")) return "Шапка";
if (path.startsWith("content.notifications.")) return "Уведомления";
if (path.startsWith("content.dock.")) return "Dock";
if (path.startsWith("content.assets.")) return "Модель и логотипы";
if (path.startsWith("content.cards.")) return "Тезисы первого экрана";
if (path.startsWith("content.app.")) return "Карточка раннего доступа";
if (path.startsWith("content.form.")) return "Форма";
if (path === "content.legalHtml") return "Юридический блок";
if (path.startsWith("content.legal.")) return "Юридический блок";
if (path === "content.filesEnabled" || path.startsWith("content.files.")) return "TXT-файлы";
if (path === "content.windowTitle" || path === "content.videoSrc") return "Видео-окно";
if (path.startsWith("content.features.")) return "Пункты";
if (path.startsWith("content.sliderItems.")) return "Слайдер";
if (path.startsWith("content.oneClick.")) return "Один клик";
if (path.startsWith("content.mcp.")) return "MCP";
if (path.startsWith("content.faq.")) return "FAQ";
if (path.startsWith("content.cta.")) return "CTA";
if (path.startsWith("content.plans.") || path.startsWith("content.pricing.")) return "Тарифы";
if (path.startsWith("content.heading.") || path === "content.introHtml") return "Заголовок";
return "Контент";
}
function field(path, label, kind = "text", renderAs = kind === "html" ? "html" : "text", group = null, options = {}) {
return { path, label, kind, renderAs, group: group || inferFieldGroup(path), ...options };
}
function tokenizeBlock(block, definition) {
let html = block.html;
for (const editableField of definition.editableFields) {
const value = getByPath({ content: definition.content }, editableField.path);
const token = `{{${editableField.renderAs}:${editableField.path}}}`;
if (typeof value !== "string") {
throw new Error(`Token field must be a string: ${block.id}.${editableField.path}`);
}
if (!html.includes(value)) {
throw new Error(`Could not find "${value}" while tokenizing ${block.id}`);
}
html = editableField.replaceAll ? html.split(value).join(token) : html.replace(value, token);
}
return {
...block,
type: definition.type,
html,
content: definition.content,
editableFields: definition.editableFields,
};
}
const demoSliderItems = [
{ videoSrc: "./assets/media/various-modes.mp4", title: "Переключение режимов ИИ" },
{ videoSrc: "./assets/media/project-flow.mp4", title: "Создание нового проекта" },
{ videoSrc: "./assets/media/ai-dock-mode.mp4", title: "ИИ-док" },
{ videoSrc: "./assets/media/account.mp4", title: "Аккаунт" },
{ videoSrc: "./assets/media/code-edits.mp4", title: "Редактирование кода с ИИ" },
{ videoSrc: "./assets/media/dev-server.mp4", title: "Dev-сервер с ИИ" },
{ videoSrc: "./assets/media/webflow-mcp.mp4", title: "Webflow MCP" },
{ videoSrc: "./assets/media/publish-flow.mp4", title: "Публикация" },
];
function sliderEditableFields(prefix = "content.sliderItems", labelPrefix = "Слайд") {
return demoSliderItems.flatMap((_, index) => [
field(`${prefix}.${index}.videoSrc`, `${labelPrefix} ${index + 1}: видео`, "media", "attr"),
field(`${prefix}.${index}.title`, `${labelPrefix} ${index + 1}: подпись`),
]);
}
const typedBlockDefinitions = {
"hero-waitlist": {
type: "heroWaitlist",
content: {
heading: {
muted: "Ваш Webflow ",
main: "ИИ-инструменты",
},
introHtml:
'Несколько агентов для проекта Webflow: от <span class="darker-70">Designer</span> до <span class="darker-70">кастомного кода.</span>',
cards: [
{
muted: "Полный",
main: "агентный контроль",
bodyHtml:
'Интеграция с <span class="darker-70">Webflow MCP,</span> мультиагентный режим позволяет работать с <span class="darker-70">несколькими экземплярами</span> параллельно.',
},
{
muted: "Компонентная",
main: "библиотека",
bodyHtml:
'Собирайте с учетом <span class="darker-70">ИИ-агентов</span> разные компонентные заготовки дают основу, а стиль и сложную функциональность можно дорабатывать промптами.',
},
{
muted: "Деплой ",
main: "пайплайн",
bodyHtml:
'<span class="darker-70">Деплой в один клик,</span> непрерывный пайплайн, мгновенный откат и быстрая <span class="darker-70">доставка через CDN.</span>',
},
],
app: {
iconAlt: "Иконка NODE.DC",
logoAlt: "Логотип NODE.DC",
release: "Бета-релиз: июль 2026",
heading: "Ранний доступ",
description: "Кастомный код, MCP и ИИ-агенты для Webflow в одном рабочем контуре.",
},
form: {
messagePlaceholder: "Сообщение (необязательно)",
namePlaceholder: "Ваше имя*",
emailPlaceholder: "Email для регистрации*",
submitLabel: "Встать в лист ожидания",
successHeading: "Заявка принята. Спасибо.",
successTextHtml: "Мы скоро свяжемся. <br/>",
betaCtaLabel: "Подать заявку в бету?",
founderIntroHtml: "Или оформить ранний Founder-доступ<br/>",
founderCtaHtml: 'Оформить Founder-доступ <span class="darker-70">350€</span>',
spotsLabel: "До 200 мест",
failureHeading: "Не получилось",
failureTextHtml: "Что-то пошло не так. <br/>Попробуйте ещё раз чуть позже.<br/>",
},
legalHtml:
'<a href="https://docs.google.com/document/d/1vME2w8ZB88HoH-JyvkkiL7exrcMfWhkERfFRlnIeZFA/edit?usp=sharing" target="_blank">Политика конфиденциальности</a> • <a href="https://docs.google.com/document/d/1vME2w8ZB88HoH-JyvkkiL7exrcMfWhkERfFRlnIeZFA/edit?usp=sharing" target="_blank">Политика cookie</a><br/>© — Все права защищены<br/>2026',
},
editableFields: [
field("content.heading.muted", "Hero: приглушённая строка"),
field("content.heading.main", "Hero: основная строка"),
field("content.introHtml", "Hero: вводный текст", "html"),
field("content.cards.0.muted", "Карточка 1: приглушённая строка"),
field("content.cards.0.main", "Карточка 1: основная строка"),
field("content.cards.0.bodyHtml", "Карточка 1: текст", "html"),
field("content.cards.1.muted", "Карточка 2: приглушённая строка"),
field("content.cards.1.main", "Карточка 2: основная строка"),
field("content.cards.1.bodyHtml", "Карточка 2: текст", "html"),
field("content.cards.2.muted", "Карточка 3: приглушённая строка"),
field("content.cards.2.main", "Карточка 3: основная строка"),
field("content.cards.2.bodyHtml", "Карточка 3: текст", "html"),
field("content.app.iconAlt", "App: alt иконки", "text", "attr"),
field("content.app.logoAlt", "App: alt логотипа", "text", "attr"),
field("content.app.release", "App: релиз"),
field("content.app.heading", "App: заголовок"),
field("content.app.description", "App: описание"),
field("content.form.messagePlaceholder", "Форма: placeholder сообщения", "text", "attr"),
field("content.form.namePlaceholder", "Форма: placeholder имени", "text", "attr"),
field("content.form.emailPlaceholder", "Форма: placeholder email", "text", "attr"),
field("content.form.submitLabel", "Форма: кнопка"),
field("content.form.successHeading", "Форма: успех, заголовок"),
field("content.form.successTextHtml", "Форма: успех, текст", "html"),
field("content.form.betaCtaLabel", "Форма: CTA беты"),
field("content.form.founderIntroHtml", "Форма: Founder intro", "html"),
field("content.form.founderCtaHtml", "Форма: Founder CTA", "html"),
field("content.form.spotsLabel", "Форма: лимит мест"),
field("content.form.failureHeading", "Форма: ошибка, заголовок"),
field("content.form.failureTextHtml", "Форма: ошибка, текст", "html"),
field("content.legalHtml", "Юридический текст", "html"),
],
},
"feature-video": {
type: "featureVideo",
content: {
windowTitle: "демо-видео.mp4",
videoSrc: "./assets/media/publish-flow.mp4",
heading: {
muted: "Всё в одном",
main: "приложение.",
},
introHtml:
'Универсальный инструмент для <span class="darker-70">ИИ-агентов</span> в вашем Webflow-проекте.',
features: [
{
eyebrow: "Контекст проекта",
bodyHtml:
'Подключитесь к <span class="darker-70">MCP</span> чтобы агенты получили полный контекст вашей сборки.',
},
{
eyebrow: "Группы агентов",
bodyHtml:
'Используйте <span class="darker-70">несколько агентов параллельно</span> чтобы добавлять кастомный код, анимации и функциональность.',
},
{
eyebrow: "Среда разработки",
bodyHtml:
'Используйте быстрые <span class="darker-70">Bun</span> сборки и автообновление для вашего <span class="darker-70">кастомного кода.</span>',
},
{
eyebrow: "Деплой в один клик",
bodyHtml:
'<span class="darker-70">Нажмите и публикуйте</span> кастомный код, написанный ИИ, чтобы он стал доступен всем посетителям сайта.',
},
],
},
editableFields: [
field("content.windowTitle", "Окно: название файла"),
field("content.videoSrc", "Окно: видео", "media", "attr"),
field("content.heading.muted", "Заголовок: приглушённая строка"),
field("content.heading.main", "Заголовок: основная строка"),
field("content.introHtml", "Вводный текст", "html"),
field("content.features.0.eyebrow", "Пункт 1: подпись"),
field("content.features.0.bodyHtml", "Пункт 1: текст", "html"),
field("content.features.1.eyebrow", "Пункт 2: подпись"),
field("content.features.1.bodyHtml", "Пункт 2: текст", "html"),
field("content.features.2.eyebrow", "Пункт 3: подпись"),
field("content.features.2.bodyHtml", "Пункт 3: текст", "html"),
field("content.features.3.eyebrow", "Пункт 4: подпись"),
field("content.features.3.bodyHtml", "Пункт 4: текст", "html"),
],
},
"component-library": {
type: "componentLibrary",
content: {
heading: {
muted: "Агентная",
middle: "Компонентная",
main: "библиотека.",
},
introHtml:
'Готовая к работе с агентами <span class="darker-70">компонентная</span> библиотека: добавление в один клик через приложение NODE.DC для Webflow и дальнейшая настройка под проект.',
sliderItems: demoSliderItems,
oneClick: {
heading: {
muted: "Один",
middle: "клик",
main: "добавления.",
},
introHtml: 'С помощью <span class="darker-70">расширения NODE.DC для Webflow.</span> ',
items: [
{
eyebrow: "Понимание компонентов",
bodyHtml:
'Используйте <span class="darker-70">несколько агентов параллельно</span> чтобы добавлять кастомный код, анимации и функциональность.',
},
{
eyebrow: "Быстрый старт",
bodyHtml:
'Используйте быстрые <span class="darker-70">Bun</span> сборки и автообновление для вашего <span class="darker-70">кастомного кода.</span>',
},
],
},
mcp: {
heading: {
muted: "Получите",
middle: "более сильный",
main: "MCP.",
},
introHtml: "Полностью переработанный и более быстрый MCP-сервер. ",
items: [
{
eyebrow: "Понимание Webflow",
bodyHtml:
'Используйте <span class="darker-70">несколько агентов параллельно</span> чтобы добавлять кастомный код, анимации и функциональность.',
},
],
},
},
editableFields: [
field("content.heading.muted", "Заголовок: приглушённая строка"),
field("content.heading.middle", "Заголовок: средняя строка"),
field("content.heading.main", "Заголовок: основная строка"),
field("content.introHtml", "Вводный текст", "html"),
...sliderEditableFields(),
field("content.oneClick.heading.muted", "Один клик: приглушённая строка"),
field("content.oneClick.heading.middle", "Один клик: средняя строка"),
field("content.oneClick.heading.main", "Один клик: основная строка"),
field("content.oneClick.introHtml", "Один клик: вводный текст", "html"),
field("content.oneClick.items.0.eyebrow", "Один клик: пункт 1, подпись"),
field("content.oneClick.items.0.bodyHtml", "Один клик: пункт 1, текст", "html"),
field("content.oneClick.items.1.eyebrow", "Один клик: пункт 2, подпись"),
field("content.oneClick.items.1.bodyHtml", "Один клик: пункт 2, текст", "html"),
field("content.mcp.heading.muted", "MCP: приглушённая строка"),
field("content.mcp.heading.middle", "MCP: средняя строка"),
field("content.mcp.heading.main", "MCP: основная строка"),
field("content.mcp.introHtml", "MCP: вводный текст", "html"),
field("content.mcp.items.0.eyebrow", "MCP: пункт 1, подпись"),
field("content.mcp.items.0.bodyHtml", "MCP: пункт 1, текст", "html"),
],
},
"ai-modes": {
type: "aiModes",
content: {
heading: {
muted: "Гибкие",
main: "режимы ИИ.",
},
introHtml: 'Разные <span class="darker-70">режимы</span> и <span class="darker-70">модели</span> для разных задач.',
bottomInfo: {
heading: {
muted: "Гибкие",
main: "режимы ИИ.",
},
introHtml: 'Разные <span class="darker-70">режимы</span> и <span class="darker-70">модели</span> для разных задач.',
},
sliderItems: demoSliderItems,
},
editableFields: [
field("content.heading.muted", "Приглушённая строка", "text", "text", "Верхний информационный блок"),
field("content.heading.main", "Основная строка", "text", "text", "Верхний информационный блок"),
field("content.introHtml", "Вводный текст", "html", "html", "Верхний информационный блок"),
field("content.bottomInfo.heading.muted", "Приглушённая строка", "text", "text", "Нижний информационный блок"),
field("content.bottomInfo.heading.main", "Основная строка", "text", "text", "Нижний информационный блок"),
field("content.bottomInfo.introHtml", "Вводный текст", "html", "html", "Нижний информационный блок"),
...sliderEditableFields(),
],
},
faq: {
type: "faqSection",
content: {
heading: {
muted: "Частые",
middle: "вопросы",
main: "и ответы",
},
introHtml: '<span class="darker-70">Подробный обзор</span> возможностей, логики работы и внутреннего устройства.',
docsHref: "https://docs.ssscript.app/",
docsLabel: "Полная документация",
windowTitle: "FAQ",
faq: [
{
question: "Это влияет на производительность?",
answerHtml:
"<p>Как и любой кастомный код, это добавляет только ту нагрузку, которую несёт сам код. </p><p>Все файлы собираются, минифицируются и сжимаются, чтобы минимально влиять на проект и не плодить внешние библиотеки и CDN-скрипты.</p>",
},
{
question: "Где размещаются мои файлы?",
answerHtml:
"<p>После сборки файлы загружаются в Cloudflare R2 и отдаются через CDN-ссылку на выделенном домене проекта, чтобы получить быстрый и совместимый с Webflow результат.</p>",
},
{
question: "Что делать, если я опубликовал не те файлы?",
answerHtml:
"<p>В панели деплоя есть мгновенный откат. </p><p>Нажмите откат, верните предыдущую рабочую версию, исправьте локально и опубликуйте заново.</p>",
},
{
question: "Что такое ИИ-агент Webflow?",
answerHtml:
"<p>Помимо <strong>@plan</strong>, <strong>@build</strong> и <strong>@explore</strong> стандартных режимов агентов мы готовим <strong>@webflow</strong> режим, специально заточенный под Webflow: структуру проекта, взаимодействие с ним и эффективную разработку.</p>",
},
{
question: "Что такое собственная база знаний?",
answerHtml:
"<p>Основано на многолетнем опыте написания кастомного кода для Webflow-анимаций. </p><p>WebGL, Three.js, GSAP и кастомная функциональность собраны в набор практик, пайплайнов и модульную систему, чтобы всё работало согласованно. </p><p>Также доступна библиотека компонентов: выберите компонент, добавьте в проект и попросите ИИ адаптировать его под задачу.</p>",
},
{
question: "Как использовать это с Webflow?",
answerHtml:
"<p>При запуске <em>NODE.DC</em> доступна отдельная вкладка для встраивания. </p><p>В зависимости от задачи можно получить скрипты для конкретной страницы или единый app.js для всего проекта. </p><p>Сгенерированная ссылка содержит и продакшен-версию, следующую за URL деплоя, и локальную версию для предпросмотра без влияния на посетителей сайта.</p>",
},
{
question: "Какие ИИ-модели доступны?",
answerHtml:
"<p>Мы используем основные доступные модели: <strong>OpenAI</strong> до <strong>Claude</strong> до <strong>Gemini</strong>. </p><p>Оркестратор выбирает модель под задачу автоматически, но вы можете вручную переключиться на нужную модель.</p>",
},
{
question: "Как это работает?",
answerHtml:
"<p>Во время работы NODE.DC выполняет две задачи параллельно.</p><p>Поднимает локальную среду разработки на Bun, синхронизирует JavaScript и TypeScript, включает HMR и держит webhook для автоматического обновления сайта при изменениях. </p><p>Одновременно работает оркестратор ИИ-агентов: обрабатывает запросы, меняет код, проверяет состояние сборки и при необходимости обновляет проект Webflow.</p>",
},
{
question: "Как работает публикация?",
answerHtml:
"<p>Процесс многоэтапный: сначала Bun собирает, сжимает, минифицирует и подготавливает проект вместе с библиотеками.</p><p>Затем файлы отправляются в CDN: <code>cdn.node.dc/{USERNAME}/{PROJECT-NAME}</code>. Ссылка уже содержит нужную структуру: вы видите локальную версию, а пользователи получают опубликованный скрипт.</p>",
},
{
question: "Это работает с MCP?",
answerHtml:
"<p>Система изначально рассчитана на работу с Webflow MCP. </p><p>Подключите проект и авторизацию, чтобы ИИ-агенты получили доступ к Webflow-сборке. Подключение приложения в Webflow Designer даёт агентам доступ к контенту, элементам, стилям и CSS, а кастомный код получает полный контекст проекта.</p>",
},
],
},
editableFields: [
field("content.heading.muted", "Заголовок: приглушённая строка"),
field("content.heading.middle", "Заголовок: средняя строка"),
field("content.heading.main", "Заголовок: основная строка"),
field("content.introHtml", "Вводный текст", "html"),
field("content.docsHref", "Документация: ссылка", "url", "attr"),
field("content.docsLabel", "Документация: кнопка"),
field("content.windowTitle", "Окно: название"),
...Array.from({ length: 10 }, (_, index) => [
field(`content.faq.${index}.question`, `FAQ ${index + 1}: вопрос`, "text", "text", "FAQ", {
replaceAll: true,
}),
field(`content.faq.${index}.answerHtml`, `FAQ ${index + 1}: ответ`, "html", "html", "FAQ"),
]).flat(),
],
},
"publish-cta": {
type: "publishCta",
content: {
cta: {
heading: {
muted: "Подключить.",
middle: "Поставить задачу.",
main: "Опубликовать.",
},
intro: "Единый путь от разработки до продакшена.",
detailHtml:
'<span class="darker-70">Настройка примерно в 12 кликов:</span> от скачивания приложения до запуска в проекте.',
href: "index.html#hero",
buttonLabel: "Встать в лист ожидания",
},
},
editableFields: [
field("content.cta.heading.muted", "CTA: приглушённая строка"),
field("content.cta.heading.middle", "CTA: средняя строка"),
field("content.cta.heading.main", "CTA: основная строка"),
field("content.cta.intro", "CTA: вводный текст", "text", "text", "CTA"),
field("content.cta.detailHtml", "CTA: пояснение", "html", "html", "CTA"),
field("content.cta.href", "CTA: ссылка", "url", "attr", "CTA"),
field("content.cta.buttonLabel", "CTA: кнопка", "text", "text", "CTA"),
],
},
"pricing-table": {
type: "pricingTable",
content: {
pricing: {
eyebrow: "Тарифы",
heading: {
muted: "Код",
middle: "для No-Code,",
main: "усиленный ИИ.",
},
introHtml: "Подписки на приложение с<br/>включённым объёмом в каждом тарифе. <br/>",
tokenBillingHtml: '<span class="darker-70">Токенная</span> оплата сверх лимитов.',
founderLimitHtml: '<span class="darker-70">200 Founder</span> мест с фиксированной ценой на первый год.',
windowTitle: "Тарифы.app",
discountLabel: "Скидка 50%",
betaHref: "beta-apply.html",
founderHref: "founders.html",
betaButtonLabel: "Подать заявку в бету",
founderButtonLabel: "Оформить Founder-доступ",
basicButtonLabel: "Выбрать Basic",
advancedButtonLabel: "Выбрать Advanced",
noteHtml: "(*) Условия могут меняться по ходу тестирования, но мы постараемся сохранить заявленные обещания.",
enterpriseNoteHtml:
'<a href="enterprise.html" class="darker-40">Enterprise</a> BYOK-тарифы будут доступны с сентября 2026.',
},
plans: [
{
name: "Бета",
priceHtml: "Бесплатно",
period: "1 месяц",
},
{
name: "Founder ",
priceHtml: '<span class="darker-70 mr-xs lighter">€</span>310',
period: "/ год, навсегда",
},
{
name: "Basic",
priceHtml: '<span class="darker-70 mr-xs lighter">€</span>20',
period: "/ месяц",
},
{
name: "Advanced",
priceHtml: '<span class="darker-70 mr-xs lighter">€</span>50',
period: "/ месяц",
},
],
},
editableFields: [
field("content.pricing.eyebrow", "Тарифы: надзаголовок"),
field("content.pricing.heading.muted", "Тарифы: приглушённая строка"),
field("content.pricing.heading.middle", "Тарифы: средняя строка"),
field("content.pricing.heading.main", "Тарифы: основная строка"),
field("content.pricing.introHtml", "Тарифы: вводный текст", "html"),
field("content.pricing.tokenBillingHtml", "Тарифы: оплата сверх лимитов", "html"),
field("content.pricing.founderLimitHtml", "Тарифы: Founder-лимит", "html"),
field("content.pricing.windowTitle", "Окно: название"),
field("content.pricing.discountLabel", "Founder: бейдж скидки"),
field("content.plans.0.name", "Бета: название"),
field("content.plans.0.priceHtml", "Бета: цена", "html"),
field("content.plans.0.period", "Бета: период"),
field("content.plans.1.name", "Founder: название"),
field("content.plans.1.priceHtml", "Founder: цена", "html"),
field("content.plans.1.period", "Founder: период"),
field("content.plans.2.name", "Basic: название"),
field("content.plans.2.priceHtml", "Basic: цена", "html"),
field("content.plans.2.period", "Basic: период"),
field("content.plans.3.name", "Advanced: название"),
field("content.plans.3.priceHtml", "Advanced: цена", "html"),
field("content.plans.3.period", "Advanced: период"),
field("content.pricing.betaHref", "Бета: ссылка", "url", "attr"),
field("content.pricing.founderHref", "Founder: ссылка", "url", "attr"),
field("content.pricing.betaButtonLabel", "Бета: кнопка"),
field("content.pricing.founderButtonLabel", "Founder: кнопка"),
field("content.pricing.basicButtonLabel", "Basic: кнопка"),
field("content.pricing.advancedButtonLabel", "Advanced: кнопка"),
field("content.pricing.noteHtml", "Тарифы: примечание", "html"),
field("content.pricing.enterpriseNoteHtml", "Тарифы: Enterprise-примечание", "html"),
],
},
};
const staticElements = {
id: "static-elements",
type: "staticElements",
adminLabel: "Статичные элементы",
enabled: true,
content: {
navigation: {
logoAlt: "главная",
brandLabel: "NODE.DC",
links: [
{
label: "О продукте",
href: "index.html#features",
target: "",
},
{
label: "Лист ожидания",
href: "index.html#hero",
target: "",
},
{
label: "Документация",
href: "https://docs.ssscript.app/",
target: "_blank",
},
{
label: "Тарифы",
href: "index.html#pricing-wrap",
target: "",
},
],
},
notifications: [
{
eyebrowHtml: '<span class="darker-70">ДАЛЕЕ</span> — ИЮЛЬ 2026',
title: "Открытая бета",
body: "Открываем доступ всем. Будем собирать и отлаживать вместе.",
},
{
eyebrowHtml: '<span class="darker-70">ДАЛЕЕ</span> —<span class="darker-70"> </span>ИЮЛЬ 2026',
title: "Альфа для Founder-доступа",
body: "На второй фазе тестирования бета открыта для всех подписчиков Founder-тарифа.",
},
{
eyebrowHtml: "СЕЙЧАС",
title: "Инвайты в закрытую бету уже отправлены.",
body: "Сейчас тестируем базовую функциональность и ИИ-интеграцию. ",
},
],
dock: {
appTarget: "app-data",
trashAlt: "Белая иконка корзины.",
demoFileLabel: "демо-видео.txt",
},
assets: {
webglModelSrc: "./assets/custom/icon.glb",
logoPng: "./assets/custom/logo.png",
logoPngAlt: "Логотип NODE.DC",
appIconSrc: "./assets/webflow/images/69ad964d951f9d17cc5a919e_logo-app.svg",
appIconAlt: "Иконка NODE.DC",
appWordmarkSrc: "./assets/webflow/images/69ad96a28b06b7552a339137_logtype.svg?v=node-dc",
appWordmarkAlt: "Логотип NODE.DC",
faviconSvg: "./assets/uploads/favicon/icon-adaptive.svg",
faviconIco: "./assets/uploads/favicon/favicon.ico",
faviconAppleTouchIcon: "./assets/uploads/favicon/apple-touch-icon.png",
faviconPng192: "./assets/uploads/favicon/icon-192.png",
faviconPng512: "./assets/uploads/favicon/icon-512.png",
faviconManifest: "./assets/uploads/favicon/manifest.webmanifest.json",
},
},
editableFields: [
field("content.navigation.logoAlt", "Логотип в шапке: alt"),
field("content.navigation.brandLabel", "Бренд в шапке"),
field("content.navigation.links.0.label", "Ссылка 1: текст"),
field("content.navigation.links.0.href", "Ссылка 1: адрес", "url", "attr"),
field("content.navigation.links.1.label", "Ссылка 2: текст"),
field("content.navigation.links.1.href", "Ссылка 2: адрес", "url", "attr"),
field("content.navigation.links.2.label", "Ссылка 3: текст"),
field("content.navigation.links.2.href", "Ссылка 3: адрес", "url", "attr"),
field("content.navigation.links.2.target", "Ссылка 3: target", "text", "attr"),
field("content.navigation.links.3.label", "Ссылка 4: текст"),
field("content.navigation.links.3.href", "Ссылка 4: адрес", "url", "attr"),
field("content.notifications.0.eyebrowHtml", "Уведомление 1: дата/статус", "html"),
field("content.notifications.0.title", "Уведомление 1: заголовок"),
field("content.notifications.0.body", "Уведомление 1: текст", "textarea"),
field("content.notifications.1.eyebrowHtml", "Уведомление 2: дата/статус", "html"),
field("content.notifications.1.title", "Уведомление 2: заголовок"),
field("content.notifications.1.body", "Уведомление 2: текст", "textarea"),
field("content.notifications.2.eyebrowHtml", "Уведомление 3: дата/статус", "html"),
field("content.notifications.2.title", "Уведомление 3: заголовок"),
field("content.notifications.2.body", "Уведомление 3: текст", "textarea"),
field("content.dock.appTarget", "Dock: ID приложения"),
field("content.dock.trashAlt", "Dock: alt корзины"),
field("content.dock.demoFileLabel", "Dock: подпись демо-файла"),
field("content.assets.webglModelSrc", "WebGL-модель логотипа", "media", "attr"),
field("content.assets.logoPng", "PNG-логотип", "media", "attr"),
field("content.assets.logoPngAlt", "PNG-логотип: alt"),
field("content.assets.appIconSrc", "Иконка приложения", "media", "attr"),
field("content.assets.appIconAlt", "Иконка приложения: alt"),
field("content.assets.appWordmarkSrc", "Wordmark приложения", "media", "attr"),
field("content.assets.appWordmarkAlt", "Wordmark приложения: alt"),
field("content.assets.faviconSvg", "Favicon SVG", "media", "attr"),
field("content.assets.faviconIco", "Favicon ICO", "media", "attr"),
field("content.assets.faviconAppleTouchIcon", "Favicon Apple 180", "media", "attr"),
field("content.assets.faviconPng192", "Favicon PNG 192", "media", "attr"),
field("content.assets.faviconPng512", "Favicon PNG 512", "media", "attr"),
field("content.assets.faviconManifest", "Favicon manifest", "media", "attr"),
],
};
function shellToken(mode, path) {
return `{{${mode}:staticElements.${path}}}`;
}
function replaceShellLiteral(source, literal, replacement, label) {
if (!source.includes(literal)) {
throw new Error(`Could not find shell fragment while tokenizing static elements: ${label}`);
}
return source.replace(literal, replacement);
}
function tokenizeStaticShell(source, definition) {
const { navigation, notifications, dock } = definition.content;
let tokenized = source;
const faviconHead = [
`{{#if staticElements.content.assets.faviconSvg}}<link rel="icon" type="image/svg+xml" sizes="any" href="${shellToken(
"attr",
"content.assets.faviconSvg",
)}"/>{{/if}}`,
`{{#if staticElements.content.assets.faviconIco}}<link rel="icon" type="image/x-icon" href="${shellToken(
"attr",
"content.assets.faviconIco",
)}"/>{{/if}}`,
`{{#if staticElements.content.assets.faviconAppleTouchIcon}}<link rel="apple-touch-icon" sizes="180x180" href="${shellToken(
"attr",
"content.assets.faviconAppleTouchIcon",
)}"/>{{/if}}`,
`{{#if staticElements.content.assets.faviconPng192}}<link rel="icon" type="image/png" sizes="192x192" href="${shellToken(
"attr",
"content.assets.faviconPng192",
)}"/>{{/if}}`,
`{{#if staticElements.content.assets.faviconPng512}}<link rel="icon" type="image/png" sizes="512x512" href="${shellToken(
"attr",
"content.assets.faviconPng512",
)}"/>{{/if}}`,
`{{#if staticElements.content.assets.faviconManifest}}<link rel="manifest" href="${shellToken(
"attr",
"content.assets.faviconManifest",
)}"/>{{/if}}`,
].join("");
tokenized = replaceShellLiteral(
tokenized,
`<link href="./assets/webflow/images/69b83bc24411350dcdb86781_favicon.png" rel="shortcut icon" type="image/x-icon"/><link href="./assets/webflow/images/69b83bc6ad9e108637556ec5_webclip.png" rel="apple-touch-icon"/>`,
faviconHead,
"favicon links",
);
tokenized = replaceShellLiteral(
tokenized,
`aria-label="${navigation.logoAlt}" aria-current`,
`aria-label="${shellToken("attr", "content.navigation.logoAlt")}" aria-current`,
"navigation.logoAlt",
);
tokenized = replaceShellLiteral(
tokenized,
`class="bar-txt medium w--current">${navigation.brandLabel}</a>`,
`class="bar-txt medium w--current">${shellToken("text", "content.navigation.brandLabel")}</a>`,
"navigation.brandLabel",
);
for (const [index, link] of navigation.links.entries()) {
const targetPart = link.target ? ` target="${link.target}"` : "";
tokenized = replaceShellLiteral(
tokenized,
`<a href="${link.href}"${targetPart} class="bar-txt">${link.label}</a>`,
`<a href="${shellToken("attr", `content.navigation.links.${index}.href`)}"${
link.target ? ` target="${shellToken("attr", `content.navigation.links.${index}.target`)}"` : ""
} class="bar-txt">${shellToken("text", `content.navigation.links.${index}.label`)}</a>`,
`navigation.links.${index}`,
);
}
notifications.forEach((notification, index) => {
tokenized = replaceShellLiteral(
tokenized,
notification.eyebrowHtml,
shellToken("html", `content.notifications.${index}.eyebrowHtml`),
`notifications.${index}.eyebrowHtml`,
);
tokenized = replaceShellLiteral(
tokenized,
`<div>${notification.title}</div>`,
`<div>${shellToken("text", `content.notifications.${index}.title`)}</div>`,
`notifications.${index}.title`,
);
tokenized = replaceShellLiteral(
tokenized,
`<div>${notification.body}</div>`,
`<div>${shellToken("text", `content.notifications.${index}.body`)}</div>`,
`notifications.${index}.body`,
);
});
tokenized = replaceShellLiteral(
tokenized,
`data-target="${dock.appTarget}" class="dock-icon ssscript"`,
`data-target="${shellToken("attr", "content.dock.appTarget")}" class="dock-icon ssscript"`,
"dock.appTarget",
);
tokenized = replaceShellLiteral(
tokenized,
`alt="${dock.trashAlt}" class="dock-app-img trash"`,
`alt="${shellToken("attr", "content.dock.trashAlt")}" class="dock-app-img trash"`,
"dock.trashAlt",
);
tokenized = replaceShellLiteral(
tokenized,
"<script>\nwindow.__API_ORIGIN__=",
`\n<script id="node-dc-static-config" type="application/json">{"assets":{"icon":"${shellToken(
"js",
"content.assets.webglModelSrc",
)}","logo":"${shellToken("js", "content.assets.logoPng")}"}}</script>\n<script>\nwindow.__API_ORIGIN__=`,
"static asset config",
);
return tokenized;
}
shell = tokenizeStaticShell(shell, staticElements);
const preparedBlocks = blocks.map((block) => {
const definition = typedBlockDefinitions[block.id];
return definition ? tokenizeBlock(block, definition) : block;
});
const page = {
schemaVersion: 1,
slug: "home",
output: "index.html",
title: "NODE.DC",
staticElements,
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: preparedBlocks
.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: preparedBlocks
.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 preparedBlocks) {
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}`);
throw new Error(
"Legacy homepage extraction is disabled. Edit content/pages/home.json or content/block-templates/home.json, then run npm run render:home.",
);

View File

@ -1,554 +1,3 @@
import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const pages = ["index.html", "beta-apply.html", "founders.html", "enterprise.html"];
const wordmarkPath = path.join(rootDir, "assets/webflow/images/69ad96a28b06b7552a339137_logtype.svg");
const nodeDcWordmarkSvg = `<svg width="102" height="26" viewBox="0 0 102 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<text x="0" y="20" fill="white" font-family="-apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif" font-size="23" font-weight="800" letter-spacing="0" textLength="98" lengthAdjust="spacingAndGlyphs">NODE.DC</text>
</svg>
`;
const translations = new Map([
["Ssscript™ — Your Webflow AI Launchpad", "NODE.DC — ИИ-платформа для Webflow"],
[
"All-in-one custom code, MCP, and AI launchpad for Webflow. Get early access to agentic control, embedded knowledge, and deployment pipeline. Beta May 2026.",
"Единая платформа для кастомного кода, MCP и ИИ-агентов в Webflow: контекст проекта, автоматизация, деплой и управляемая разработка.",
],
["All-in-one custom code, MCP, and AI launchpad for Webflow", "Единая платформа для кастомного кода, MCP и ИИ-агентов в Webflow"],
["Ssscript App&#x27;s Icon", "Иконка NODE.DC"],
["Ssscript App logo", "Логотип NODE.DC"],
["Ssscript App", "NODE.DC"],
["Ssscript™", "NODE.DC"],
["Ssscript", "NODE.DC"],
["ssscript", "NODE.DC"],
["Your Webflow", "Ваш Webflow"],
["AI Toolkit", "ИИ-инструменты"],
["Multiple agents on your Webflow project, from the", "Несколько агентов для проекта Webflow: от"],
["Designer", "Designer"],
["to", "до"],
["Custom Code.", "кастомного кода."],
["Custom Code", "Кастомный код"],
["Custom code", "Кастомный код"],
["custom code", "кастомный код"],
["Complete", "Полный"],
["Agentic Control", "агентный контроль"],
["Integrated with", "Интеграция с"],
["Webflow MCP,", "Webflow MCP,"],
["multi agent mode allows you to work on", "мультиагентный режим позволяет работать с"],
["multiple instances", "несколькими экземплярами"],
["in parallel.", "параллельно."],
["Component", "Компонентная"],
["Library", "библиотека"],
["Library.", "библиотека."],
["Build with", "Собирайте с учетом"],
["AI Agents", "ИИ-агентов"],
["in mind, diverse component setups that provide the backbone, while design the style and prompt advanced functionality.", "разные компонентные заготовки дают основу, а стиль и сложную функциональность можно дорабатывать промптами."],
["Deployment", "Деплой"],
["Pipeline", "пайплайн"],
["One click deploy,", "Деплой в один клик,"],
["continuous pipeline, instant rollback, and fast", "непрерывный пайплайн, мгновенный откат и быстрая"],
["CDN delivery.", "доставка через CDN."],
["Beta release July 2026", "Бета-релиз: июль 2026"],
["Beta release May 2026", "Бета-релиз: май 2026"],
["Release September 2026", "Релиз: сентябрь 2026"],
["Get early access", "Ранний доступ"],
["All in one Custom Code, MCP, AI launchpad for Webflow.", "Кастомный код, MCP и ИИ-агенты для Webflow в одном рабочем контуре."],
["All in one Custom Code, MCP, Ai launchpad for Webflow.", "Кастомный код, MCP и ИИ-агенты для Webflow в одном рабочем контуре."],
["Всё в одном Custom Code, MCP, AI launchpad for Webflow.", "Кастомный код, MCP и ИИ-агенты для Webflow в одном рабочем контуре."],
["Join Waitlist", "Встать в лист ожидания"],
["You&#x27;re In, thanks.", "Заявка принята. Спасибо."],
["We&#x27;ll be in touch soon.", "Мы скоро свяжемся."],
["Apply for Beta?", "Подать заявку в бету?"],
["Or get Early founder&#x27;s access", "Или оформить ранний Founder-доступ"],
["Get Founder Now", "Оформить Founder-доступ"],
["Get Founder Now <span class=\"darker-70\">350€</span>", "Оформить Founder-доступ <span class=\"darker-70\">350€</span>"],
["Get Founder Now <span class=\"darker-70\">310€</span>", "Оформить Founder-доступ <span class=\"darker-70\">310€</span>"],
["200 Spots Max", "До 200 мест"],
["Nope :(", "Не получилось"],
["Something went wrong.", "Что-то пошло не так."],
["Try again (later maybe)?", "Попробуйте ещё раз чуть позже."],
["Privacy Policy", "Политика конфиденциальности"],
["Cookie Policy", "Политика cookie"],
["Terms of Usage", "Условия использования"],
["© — All Right Reserved", "© — Все права защищены"],
["© 2026", "© 2026"],
["Leave a Message (optional)", "Сообщение (необязательно)"],
["Your Name*", "Ваше имя*"],
["Name", "Имя"],
["close window", "закрыть окно"],
["close", "закрыть"],
["homepage", "главная"],
["Sign-up Email*", "Email для регистрации*"],
["Why you? *", "Почему именно вы? *"],
["Twitter Profile", "Профиль в Twitter"],
["Instagram Profile", "Профиль в Instagram"],
["Portfolio URL", "Ссылка на портфолио"],
["video-demo.mp4", "демо-видео.mp4"],
["One click publish.png", "публикация-в-один-клик.png"],
["Complete code editor.webp", "полноценный-редактор-кода.webp"],
["AI Dock Mode.jpg", "ИИ-док-режим.jpg"],
["Deep Webflow connection.webp", "глубокая-интеграция-webflow.webp"],
["Install, setup and run.jpg", "установка-настройка-запуск.jpg"],
["Pricing.app", "Тарифы.app"],
[".txt", ".txt"],
["video-demo.txt", "демо-видео.txt"],
["All in one", "Всё в одном"],
["App.", "приложение."],
["The swiss-army knife for", "Универсальный инструмент для"],
["AI Agent", "ИИ-агентов"],
["in your Webflow project.", "в вашем Webflow-проекте."],
["Contextual Awareness", "Контекст проекта"],
["Connect to the", "Подключитесь к"],
["MCP", "MCP"],
["to give the Agents the full context on your build.", "чтобы агенты получили полный контекст вашей сборки."],
["Agent Swarms", "Группы агентов"],
["Use", "Используйте"],
["multiple agents in parallel", "несколько агентов параллельно"],
["to add custom code, animations, and functionality.", "чтобы добавлять кастомный код, анимации и функциональность."],
["Development Setup", "Среда разработки"],
["Leverage fast", "Используйте быстрые"],
["Bun", "Bun"],
["based builds, and live reload of your", "сборки и автообновление для вашего"],
["custom code.", "кастомного кода."],
["One-click Deploy", "Деплой в один клик"],
["Click and deploy", "Нажмите и публикуйте"],
["your AI written custom code to make it available to all users visiting your website.", "кастомный код, написанный ИИ, чтобы он стал доступен всем посетителям сайта."],
["Agentic", "Агентная"],
["Agent ready", "Готовая к работе с агентами"],
["component", "компонентная"],
["library. One click add (via the Ssscript Webflow app) ready to customise.", "библиотека: добавление в один клик через приложение NODE.DC для Webflow и дальнейшая настройка под проект."],
["AI Mode Switch", "Переключение режимов ИИ"],
["New Project Flow", "Создание нового проекта"],
["AI Dock Mode", "ИИ-док"],
["Account", "Аккаунт"],
["AI Code Editing", "Редактирование кода с ИИ"],
["AI Dev Server", "Dev-сервер с ИИ"],
["Webflow MCP", "Webflow MCP"],
["Publishing", "Публикация"],
["One", "Один"],
["click", "клик"],
["Add.", "добавления."],
["With the", "С помощью"],
["Ssscript Webflow Extension.", "расширения NODE.DC для Webflow."],
["Component Awareness", "Понимание компонентов"],
["Faster Starters", "Быстрый старт"],
["Get a", "Получите"],
["better", "более сильный"],
["MCP.", "MCP."],
["Fully re-engineered faster MCP server.", "Полностью переработанный и более быстрый MCP-сервер."],
["Webflow Awareness", "Понимание Webflow"],
["Flexible", "Гибкие"],
["AI Modes.", "режимы ИИ."],
["Different", "Разные"],
["modes", "режимы"],
["and", "и"],
["models", "модели"],
["for different tasks.", "для разных задач."],
["Frequently", "Частые"],
["Asked", "вопросы"],
["Questions", "и ответы"],
["Detailed overview", "Подробный обзор"],
["of features, functionality and inner workings.", "возможностей, логики работы и внутреннего устройства."],
["Complete Docs", "Полная документация"],
["FAQs", "FAQ"],
["Reply-To", "Ответить"],
["Does it impact performance?", "Это влияет на производительность?"],
["Where are my files hosted?", "Где размещаются мои файлы?"],
["Help! I pushed the wrong files!", "Что делать, если я опубликовал не те файлы?"],
["What&#x27;s the Webflow AI Agent?", "Что такое Webflow AI Agent?"],
["What is the custom knowledge base?", "Что такое собственная база знаний?"],
["How do I use it with Webflow?", "Как использовать это с Webflow?"],
["What AIs are available?", "Какие ИИ-модели доступны?"],
["How does it work?", "Как это работает?"],
["How does publishing work?", "Как работает публикация?"],
["Does it work with the MCP?", "Это работает с MCP?"],
["As every custom code addition does: the overhead is just the one added by the code itself.", "Как и любой кастомный код, это добавляет только ту нагрузку, которую несёт сам код."],
["Everything is also bundled, minified and compressed, to have the least impact on the project, avoiding loading external libraries and increase the number of CDN based scripts.", "Все файлы собираются, минифицируются и сжимаются, чтобы минимально влиять на проект и не плодить внешние библиотеки и CDN-скрипты."],
["Sent from my iPhone", "Отправлено с iPhone"],
["After the build step, all bundled files are uploaded to Cloudflare R2 and served through a CDN link on ssscript.dev domain, a dedicated CDN for this project, for the fastest, Webflow compatible result.", "После сборки файлы загружаются в Cloudflare R2 и отдаются через CDN-ссылку на выделенном домене проекта, чтобы получить быстрый и совместимый с Webflow результат."],
["In the deploy panel there&#x27;s the option for an instant rollback.", "В панели деплоя есть мгновенный откат."],
["Hit that, and you&#x27;ll get the previous deployment working right away, fix locally, and push again.", "Нажмите откат, верните предыдущую рабочую версию, исправьте локально и опубликуйте заново."],
["Other than", "Помимо"],
["default Agent modes, we&#x27;re working on a", "стандартных режимов агентов мы готовим"],
["mode, made specifically of webflow knowlege, how to interact with the project and how to be effective in it.", "режим, специально заточенный под Webflow: структуру проекта, взаимодействие с ним и эффективную разработку."],
["Built on top of years writing custom code in webflow for animation.", "Основано на многолетнем опыте написания кастомного кода для Webflow-анимаций."],
["WebGl, Threejs, GSAP and custom functionality, we collected best practices, build pipelines, and a module based systems to ensure everything works in unison.", "WebGL, Three.js, GSAP и кастомная функциональность собраны в набор практик, пайплайнов и модульную систему, чтобы всё работало согласованно."],
["A custom component library is also available for you to pick from, add to your project, and ask the AI to modify to fit your needs.", "Также доступна библиотека компонентов: выберите компонент, добавьте в проект и попросите ИИ адаптировать его под задачу."],
["When running the", "При запуске"],
[", there&#x27;s a custom Embed tab.", " доступна отдельная вкладка для встраивания."],
["Depending on your needs, you can get page based scripts, or a unique app.js for the entirety of the project.", "В зависимости от задачи можно получить скрипты для конкретной страницы или единый app.js для всего проекта."],
["The generated link already contains both a deployed version, that&#x27;ll follow your deploy url, and a local version, allowing you to work on you to preview your code without disturbing other people visiting your website.", "Сгенерированная ссылка содержит и продакшен-версию, следующую за URL деплоя, и локальную версию для предпросмотра без влияния на посетителей сайта."],
["We&#x27;re using all major models available, from", "Мы используем основные доступные модели:"],
["The model orchestrates automatically based on the request and needs, and you can switch to specific models by selecting them in a toggle.", "Оркестратор выбирает модель под задачу автоматически, но вы можете вручную переключиться на нужную модель."],
["When running, Ssscript is doing 2 things in parallel.", "Во время работы NODE.DC выполняет две задачи параллельно."],
["When running, Ssscript™ is doing 2 things in parallel.", "Во время работы NODE.DC выполняет две задачи параллельно."],
["When running, NODE.DC is doing 2 things in parallel.", "Во время работы NODE.DC выполняет две задачи параллельно."],
["Running a local Development environment, based on Bun, that keeps javascript and typescript files in sync, provides Hot Module Reload (HMR), and keeps a webhook active to automatically refresh your website when changes are made.", "Поднимает локальную среду разработки на Bun, синхронизирует JavaScript и TypeScript, включает HMR и держит webhook для автоматического обновления сайта при изменениях."],
["At the same time, an ai agent orchestrator is running, accessing agents and processing your requests, changing code, checking the build health, and modifying the Webflow project when needed.", "Одновременно работает оркестратор ИИ-агентов: обрабатывает запросы, меняет код, проверяет состояние сборки и при необходимости обновляет проект Webflow."],
["It&#x27;s a multi step project. First, we use Bun to build, compress, minify and set up the project and libraries used.", "Процесс многоэтапный: сначала Bun собирает, сжимает, минифицирует и подготавливает проект вместе с библиотеками."],
["Then we add to our CDN:", "Затем файлы отправляются в CDN:"],
["ssscript.dev/{USERNAME}/{PROJECT-NAME}", "cdn.node.dc/{USERNAME}/{PROJECT-NAME}"],
[". The link already contains the correct structure, so while you keep seeing the local version of the files, everyone else get the right script served instead.", ". Ссылка уже содержит нужную структуру: вы видите локальную версию, а пользователи получают опубликованный скрипт."],
["It&#x27;s built to work with the Webflow MCP natively.", "Система изначально рассчитана на работу с Webflow MCP."],
["Connect project, set authorization, and the AI Agents have now access to the Webflow build. Connect the App in the webflow Designer to also allow for Designer access, so the agents can update content, create elements, change styles and CSS, and to give the custom code full awareness of the Webflow project.", "Подключите проект и авторизацию, чтобы ИИ-агенты получили доступ к Webflow-сборке. Подключение приложения в Webflow Designer даёт агентам доступ к контенту, элементам, стилям и CSS, а кастомный код получает полный контекст проекта."],
["Connect.", "Подключить."],
["Prompt.", "Поставить задачу."],
["Ship.", "Опубликовать."],
["All in one development to production pipeline.", "Единый путь от разработки до продакшена."],
["12-click total setup,", "Настройка примерно в 12 кликов:"],
["from app download to have it running in your project.", "от скачивания приложения до запуска в проекте."],
["Pricing", "Тарифы"],
["Get Code", "Код"],
["to No-Code,", "для No-Code,"],
["with AI.", "усиленный ИИ."],
["App subscriptions with", "Подписки на приложение с"],
["inlcuded usage in every plan.", "включённым объёмом в каждом тарифе."],
["Token based", "Токенная"],
["pricing for overages.", "оплата сверх лимитов."],
["200 Founder", "200 Founder"],
["spots with locked price for the first year.", "мест с фиксированной ценой на первый год."],
["Beta", "Бета"],
["Free", "Бесплатно"],
["1 Month", "1 месяц"],
["Founder", "Founder"],
["/ Yearly, forever", "/ год, навсегда"],
["50% Discount", "Скидка 50%"],
["Basic", "Basic"],
["/ Month", "/ месяц"],
["Advanced", "Advanced"],
["Dropping", "Старт"],
["Total Availability", "Всего доступно"],
["Expansion (20M)", "Расширение (20M)"],
["Expansion (10M)", "Расширение (10M)"],
["Reasoning Depth", "Глубина рассуждения"],
["Core Intelligence", "Базовый интеллект"],
["Project Workspace", "Рабочее пространство"],
["Token Capacity", "Объём токенов"],
["June 2026", "Июнь 2026"],
["April 2026", "Апрель 2026"],
["May 2026", "Май 2026"],
["Plan Credits", "Кредиты тарифа"],
["Unlimited", "Без ограничений"],
["40,000 Included", "40 000 включено"],
["12,000 Included", "12 000 включено"],
["10 Projects", "10 проектов"],
["All Models", "Все модели"],
["All fast", "Все быстрые"],
["Full", "Полный"],
["Limited", "Ограниченный"],
["Expansion SM 10€", "Расширение SM 10€"],
["Unlimited Refresh", "Обновления без лимита"],
["+ 10,000 Credits", "+ 10 000 кредитов"],
["+ 8,000 Credits", "+ 8 000 кредитов"],
["Expansion MD 20€", "Расширение MD 20€"],
["+ 26,000 Credits", "+ 26 000 кредитов"],
["+ 20,000 Credits", "+ 20 000 кредитов"],
["40 testers", "40 тестировщиков"],
["200 Spots", "200 мест"],
["Apply for Beta", "Подать заявку в бету"],
["Get Basic", "Выбрать Basic"],
["Get Advanced", "Выбрать Advanced"],
["(*) Stuffs might change as testing progresses, though I&#x27;ll do my best to honour what&#x27;s here now.", "(*) Условия могут меняться по ходу тестирования, но мы постараемся сохранить заявленные обещания."],
["Enterprise", "Enterprise"],
["BYOK plans available from September 2026.", "BYOK-тарифы будут доступны с сентября 2026."],
["Get in the Beta", "Попасть в бету"],
["Apply only if you think you can follow up with this.", "Подавайте заявку, если готовы действительно тестировать и давать обратную связь."],
["Thanks!", "Спасибо!"],
["We&#x27;ll reach out if you&#x27;re a fit.", "Мы свяжемся, если ваш профиль подойдёт для тестирования."],
["Want to", "Хотите"],
["get in early", "получить ранний доступ"],
["and contribute to the development, locking a", "и повлиять на разработку, зафиксировав"],
["yearly discounted 50% advanced access forever?", "годовой Advanced-доступ со скидкой 50% навсегда?"],
["Influence", "Влияйте на"],
["Early Choices", "ранние решения"],
["And", "И"],
["actively participating", "активное участие"],
["actively participate", "активно участвуйте"],
["product roadmap", "дорожная карта продукта"],
["to the product roadmap.", "в формировании дорожной карты продукта."],
["Direct access", "Прямой доступ"],
["To the App.", "к приложению."],
["As an early tester.", "В статусе раннего тестировщика."],
["Infinite credits", "Безлимитные кредиты"],
["and tokens,", "и токены,"],
["unlimiterd projects.", "проекты без ограничений."],
["Discounted", "Льготная"],
["Sign up.", "регистрация."],
["When launching.", "на старте запуска."],
["What&#x27;s in", "Что входит в"],
["The Beta.", "бету."],
["You&#x27;ll be", "Вы будете"],
["in the early development stages of the app,", "на ранних этапах разработки приложения,"],
["influencing the development and future direction.", "влияя на развитие и дальнейшее направление продукта."],
["What am I expecting?", "Что мы ожидаем?"],
["Active participation.", "Активное участие."],
["Either through testing scenarios, or even better, to roll it in production.", "Тестовые сценарии, а ещё лучше — применение в реальном продакшене."],
["What you&#x27;ll get?", "Что вы получите?"],
["I&#x27;ll be there for you.", "Мы будем рядом на всём пути."],
["Constant communication,", "Постоянная коммуникация,"],
["bug fixing and 1 to 1 involvement.", "исправление ошибок и персональное сопровождение."],
["Where?", "Где?"],
["We&#x27;ll have a shared, private", "Будет общий закрытый"],
["Discord channel,", "Discord-канал,"],
["with other Beta Testers (~40 ppl).", "с другими бета-тестировщиками (~40 человек)."],
["What you get,", "Что вы получаете:"],
["Preference,", "приоритет,"],
["early and discounted access", "ранний доступ со скидкой"],
["to Founders plan.", "к тарифу Founders."],
["Founders", "Founders"],
["Founders Access", "Founder-доступ"],
["Get in Early.", "Войти раньше остальных."],
["Access the early beta before official release, with benefits.", "Ранний доступ к бете до официального релиза и дополнительные преимущества."],
["/ Yearly, Forever", "/ год, навсегда"],
["Token Capacity Beta", "Токены в бете"],
["Token Capacity After", "Токены после беты"],
["20M / Month", "20M / месяц"],
["€10/€16", "€10/€16"],
["Help fund the", "Поддержите"],
["training", "обучение"],
["fine-tuning", "дообучение"],
["fine tuning", "дообучение"],
["of the", "для"],
["Webflow code model", "кодовой модели Webflow"],
[", and the early development of the App.", " и раннюю разработку приложения."],
["Early access", "Ранний доступ"],
["To the Beta", "к бете"],
["You&#x27;ll get in for the second phase of the beta,", "Вы попадёте во вторую фазу беты,"],
["help test, provide feedback and", "поможете тестировать, давать обратную связь и"],
["shape the future of the app.", "формировать будущее продукта."],
["50% discount", "Скидка 50%"],
["On the advanced plan.", "на тариф Advanced."],
["Yearly, forever.", "Годовой доступ, навсегда."],
["What", "Что означает"],
["Founder", "Founder"],
["Means.", "доступ."],
["Early Beta Access, 50% discount.", "Ранний доступ к бете и скидка 50%."],
["Forever.", "Навсегда."],
["Early Access", "Ранний доступ"],
["Access the app in beta with the testers, you&#x27;ll get access on the first beta open wave", "Доступ к приложению вместе с тестировщиками: вы попадёте в первую открытую волну беты"],
["in approximately a week.", "примерно через неделю."],
["Lifetime Advanced Perks", "Преимущества Advanced навсегда"],
["at half the price.", "за половину стоимости."],
["At more than", "Более чем"],
["50% discounts.", "50% скидки."],
["All the good stuffs,", "Все ключевые возможности,"],
["less than half the price.", "меньше чем за половину цены."],
["Support the development", "Поддержите разработку"],
["Your money will go towards", "Средства пойдут на"],
["the custom model we&#x27;re working on.", "кастомной модели, над которой мы работаем."],
["Compare", "Сравните"],
["your", "свои"],
["Options", "варианты"],
["Founders get you access to the second Beta phase, with everything unlimited while testing, for 1 month.", "Founders получают доступ ко второй фазе беты и безлимит на время тестирования в течение 1 месяца."],
["Priced Yearly", "Годовая оплата"],
["supports the custom model fine tuning costs, while giving you Advanced pricing perks, forever.", "помогает покрыть расходы на дообучение модели и навсегда фиксирует преимущества тарифа Advanced."],
["spots total available.", "мест всего доступно."],
["Get in!", "Присоединиться!"],
["Enterprise Plan", "Enterprise-тариф"],
["Bring Your Own Keys.", "Используйте собственные ключи."],
["Extra security and privacy. Use any model.", "Больше безопасности и приватности. Любые модели на ваших условиях."],
["/ Monthly (5 users)", "/ месяц (5 пользователей)"],
["Members Included", "Пользователей включено"],
["5 Seats", "5 мест"],
["6+ Members", "6+ пользователей"],
["+ 40€ / Month / User", "+ 40€ / месяц / пользователь"],
["Get in Touch", "Связаться"],
["Direct", "Прямая"],
["Assistance", "поддержка"],
["Privileged access to", "Приоритетный доступ к"],
["support channels.", "каналам поддержки."],
["Included", "включено"],
["(40€/month) included.", "(40€/месяц) включено."],
["Pay what you use, get unlimited projects.", "Платите за использование и работайте с неограниченным числом проектов."],
["Bring", "Используйте"],
["Your Own", "собственные"],
["Keys.", "ключи."],
["Complete privacy and isolation.", "Полная приватность и изоляция."],
["Pay on your terms.", "Оплата на ваших условиях."],
["Enterprise Pricing", "Enterprise-цены"],
["Use any model.", "Используйте любую модель."],
["Additional Seats", "Дополнительные места"],
["Available at", "Доступны по"],
["discounted price", "льготной цене"],
["(30€/month per user).", "(30€/месяц за пользователя)."],
["Premium Support", "Премиальная поддержка"],
["Available", "Доступна"],
["24h.", "24/7."],
["Navigate", "Навигация"],
["Docs", "Документация"],
["Waitlist", "Лист ожидания"],
["Features", "Возможности"],
["Demo", "Демо"],
["Connect", "Контакты"],
["Twitter", "Twitter"],
["Instagram", "Instagram"],
["Github", "GitHub"],
["Portfolio", "Портфолио"],
["About", "О продукте"],
["Sat Jun 10", "Сб, 10 июня"],
["9:41 AM", "09:41"],
["UPCOMING", "ДАЛЕЕ"],
["— JULY 2026", "— ИЮЛЬ 2026"],
["Open Beta Release", "Открытая бета"],
["Everyone in. Let&#x27;s build (and debug) together.", "Открываем доступ всем. Будем собирать и отлаживать вместе."],
["JULY 2026", "ИЮЛЬ 2026"],
["Founders Alpha Release", "Альфа для Founder-доступа"],
["Founders Alpha", "Альфа для Founder-доступа"],
["For the second phase of testing, Beta is open to all subscribers with a founders plan.", "На второй фазе тестирования бета открыта для всех подписчиков Founder-тарифа."],
["CURRENTLY", "СЕЙЧАС"],
["Invites are out for the Closed Beta.", "Инвайты в закрытую бету уже отправлены."],
["We&#x27;re testing basic functionality and AI Integration atm.", "Сейчас тестируем базовую функциональность и ИИ-интеграцию."],
["Webflow AI Agent", "ИИ-агент Webflow"],
["Icon of a MOV video file featuring the QuickTime logo on a white document with a folded corner.", "Иконка MOV-файла."],
["Typed text on a white page, laid out in short lines like a text document preview.", "Иконка текстового файла."],
["Typed text on a white page with a folded top-right corner, featuring an inspirational passage about &#x27;the crazy ones&#x27; who challenge norms, create, inspire, and push humanity forward.", "Иконка текстового файла."],
["Txt Apple Icon", "Иконка TXT-файла"],
["Plain white cylindrical trash can with a slightly tapered top.", "Белая иконка корзины."],
["Made in Webflow", ""],
["Federico Valla", "NODE.DC"],
["Federico", "NODE.DC"],
["just found your website, looks great !", "только что нашёл ваш сайт — выглядит отлично!"],
["i d know but yeah", "пока не уверен, но да, интересно"],
["Please let me check this out!", "Дайте, пожалуйста, посмотреть продукт!"],
["GIMME", "ХОЧУ ДОСТУП"],
["Hello!!", "Привет!"],
["We&#x27;ve worked in Webflow since 2016 and use it exclusively. We&#x27;re also implementing AI into our workflows and I&#x27;m very interested in your offering here. Good luck with the launch!", "Мы работаем с Webflow с 2016 года и используем его постоянно. Сейчас внедряем ИИ в процессы, поэтому ваш продукт нам очень интересен. Удачи с запуском!"],
["Just a PSA for all you other devs.. haha I found out about this very early on. One of the favorites ykyk.", "Для всех разработчиков: я нашёл это очень рано. Уже один из фаворитов."],
["Hi can i get early access please i wanna test the ai i need it so much i will really appreciate it", "Здравствуйте, можно получить ранний доступ? Очень хочу протестировать ИИ, это сейчас действительно нужно."],
["interesting. Would love to know more", "Интересно. Хотелось бы узнать больше."],
["Wooooooaaahhh!!", "Вау!"],
["Please let me into the beta!", "Пожалуйста, пустите меня в бету!"],
["I would love to try out this product. Seems very interesting", "С удовольствием протестировал бы продукт. Выглядит очень интересно."],
["Am I the first founder member now? Take my money Fed, make this happen &lt;3", "Я теперь первый Founder? Берите деньги и запускайте это."],
["I&#x27;m your biggest fan", "Я ваш главный фанат"],
["hello", "привет"],
["testingggg", "тестирую"],
["Interested", "Интересно"],
["I know Fed!", "Я знаю Fed!"],
["Let me join!", "Пустите меня!"],
["waiting", "жду"],
["I would love early access to this product!", "Очень хочу ранний доступ к продукту!"],
["Whatup homie, the site is 🔥", "Привет, сайт огонь."],
["Hey, we really like your design , cheers!", "Нам очень нравится ваш дизайн. Спасибо!"],
["&quot;Goat&quot; refers to the horned, domesticated mammal (Capra aegagrus hircus) known for agility. As a popular acronym, G.O.A.T. means &quot;Greatest of All Time", "G.O.A.T. часто используют как сокращение от Greatest of All Time — «лучший за всё время»."],
]);
const translatableAttributes = new Set([
"alt",
"aria-label",
"arial-label",
"content",
"data-name",
"placeholder",
"title",
]);
function escapeRegex(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function applyTranslationsToText(text) {
let output = text;
const ordered = [...translations.entries()].sort((a, b) => b[0].length - a[0].length);
for (const [source, target] of ordered) {
if (/^[A-Za-z]+$/.test(source) && source.length <= 5) {
output = output.replace(new RegExp(`\\b${escapeRegex(source)}\\b`, "g"), target);
} else {
output = output.split(source).join(target);
}
}
return output;
}
function patchTag(tag) {
return tag.replace(/([\w:-]+)="([^"]*)"/g, (full, name, value) => {
if (!translatableAttributes.has(name)) return full;
if (/^(https?:|\.\/|\/|summary_large_image|website|width=device-width)/i.test(value)) return full;
return `${name}="${applyTranslationsToText(value)}"`;
});
}
function patchHtmlTextAndTags(html) {
const blockPattern = /(<script\b[^>]*>[\s\S]*?<\/script>|<style\b[^>]*>[\s\S]*?<\/style>|<[^>]+>)/gi;
return html
.split(blockPattern)
.map((part) => {
if (!part) return part;
if (/^<style\b/i.test(part)) return part;
if (/^<script\b/i.test(part)) {
if (!/type="application\/ld\+json"/i.test(part)) return part;
return applyTranslationsToText(part);
}
if (part.startsWith("<")) return patchTag(part);
return applyTranslationsToText(part);
})
.join("");
}
function injectLocalizationStyle(html) {
if (html.includes("node-dc-localization")) return html;
const style = `<style id="node-dc-localization">.w-webflow-badge{display:none!important}</style>`;
return html.replace("</head>", `${style}</head>`);
}
function patchPage(html) {
let output = html.replace('lang="en"', 'lang="ru"');
output = patchHtmlTextAndTags(output);
output = output
.replace(/<title>[^<]*<\/title>/, "<title>NODE.DC — ИИ-платформа для Webflow</title>")
.replace(/property="og:locale" content="[^"]*"/, 'property="og:locale" content="ru_RU"')
.replace(
/69ad96a28b06b7552a339137_logtype\.svg(?:\?v=node-dc)?/g,
"69ad96a28b06b7552a339137_logtype.svg?v=node-dc"
);
return injectLocalizationStyle(output);
}
function patchCustomRuntime(js) {
return js
.replace(
'new Intl.DateTimeFormat("en-US",{hour:"numeric",minute:"2-digit",hour12:!0})',
'new Intl.DateTimeFormat("ru-RU",{hour:"2-digit",minute:"2-digit",hour12:!1})'
)
.replace(
'let J=$.locale||void 0,H=new Intl.DateTimeFormat(J,{weekday:"long"});',
'let J=$.locale||"ru-RU",H=new Intl.DateTimeFormat(J,{weekday:"short",day:"numeric",month:"short"});'
);
}
async function main() {
for (const page of pages) {
const file = path.join(rootDir, page);
const html = await readFile(file, "utf8");
await writeFile(file, patchPage(html));
}
const appJsPath = path.join(rootDir, "assets/custom/js/app.js");
const appJs = await readFile(appJsPath, "utf8");
await writeFile(appJsPath, patchCustomRuntime(appJs));
await writeFile(wordmarkPath, nodeDcWordmarkSvg);
console.log(`Localized ${pages.length} pages to Russian and patched custom runtime.`);
}
main().catch((error) => {
console.error(error);
process.exit(1);
});
throw new Error(
"Legacy localization is disabled. NODE.DC content is maintained directly in content/pages/home.json and admin presets.",
);

View File

@ -4,6 +4,8 @@ import { fileURLToPath } from "node:url";
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
const pagePath = join(repoRoot, "content", "pages", "home.json");
const robotsPath = join(repoRoot, "robots.txt");
const sitemapPath = join(repoRoot, "sitemap.xml");
const templateRoot = join(repoRoot, "templates", "pages", "home");
const shellPath = join(templateRoot, "shell.html");
const blockRoot = join(templateRoot, "blocks");
@ -14,6 +16,21 @@ const REGION_TOKEN = {
sections: "{{NODEDC_BLOCKS_SECTIONS}}",
};
const PAGE_SEO_DEFAULTS = {
title: "NODE.DC - платформа для процессов, данных и ИИ-агентов",
description:
"NODE.DC объединяет Hub, Engine, Ops, прикладные модули и сквозного ассистента для задач, workflow, данных, ролей, интеграций и инженерных моделей.",
canonicalUrl: "https://nodedc.dctouch.ru/",
robots: "index,follow,max-image-preview:large",
siteName: "NODE.DC",
locale: "ru_RU",
ogImage: "https://nodedc.dctouch.ru/assets/site/images/69b6acdde3123ef078e2b7f3_og-img.jpg",
imageAlt: "NODE.DC - платформа для процессов, данных и ИИ-агентов",
organizationName: "DCTOUCH",
organizationUrl: "https://dctouch.ru/",
applicationCategory: "BusinessApplication",
};
const page = JSON.parse(await readFile(pagePath, "utf8"));
const shell = await readFile(shellPath, "utf8");
const templateCache = new Map();
@ -51,6 +68,13 @@ function allBlocks(model) {
return (model.regions ?? []).flatMap((region) => region.blocks ?? []);
}
function applySeoDefaults(model) {
model.seo = {
...PAGE_SEO_DEFAULTS,
...(model.seo || {}),
};
}
function applySharedDemoRequest(model) {
const blocks = allBlocks(model);
const source = blocks.find((block) => block.id === "hero-waitlist");
@ -160,6 +184,45 @@ function escapeJsString(value) {
.replaceAll("</script", "<\\/script");
}
function escapeXml(value) {
return String(value)
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&apos;");
}
function canonicalUrl(model) {
const fallback = "https://nodedc.dctouch.ru/";
const value = String(model.seo?.canonicalUrl || fallback).trim() || fallback;
try {
const url = new URL(value);
return url.href;
} catch {
return fallback;
}
}
async function writeSeoFiles(model) {
const canonical = canonicalUrl(model);
const sitemapUrl = new URL("sitemap.xml", canonical).href;
const robots = ["User-agent: *", "Allow: /", `Sitemap: ${sitemapUrl}`, ""].join("\n");
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>${escapeXml(canonical)}</loc>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
</urlset>
`;
await writeFile(robotsPath, robots);
await writeFile(sitemapPath, sitemap);
}
function renderTemplateTokens(html, block) {
return html.replace(/\{\{\s*(text|html|attr|js):([a-zA-Z0-9_.-]+)\s*\}\}/g, (_match, mode, path) => {
const value = getByPath(block, path);
@ -301,6 +364,7 @@ async function renderSections() {
return output.join("");
}
applySeoDefaults(page);
applySharedDemoRequest(page);
applyRenderableLinks(page);
@ -328,5 +392,6 @@ for (const token of Object.values(REGION_TOKEN)) {
const outputPath = join(repoRoot, page.output || "index.html");
await mkdir(dirname(outputPath), { recursive: true });
await writeFile(outputPath, output);
await writeSeoFiles(page);
console.log(`Rendered ${page.slug} -> ${outputPath}`);