diff --git a/admin/admin.css b/admin/admin.css index bdc61ca..df0ed43 100644 --- a/admin/admin.css +++ b/admin/admin.css @@ -989,8 +989,37 @@ body[data-file-picker-active="true"] .editor { white-space: nowrap; } +.number-control { + display: flex; + min-height: 2.72rem; + align-items: center; + gap: 0.52rem; + border-radius: var(--launcher-radius-control); + background: var(--field); + padding: 0 0.86rem; +} + +.number-control input[type="number"] { + width: 100%; + min-width: 0; + border: 0; + background: transparent; + color: var(--text-primary); + font-size: 0.86rem; + font-weight: 820; + padding: 0; +} + +.number-suffix { + flex: 0 0 auto; + color: var(--text-muted); + font-size: 0.72rem; + font-weight: 850; +} + .boolean-field input, -.group-head-toggle input { +.group-head-toggle input, +.collection-visibility-toggle input { width: 2.42rem; height: 1.32rem; margin: 0; @@ -1001,7 +1030,8 @@ body[data-file-picker-active="true"] .editor { } .boolean-field input::before, -.group-head-toggle input::before { +.group-head-toggle input::before, +.collection-visibility-toggle input::before { display: block; width: 1rem; aspect-ratio: 1; @@ -1012,12 +1042,14 @@ body[data-file-picker-active="true"] .editor { } .boolean-field input:checked, -.group-head-toggle input:checked { +.group-head-toggle input:checked, +.collection-visibility-toggle input:checked { background: rgba(255, 255, 255, 0.9); } .boolean-field input:checked::before, -.group-head-toggle input:checked::before { +.group-head-toggle input:checked::before, +.collection-visibility-toggle input:checked::before { background: #050506; transform: translateX(1.1rem); } @@ -1098,6 +1130,10 @@ body[data-file-picker-active="true"] .editor { opacity: 0.16; } +.collection-item-card.is-hidden { + opacity: 0.55; +} + .collection-summary { min-height: 3rem; cursor: pointer; @@ -1110,6 +1146,8 @@ body[data-file-picker-active="true"] .editor { } .collection-summary-title { + min-width: 0; + flex: 1 1 auto; overflow: hidden; color: var(--text-secondary); font-size: 0.82rem; @@ -1124,6 +1162,45 @@ body[data-file-picker-active="true"] .editor { gap: 0.22rem; } +.collection-visibility-toggle { + display: inline-flex; + height: 2.2rem; + align-items: center; + gap: 0.38rem; + border-radius: var(--launcher-radius-circle); + background: rgba(255, 255, 255, 0.07); + padding: 0 0.52rem; + color: var(--text-secondary); + cursor: pointer; +} + +.collection-visibility-toggle:hover { + background: rgba(255, 255, 255, 0.14); + color: var(--text-primary); +} + +.collection-visibility-toggle span { + font-size: 0.68rem; + font-weight: 850; + line-height: 1; + text-transform: uppercase; +} + +.collection-visibility-toggle input { + width: 2rem; + height: 1.12rem; + flex: 0 0 auto; + padding: 0.13rem; +} + +.collection-visibility-toggle input::before { + width: 0.86rem; +} + +.collection-visibility-toggle input:checked::before { + transform: translateX(0.88rem); +} + .collection-drag-handle, .collection-icon-btn { display: grid; @@ -1589,6 +1666,33 @@ textarea { padding-top: 0; } +.delete-modal-layer { + z-index: 240; +} + +.delete-modal { + width: min(30rem, calc(100vw - 2.8rem)); +} + +.delete-modal-head { + padding-right: 1.25rem; +} + +.delete-modal-foot { + justify-content: flex-end; +} + +.delete-confirm-btn { + min-width: 4.4rem; + background: rgba(255, 255, 255, 0.92); + color: rgba(8, 8, 10, 0.96); +} + +.delete-confirm-btn:hover { + background: #fff; + color: rgba(8, 8, 10, 1); +} + @media (max-width: 1100px) { body { overflow: auto; diff --git a/admin/admin.js b/admin/admin.js index a6084a7..13e0a9b 100644 --- a/admin/admin.js +++ b/admin/admin.js @@ -9,8 +9,12 @@ const state = { filePickerActive: false, mediaUploadActive: false, filePickerResetTimer: null, + deleteConfirmResolve: null, }; +const SECTIONS_REGION_ID = "sections"; +const SECTIONS_REGION_LABEL = "Секции"; + const el = { regions: document.querySelector("#regions"), addBlock: document.querySelector("#add-block"), @@ -40,6 +44,11 @@ const el = { templateList: document.querySelector("#template-list"), templateModalClose: document.querySelector("#template-modal-close"), templateModalCancel: document.querySelector("#template-modal-cancel"), + deleteModal: document.querySelector("#delete-modal"), + deleteModalTitle: document.querySelector("#delete-modal-title"), + deleteModalBody: document.querySelector("#delete-modal-body"), + deleteModalCancel: document.querySelector("#delete-modal-cancel"), + deleteModalConfirm: document.querySelector("#delete-modal-confirm"), }; const STATIC_FALLBACK = { @@ -423,6 +432,32 @@ function ensureStaticElements() { state.page.staticElements.editableFields = clone(STATIC_FALLBACK.editableFields); } +function ensureSectionsRegion() { + const regions = Array.isArray(state.page.regions) ? state.page.regions : []; + const blocks = regions.flatMap((region) => + (region.blocks ?? []).map((block) => { + block.region ||= region.id; + return block; + }), + ); + + state.page.regions = [ + { + id: SECTIONS_REGION_ID, + label: SECTIONS_REGION_LABEL, + description: "Единый порядок секций главной страницы.", + blocks, + }, + ]; +} + +function sectionsRegionIndex() { + return Math.max( + 0, + state.page.regions.findIndex((region) => region.id === SECTIONS_REGION_ID), + ); +} + function isStaticSelection() { return state.selected?.kind === "static"; } @@ -559,8 +594,7 @@ function readFileAsDataUrl(file) { } function labelForRegion(region) { - if (region.id === "beforeCViz") return "Главная"; - if (region.id === "cViz") return "Секции"; + if (region.id === SECTIONS_REGION_ID || region.id === "beforeCViz" || region.id === "cViz") return SECTIONS_REGION_LABEL; return region.label || region.id; } @@ -855,14 +889,13 @@ function renderRegions() { } function blockTemplates() { - return state.templates.map((template) => { - const regionIndex = state.page.regions.findIndex((region) => region.id === template.regionId); - const region = state.page.regions[regionIndex] || state.page.regions[0]; + const regionIndex = sectionsRegionIndex(); + return state.templates.map((template) => { return { ...template, - regionIndex: regionIndex >= 0 ? regionIndex : 0, - regionLabel: template.regionLabel || labelForRegion(region), + regionIndex, + regionLabel: SECTIONS_REGION_LABEL, }; }); } @@ -907,6 +940,29 @@ function closeTemplateModal() { el.addBlock.focus(); } +function closeDeleteModal(confirmed = false) { + const resolve = state.deleteConfirmResolve; + + state.deleteConfirmResolve = null; + el.deleteModal.classList.add("hidden"); + if (resolve) resolve(confirmed); +} + +function requestDeleteConfirmation({ + title = "Подтвердите действие", + body = "Вы действительно подтвердить удаление элемента?", +} = {}) { + if (state.deleteConfirmResolve) closeDeleteModal(false); + + return new Promise((resolve) => { + state.deleteConfirmResolve = resolve; + el.deleteModalTitle.textContent = title; + el.deleteModalBody.textContent = body; + el.deleteModal.classList.remove("hidden"); + el.deleteModalConfirm.focus(); + }); +} + function addBlockFromTemplate(templateKey) { const template = blockTemplates().find((candidate) => candidate.key === templateKey); if (!template) return; @@ -1142,18 +1198,20 @@ async function uploadMediaFile({ block, editableField, hiddenInput, urlInput, fi fileName.textContent = "Сохраняем в assets..."; try { - const dataUrl = await readFileAsDataUrl(file); + const formData = new FormData(); + formData.append("file", file, file.name); + formData.append("fileName", file.name); + formData.append("mimeType", file.type || "application/octet-stream"); + formData.append("bucket", uploadBucketForField(block, editableField)); + const response = await fetch("/api/upload/asset", { method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ - fileName: file.name, - mimeType: file.type || "application/octet-stream", - bucket: uploadBucketForField(block, editableField), - dataUrl, - }), + body: formData, }); - const payload = await response.json(); + const payload = await response.json().catch(() => ({ + ok: false, + error: `Сервер вернул ${response.status}`, + })); if (!response.ok || !payload.ok) { throw new Error(payload.error || "Файл не удалось сохранить"); @@ -1500,6 +1558,54 @@ function renderRangeField({ block, editableField, grid }) { grid.append(label); } +function renderNumberField({ block, editableField, 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 control = document.createElement("span"); + const input = document.createElement("input"); + const suffix = document.createElement("span"); + const rawValue = Number.parseFloat(getByPath(block, editableField.path)); + const fallback = Number.parseFloat(editableField.defaultValue ?? editableField.min ?? 0); + const value = Number.isFinite(rawValue) ? rawValue : fallback; + + label.className = "field-control number-field"; + labelRow.className = "field-label-row"; + labelText.className = "field-label-text"; + labelText.textContent = editableField.label || editableField.path; + kind.className = "field-kind"; + kind.textContent = editableField.kind || "number"; + path.className = "field-path"; + path.textContent = editableField.path; + control.className = "number-control"; + suffix.className = "number-suffix"; + suffix.textContent = editableField.suffix || ""; + labelRow.append(labelText, kind); + + input.dataset.path = editableField.path; + input.dataset.valueType = "number"; + input.type = "number"; + input.value = Number.isFinite(value) ? String(value) : "0"; + input.autocomplete = "off"; + + if (editableField.min != null) input.min = String(editableField.min); + if (editableField.max != null) input.max = String(editableField.max); + if (editableField.step != null) input.step = String(editableField.step); + + input.addEventListener("input", () => { + const active = activeBlock(); + if (!active) return; + syncFieldValue(active, editableField.path, input); + }); + + control.append(input); + if (suffix.textContent) control.append(suffix); + label.append(labelRow, control, path); + grid.append(label); +} + function renderHeaderBooleanToggle({ block, editableField, head }) { const label = document.createElement("label"); const input = document.createElement("input"); @@ -1532,6 +1638,11 @@ function renderCollectionScalarField({ block, field, grid }) { return; } + if (field.kind === "number") { + renderNumberField({ block, editableField: field, grid }); + return; + } + const label = document.createElement("label"); const labelRow = document.createElement("span"); const labelText = document.createElement("span"); @@ -1666,8 +1777,14 @@ function renderCollectionField({ block, editableField, grid }) { const dragHandle = document.createElement("button"); const body = document.createElement("div"); const itemGrid = document.createElement("div"); + const hasVisibilityToggle = + item && + typeof item === "object" && + (Object.prototype.hasOwnProperty.call(item, "enabled") || + Object.prototype.hasOwnProperty.call(editableField.itemTemplate ?? {}, "enabled")); details.className = "collection-item-card"; + details.classList.toggle("is-hidden", item?.enabled === false); if (index === 0 && items.length <= 8) details.open = true; summary.className = "collection-summary"; summaryTitle.className = "collection-summary-title"; @@ -1685,6 +1802,29 @@ function renderCollectionField({ block, editableField, grid }) { dragHandle.addEventListener("pointerdown", (event) => startCollectionPointerDrag(event, details, items, index, rerender)); controls.append(dragHandle); + if (hasVisibilityToggle) { + if (!Object.prototype.hasOwnProperty.call(item, "enabled")) item.enabled = true; + + const visibilityToggle = document.createElement("span"); + const visibilityInput = document.createElement("input"); + const visibilityText = document.createElement("span"); + + visibilityToggle.className = "collection-visibility-toggle"; + visibilityToggle.title = "Скрыть/показать"; + visibilityToggle.setAttribute("aria-label", "Скрыть/показать"); + visibilityInput.type = "checkbox"; + visibilityInput.checked = item.enabled !== false; + visibilityText.textContent = "Показать"; + visibilityToggle.addEventListener("click", (event) => event.stopPropagation()); + visibilityInput.addEventListener("click", (event) => event.stopPropagation()); + visibilityInput.addEventListener("change", () => { + item.enabled = visibilityInput.checked; + rerender(visibilityInput.checked ? "Элемент коллекции показан локально." : "Элемент коллекции скрыт локально."); + }); + visibilityToggle.append(visibilityInput, visibilityText); + controls.append(visibilityToggle); + } + [ ["↑", "Поднять", () => { if (index === 0) return; @@ -1702,7 +1842,9 @@ function renderCollectionField({ block, editableField, grid }) { items.splice(index + 1, 0, prepareCollectionItemForInsert(items, item)); rerender("Элемент коллекции продублирован локально."); }], - ["⌫", "Удалить", () => { + ["⌫", "Удалить", async () => { + const confirmed = await requestDeleteConfirmation(); + if (!confirmed) return; items.splice(index, 1); rerender("Элемент коллекции удалён локально."); }], @@ -1716,7 +1858,7 @@ function renderCollectionField({ block, editableField, grid }) { button.addEventListener("click", (event) => { event.preventDefault(); event.stopPropagation(); - handler(); + Promise.resolve(handler()).catch((error) => setStatus(`Ошибка действия коллекции: ${error.message}`)); }); controls.append(button); }); @@ -1802,6 +1944,11 @@ function renderContentFields(block) { continue; } + if (editableField.kind === "number") { + renderNumberField({ block, editableField, grid }); + continue; + } + const label = document.createElement("label"); const labelRow = document.createElement("span"); const labelText = document.createElement("span"); @@ -1886,6 +2033,7 @@ async function loadPage() { state.page = await pageResponse.json(); state.templates = await templatesResponse.json(); ensureStaticElements(); + ensureSectionsRegion(); state.selected = { kind: "static" }; renderAll(); setDirty(false); @@ -1979,11 +2127,14 @@ function pasteAfterSelected() { markDirty(`Блок «${pasted.adminLabel || pasted.id}» вставлен локально.`); } -function deleteSelected() { +async function deleteSelected() { if (isStaticSelection()) return; const region = activeRegion(); if (!region || state.selected.blockIndex == null) return; + const confirmed = await requestDeleteConfirmation(); + if (!confirmed) return; + region.blocks.splice(state.selected.blockIndex, 1); state.selected.blockIndex = Math.min(state.selected.blockIndex, region.blocks.length - 1); if (state.selected.blockIndex < 0) state.selected = { kind: "static" }; @@ -2010,8 +2161,20 @@ el.templateModalCancel.addEventListener("click", closeTemplateModal); el.templateModal.addEventListener("click", (event) => { if (event.target === el.templateModal) closeTemplateModal(); }); +el.deleteModalCancel.addEventListener("click", () => closeDeleteModal(false)); +el.deleteModalConfirm.addEventListener("click", () => closeDeleteModal(true)); +el.deleteModal.addEventListener("click", (event) => { + if (event.target === el.deleteModal) closeDeleteModal(false); +}); document.addEventListener("keydown", (event) => { - if (event.key === "Escape" && !el.templateModal.classList.contains("hidden")) { + if (event.key !== "Escape") return; + + if (!el.deleteModal.classList.contains("hidden")) { + closeDeleteModal(false); + return; + } + + if (!el.templateModal.classList.contains("hidden")) { closeTemplateModal(); } }); @@ -2023,7 +2186,7 @@ el.moveDown.addEventListener("click", () => moveSelected(1)); el.duplicate.addEventListener("click", duplicateSelected); el.copy.addEventListener("click", copySelected); el.pasteAfter.addEventListener("click", pasteAfterSelected); -el.deleteBlock.addEventListener("click", deleteSelected); +el.deleteBlock.addEventListener("click", () => deleteSelected().catch((error) => setStatus(`Ошибка удаления: ${error.message}`))); window.addEventListener("error", (event) => { recoverEditorSurface(); diff --git a/admin/index.html b/admin/index.html index b7e5272..5059f0e 100644 --- a/admin/index.html +++ b/admin/index.html @@ -190,6 +190,21 @@ + diff --git a/assets/custom/css/app.css b/assets/custom/css/app.css index 45c0b91..cd15f46 100644 --- a/assets/custom/css/app.css +++ b/assets/custom/css/app.css @@ -1 +1,2 @@ *{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}img{pointer-events:none}.text-scrollable{scrollbar-width:none;-ms-overflow-style:none}.text-scrollable::-webkit-scrollbar{display:none}.circle .x-icon{opacity:0}.circle:hover .x-icon{opacity:1}[data-css=fade-tb]{mask-image:linear-gradient(#0000 0%,#000 15% 85%,#0000 100%);-webkit-mask-image:linear-gradient(#0000 0%,#000 15% 85%,#0000 100%);mask-size:100% 100%;mask-repeat:no-repeat;-webkit-mask-size:100% 100%;-webkit-mask-repeat:no-repeat}.current .mail-item{background-color:#3d3d3dc9}[data-module=""]{outline:10px solid red}[data-module=btn]>div>div>div>div{text-shadow:0 1.3em #fff;transition:transform .3s ease-in-out}[data-module=btn]:hover>div>div>div>div{transform:translateY(-1.3em)}[data-module=link]{transition:opacity .1s ease-in-out}[data-module=link] div>div{text-shadow:0 1.3em #fff;transition:transform .3s ease-in-out}[data-module=link]:hover{opacity:.6!important}[data-module=link]:hover div>div{transform:translateY(-.65em)}[data-module=apple-icon]:focus [data-txt]{position:relative}[data-module=apple-icon]:focus [data-txt]:before{content:"";position:absolute;z-index:-1;background-color:#0059d0;border-radius:.3em;width:120%;height:120%;top:-10%;left:-10%}[data-module=notification-w]{--notification-height:98%}[data-module=notification-w]>div:nth-child(2){transform:translateY(calc(-1*var(--notification-height)));z-index:2}[data-module=notification-w]>div:nth-child(3){transform:translateY(calc(-2*var(--notification-height)));z-index:3}[data-module=notification-w]:hover{--notification-height:98%}[data-module=notification-w]:hover>div:nth-child(2){z-index:2;transform:translateY(0)}[data-module=notification-w]:hover>div:nth-child(3){z-index:3;transform:translateY(0)}[data-module=notification]{transition:transform .3s cubic-bezier(.22,1,.36,1)} +.app-item.video[data-adaptive-video-window]{--nodedc-video-source-aspect:1.777778;--nodedc-video-height:620px;--nodedc-video-max-height:72svh;--nodedc-video-max-width:70vw;--nodedc-video-render-height:min(var(--nodedc-video-height),var(--nodedc-video-max-height));aspect-ratio:auto;width:min(var(--nodedc-video-max-width),calc(var(--nodedc-video-render-height)*var(--nodedc-video-source-aspect)));height:auto}.app-item.video[data-adaptive-video-window] .video-size[data-adaptive-video]{aspect-ratio:var(--nodedc-video-source-aspect);display:block;width:100%;height:auto;max-height:var(--nodedc-video-render-height)}.app-item.video[data-adaptive-video-window] .video-size[data-adaptive-video]>video.video{display:block;width:100%;height:100%;object-fit:cover}.video-screen [data-media-handle]{display:block;width:100%;height:100%}.video-screen [data-media-handle]>img,.video-screen [data-media-handle]>video{display:block;width:100%;height:100%;object-fit:cover}@media screen and (max-width:767px){.app-item.video[data-adaptive-video-window]{--nodedc-video-max-height:58svh;--nodedc-video-max-width:95vw}} diff --git a/assets/custom/css/out.css b/assets/custom/css/out.css index 874f9fd..295f362 100644 --- a/assets/custom/css/out.css +++ b/assets/custom/css/out.css @@ -1 +1,2 @@ *{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}img{pointer-events:none}.text-scrollable{scrollbar-width:none;-ms-overflow-style:none}.text-scrollable::-webkit-scrollbar{display:none}.circle .x-icon{opacity:0}.circle:hover .x-icon{opacity:1}[data-css=fade-tb]{mask-image:linear-gradient(#0000 0%,#000 15% 85%,#0000 100%);-webkit-mask-image:linear-gradient(#0000 0%,#000 15% 85%,#0000 100%);mask-size:100% 100%;mask-repeat:no-repeat;-webkit-mask-size:100% 100%;-webkit-mask-repeat:no-repeat}.current .mail-item{background-color:#3d3d3dc9}[data-module=""]{outline:10px solid red}[data-module=btn]>div>div>div>div{text-shadow:0 1.3em #fff;transition:transform .3s ease-in-out}[data-module=btn]:hover>div>div>div>div{transform:translateY(-1.3em)}[data-module=link]{transition:opacity .1s ease-in-out}[data-module=link] div>div{text-shadow:0 1.3em #fff;transition:transform .3s ease-in-out}[data-module=link]:hover{opacity:.6!important}[data-module=link]:hover div>div{transform:translateY(-.65em)}[data-module=apple-icon]:focus [data-txt]{position:relative}[data-module=apple-icon]:focus [data-txt]:before{content:"";position:absolute;z-index:-1;background-color:#0059d0;border-radius:.3em;width:120%;height:120%;top:-10%;left:-10%}[data-module=notification-w]{--notification-height:98%}[data-module=notification-w]>div:nth-child(2){transform:translateY(calc(-1*var(--notification-height)));z-index:2}[data-module=notification-w]>div:nth-child(3){transform:translateY(calc(-2*var(--notification-height)));z-index:3}[data-module=notification-w]:hover{--notification-height:98%}[data-module=notification-w]:hover>div:nth-child(2){z-index:2;transform:translateY(0)}[data-module=notification-w]:hover>div:nth-child(3){z-index:3;transform:translateY(0)}[data-module=notification]{transition:transform .3s cubic-bezier(.22,1,.36,1)}[data-start=closed]{pointer-events:none;visibility:hidden}[data-module=app]:not([data-start=closed]){transform:scale(0)translateY(200%)}[data-module=folder-wrap]>*{transform:scale(0)}[data-module=list] a{opacity:0}[data-success],[data-failure]{display:none}[data-module=alpha]{opacity:0} +.app-item.video[data-adaptive-video-window]{--nodedc-video-source-aspect:1.777778;--nodedc-video-height:620px;--nodedc-video-max-height:72svh;--nodedc-video-max-width:70vw;--nodedc-video-render-height:min(var(--nodedc-video-height),var(--nodedc-video-max-height));aspect-ratio:auto;width:min(var(--nodedc-video-max-width),calc(var(--nodedc-video-render-height)*var(--nodedc-video-source-aspect)));height:auto}.app-item.video[data-adaptive-video-window] .video-size[data-adaptive-video]{aspect-ratio:var(--nodedc-video-source-aspect);display:block;width:100%;height:auto;max-height:var(--nodedc-video-render-height)}.app-item.video[data-adaptive-video-window] .video-size[data-adaptive-video]>video.video{display:block;width:100%;height:100%;object-fit:cover}.video-screen [data-media-handle]{display:block;width:100%;height:100%}.video-screen [data-media-handle]>img,.video-screen [data-media-handle]>video{display:block;width:100%;height:100%;object-fit:cover}@media screen and (max-width:767px){.app-item.video[data-adaptive-video-window]{--nodedc-video-max-height:58svh;--nodedc-video-max-width:95vw}} diff --git a/assets/custom/js/interaction-overrides.js b/assets/custom/js/interaction-overrides.js index 3a780c6..72cc046 100644 --- a/assets/custom/js/interaction-overrides.js +++ b/assets/custom/js/interaction-overrides.js @@ -8,6 +8,10 @@ const MIN_SCROLL_SPEED = 0.2; const MAX_SCROLL_SPEED = 2; const DEFAULT_SCROLL_SPEED = 0.55; + const ADAPTIVE_VIDEO_BOUND_FLAG = "__nodedcAdaptiveVideoBound"; + const MEDIA_HANDLE_BOUND_FLAG = "__nodedcMediaHandleBound"; + const IMAGE_EXTENSIONS = new Set(["avif", "gif", "jpeg", "jpg", "png", "svg", "webp"]); + const VIDEO_EXTENSIONS = new Set(["m4v", "mov", "mp4", "webm"]); function isDesktopPointer() { return window.matchMedia?.("(pointer: fine)").matches ?? true; @@ -227,14 +231,106 @@ document.querySelectorAll('[data-module="mail-list"]').forEach(bindMailListWheel); } + function mediaExtension(src) { + const cleanSrc = String(src || "").split(/[?#]/)[0]; + const match = /\.([a-z0-9]+)$/i.exec(cleanSrc); + return match ? match[1].toLowerCase() : ""; + } + + function createMediaElement(src) { + const extension = mediaExtension(src); + + if (IMAGE_EXTENSIONS.has(extension)) { + const image = document.createElement("img"); + image.className = "video-el"; + image.src = src; + image.alt = ""; + image.loading = "lazy"; + image.decoding = "async"; + return image; + } + + if (!VIDEO_EXTENSIONS.has(extension)) return null; + + const video = document.createElement("video"); + video.className = "video-el"; + video.dataset.module = "video-handle"; + video.src = src; + video.muted = true; + video.loop = true; + video.playsInline = true; + video.preload = "none"; + video.setAttribute("playsinline", "true"); + return video; + } + + function hydrateMediaHandle(handle) { + if (!handle || handle[MEDIA_HANDLE_BOUND_FLAG]) return; + + const src = handle.dataset.mediaSrc; + const media = createMediaElement(src); + + if (!media) return; + handle[MEDIA_HANDLE_BOUND_FLAG] = true; + handle.textContent = ""; + handle.append(media); + } + + function hydrateMediaHandles() { + document.querySelectorAll("[data-media-handle]").forEach(hydrateMediaHandle); + } + + function applyAdaptiveVideoAspect(video) { + const surface = video.closest("[data-adaptive-video]"); + if (!surface || !video.videoWidth || !video.videoHeight) return; + + const windowEl = video.closest("[data-adaptive-video-window]"); + const sourceAspect = video.videoWidth / video.videoHeight; + const targets = windowEl ? [surface, windowEl] : [surface]; + + targets.forEach((target) => { + target.style.setProperty("--nodedc-video-source-aspect", sourceAspect.toFixed(6)); + }); + } + + function bindAdaptiveVideo(video) { + if (!video || video[ADAPTIVE_VIDEO_BOUND_FLAG]) return; + + video[ADAPTIVE_VIDEO_BOUND_FLAG] = true; + video.addEventListener("loadedmetadata", () => applyAdaptiveVideoAspect(video)); + applyAdaptiveVideoAspect(video); + } + + function bindAdaptiveVideos() { + document.querySelectorAll("[data-adaptive-video] video").forEach(bindAdaptiveVideo); + } + + hydrateMediaHandles(); + if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", bindInteractiveScroll, { once: true }); + document.addEventListener( + "DOMContentLoaded", + () => { + hydrateMediaHandles(); + bindInteractiveScroll(); + bindAdaptiveVideos(); + }, + { once: true }, + ); } else { + hydrateMediaHandles(); bindInteractiveScroll(); + bindAdaptiveVideos(); } window.addEventListener("load", () => { + hydrateMediaHandles(); bindInteractiveScroll(); - window.setTimeout(bindInteractiveScroll, 500); + bindAdaptiveVideos(); + window.setTimeout(() => { + hydrateMediaHandles(); + bindInteractiveScroll(); + bindAdaptiveVideos(); + }, 500); }); })(); diff --git a/assets/uploads/media/ai-modes/ndc_engine_base-mqv6lvyr.mp4 b/assets/uploads/media/ai-modes/ndc_engine_base-mqv6lvyr.mp4 new file mode 100644 index 0000000..ea1b068 Binary files /dev/null and b/assets/uploads/media/ai-modes/ndc_engine_base-mqv6lvyr.mp4 differ diff --git a/assets/uploads/media/ai-modes/ndc_hub_base-mqv6lm07.mp4 b/assets/uploads/media/ai-modes/ndc_hub_base-mqv6lm07.mp4 new file mode 100644 index 0000000..ca9cb95 Binary files /dev/null and b/assets/uploads/media/ai-modes/ndc_hub_base-mqv6lm07.mp4 differ diff --git a/assets/uploads/media/ai-modes/ndc_ops_blur-mqv6m5i3.mp4 b/assets/uploads/media/ai-modes/ndc_ops_blur-mqv6m5i3.mp4 new file mode 100644 index 0000000..c43b78e Binary files /dev/null and b/assets/uploads/media/ai-modes/ndc_ops_blur-mqv6m5i3.mp4 differ diff --git a/assets/uploads/media/ai-modes/ndc_ops_blur-mqv6qi8l.mp4 b/assets/uploads/media/ai-modes/ndc_ops_blur-mqv6qi8l.mp4 new file mode 100644 index 0000000..c43b78e Binary files /dev/null and b/assets/uploads/media/ai-modes/ndc_ops_blur-mqv6qi8l.mp4 differ diff --git a/assets/uploads/media/ai-modes/ndc_ops_blur-mqv6ss6k.mp4 b/assets/uploads/media/ai-modes/ndc_ops_blur-mqv6ss6k.mp4 new file mode 100644 index 0000000..c43b78e Binary files /dev/null and b/assets/uploads/media/ai-modes/ndc_ops_blur-mqv6ss6k.mp4 differ diff --git a/assets/uploads/media/component-library-copy-copy-copy/ndc_1c-mqvamdlq.jpg b/assets/uploads/media/component-library-copy-copy-copy/ndc_1c-mqvamdlq.jpg new file mode 100644 index 0000000..7715466 Binary files /dev/null and b/assets/uploads/media/component-library-copy-copy-copy/ndc_1c-mqvamdlq.jpg differ diff --git a/assets/uploads/media/component-library-copy-copy-copy/ndc_dt_mmap-mqvamokr.jpg b/assets/uploads/media/component-library-copy-copy-copy/ndc_dt_mmap-mqvamokr.jpg new file mode 100644 index 0000000..a5f0775 Binary files /dev/null and b/assets/uploads/media/component-library-copy-copy-copy/ndc_dt_mmap-mqvamokr.jpg differ diff --git a/assets/uploads/media/component-library-copy-copy-copy/ndc_tender_p-mqvamj2p.jpeg b/assets/uploads/media/component-library-copy-copy-copy/ndc_tender_p-mqvamj2p.jpeg new file mode 100644 index 0000000..949d243 Binary files /dev/null and b/assets/uploads/media/component-library-copy-copy-copy/ndc_tender_p-mqvamj2p.jpeg differ diff --git a/assets/uploads/media/component-library-copy-copy/cast_ops_bim-mqv9gwoe.mp4 b/assets/uploads/media/component-library-copy-copy/cast_ops_bim-mqv9gwoe.mp4 new file mode 100644 index 0000000..d37b1ba Binary files /dev/null and b/assets/uploads/media/component-library-copy-copy/cast_ops_bim-mqv9gwoe.mp4 differ diff --git a/assets/uploads/media/component-library-copy-copy/cast_ops_cards-mqv9go9s.mp4 b/assets/uploads/media/component-library-copy-copy/cast_ops_cards-mqv9go9s.mp4 new file mode 100644 index 0000000..e450de6 Binary files /dev/null and b/assets/uploads/media/component-library-copy-copy/cast_ops_cards-mqv9go9s.mp4 differ diff --git a/assets/uploads/media/component-library-copy-copy/cast_ops_dashboard-mqv9gga5.mp4 b/assets/uploads/media/component-library-copy-copy/cast_ops_dashboard-mqv9gga5.mp4 new file mode 100644 index 0000000..1544106 Binary files /dev/null and b/assets/uploads/media/component-library-copy-copy/cast_ops_dashboard-mqv9gga5.mp4 differ diff --git a/assets/uploads/media/component-library-copy/engine_coding_agent-mqv94pol.mov b/assets/uploads/media/component-library-copy/engine_coding_agent-mqv94pol.mov new file mode 100644 index 0000000..e1a4356 Binary files /dev/null and b/assets/uploads/media/component-library-copy/engine_coding_agent-mqv94pol.mov differ diff --git a/assets/uploads/media/component-library-copy/engine_coding_agent-mqv96lq3.mp4 b/assets/uploads/media/component-library-copy/engine_coding_agent-mqv96lq3.mp4 new file mode 100644 index 0000000..322c676 Binary files /dev/null and b/assets/uploads/media/component-library-copy/engine_coding_agent-mqv96lq3.mp4 differ diff --git a/assets/uploads/media/component-library-copy/engine_coding_workflow-mqv94k3x.mov b/assets/uploads/media/component-library-copy/engine_coding_workflow-mqv94k3x.mov new file mode 100644 index 0000000..b268196 Binary files /dev/null and b/assets/uploads/media/component-library-copy/engine_coding_workflow-mqv94k3x.mov differ diff --git a/assets/uploads/media/component-library-copy/engine_coding_workflow-mqv96gya.mp4 b/assets/uploads/media/component-library-copy/engine_coding_workflow-mqv96gya.mp4 new file mode 100644 index 0000000..741d097 Binary files /dev/null and b/assets/uploads/media/component-library-copy/engine_coding_workflow-mqv96gya.mp4 differ diff --git a/assets/uploads/media/component-library-copy/engine_monitor-mqv94vo6.mov b/assets/uploads/media/component-library-copy/engine_monitor-mqv94vo6.mov new file mode 100644 index 0000000..67eae80 Binary files /dev/null and b/assets/uploads/media/component-library-copy/engine_monitor-mqv94vo6.mov differ diff --git a/assets/uploads/media/component-library-copy/engine_monitor-mqv96tdn.mp4 b/assets/uploads/media/component-library-copy/engine_monitor-mqv96tdn.mp4 new file mode 100644 index 0000000..ea55506 Binary files /dev/null and b/assets/uploads/media/component-library-copy/engine_monitor-mqv96tdn.mp4 differ diff --git a/assets/uploads/media/component-library/hub_audit-mqv7z4xo.mov b/assets/uploads/media/component-library/hub_audit-mqv7z4xo.mov new file mode 100644 index 0000000..f16ae80 Binary files /dev/null and b/assets/uploads/media/component-library/hub_audit-mqv7z4xo.mov differ diff --git a/assets/uploads/media/component-library/hub_dostup-mqv7zakv.mov b/assets/uploads/media/component-library/hub_dostup-mqv7zakv.mov new file mode 100644 index 0000000..eb9017d Binary files /dev/null and b/assets/uploads/media/component-library/hub_dostup-mqv7zakv.mov differ diff --git a/assets/uploads/media/component-library/hub_vitrina-mqv7yt7k.mov b/assets/uploads/media/component-library/hub_vitrina-mqv7yt7k.mov new file mode 100644 index 0000000..1b7dbc5 Binary files /dev/null and b/assets/uploads/media/component-library/hub_vitrina-mqv7yt7k.mov differ diff --git a/assets/uploads/media/feature-video-copy-copy/ndc_ops_blur-mqv99k3r.mp4 b/assets/uploads/media/feature-video-copy-copy/ndc_ops_blur-mqv99k3r.mp4 new file mode 100644 index 0000000..c43b78e Binary files /dev/null and b/assets/uploads/media/feature-video-copy-copy/ndc_ops_blur-mqv99k3r.mp4 differ diff --git a/assets/uploads/media/feature-video-copy/ndc_engine_base-mqv82irv.mp4 b/assets/uploads/media/feature-video-copy/ndc_engine_base-mqv82irv.mp4 new file mode 100644 index 0000000..ea1b068 Binary files /dev/null and b/assets/uploads/media/feature-video-copy/ndc_engine_base-mqv82irv.mp4 differ diff --git a/assets/uploads/media/feature-video/ndc_hub_base-mqv741eh.mp4 b/assets/uploads/media/feature-video/ndc_hub_base-mqv741eh.mp4 new file mode 100644 index 0000000..ca9cb95 Binary files /dev/null and b/assets/uploads/media/feature-video/ndc_hub_base-mqv741eh.mp4 differ diff --git a/content/block-templates/home.json b/content/block-templates/home.json index e84695a..504fd5c 100644 --- a/content/block-templates/home.json +++ b/content/block-templates/home.json @@ -1,16 +1,16 @@ [ { - "key": "beforeCViz:heroWaitlist:hero-waitlist.html", + "key": "beforeCViz:heroWaitlist:theses-request-demo.html", "regionId": "beforeCViz", "regionLabel": "Главная", - "title": "Первый экран и заявка", - "description": "hero-waitlist.html", + "title": "ТЕЗИСЫ + ЗАПРОС ДЕМО", + "description": "theses-request-demo.html", "block": { "region": "beforeCViz", "id": "hero-waitlist", "type": "heroWaitlist", - "template": "hero-waitlist.html", - "adminLabel": "Первый экран и заявка", + "template": "theses-request-demo.html", + "adminLabel": "ТЕЗИСЫ + ЗАПРОС ДЕМО", "anchor": "hero", "content": { "heading": { @@ -299,17 +299,145 @@ } }, { - "key": "beforeCViz:featureVideo:feature-video.html", + "key": "cViz:aiModes:project-composition.html", + "regionId": "cViz", + "regionLabel": "Секции", + "title": "СОСТАВ ПРОЕКТА", + "description": "project-composition.html", + "block": { + "region": "cViz", + "id": "ai-modes", + "type": "aiModes", + "template": "project-composition.html", + "adminLabel": "СОСТАВ ПРОЕКТА", + "anchor": null, + "content": { + "heading": { + "muted": "Гибкие", + "main": "режимы ИИ." + }, + "introHtml": "Разные режимы и модели для разных задач.", + "sliderItems": [ + { + "enabled": true, + "videoSrc": "./assets/media/various-modes.mp4", + "title": "Переключение режимов ИИ" + }, + { + "enabled": true, + "videoSrc": "./assets/media/project-flow.mp4", + "title": "Создание нового проекта" + }, + { + "enabled": true, + "videoSrc": "./assets/media/ai-dock-mode.mp4", + "title": "ИИ-док" + }, + { + "enabled": true, + "videoSrc": "./assets/media/account.mp4", + "title": "Аккаунт" + }, + { + "enabled": true, + "videoSrc": "./assets/media/code-edits.mp4", + "title": "Редактирование кода с ИИ" + }, + { + "enabled": true, + "videoSrc": "./assets/media/dev-server.mp4", + "title": "Dev-сервер с ИИ" + }, + { + "enabled": true, + "videoSrc": "./assets/media/webflow-mcp.mp4", + "title": "Webflow MCP" + }, + { + "enabled": true, + "videoSrc": "./assets/media/publish-flow.mp4", + "title": "Публикация" + } + ], + "scroll": { + "speed": 0.55 + } + }, + "editableFields": [ + { + "path": "content.scroll.speed", + "label": "Скорость скролла", + "kind": "range", + "renderAs": "number", + "group": "Скролл", + "min": 0.2, + "max": 2, + "step": 0.05, + "suffix": "x" + }, + { + "path": "content.heading.muted", + "label": "Заголовок: приглушённая строка", + "kind": "text", + "renderAs": "text", + "group": "Заголовок" + }, + { + "path": "content.heading.main", + "label": "Заголовок: основная строка", + "kind": "text", + "renderAs": "text", + "group": "Заголовок" + }, + { + "path": "content.introHtml", + "label": "Вводный текст", + "kind": "html", + "renderAs": "html", + "group": "Заголовок" + }, + { + "path": "content.sliderItems", + "label": "Слайды", + "kind": "collection", + "renderAs": "collection", + "group": "Слайдер", + "itemTemplate": { + "enabled": true, + "videoSrc": "./assets/media/publish-flow.mp4", + "title": "Новый слайд" + }, + "itemFields": [ + { + "path": "videoSrc", + "label": "Видео", + "kind": "media", + "renderAs": "attr" + }, + { + "path": "title", + "label": "Подпись", + "kind": "text", + "renderAs": "text" + } + ] + } + ], + "enabled": true + } + }, + { + "key": "beforeCViz:featureVideo:project-video-first.html", "regionId": "beforeCViz", "regionLabel": "Главная", - "title": "Демо и базовая логика", - "description": "feature-video.html", + "title": "PROJECT VIDEO - FIRST", + "description": "project-video-first.html", "block": { "region": "beforeCViz", "id": "feature-video", "type": "featureVideo", - "template": "feature-video.html", - "adminLabel": "Демо и базовая логика", + "template": "project-video-first.html", + "adminLabel": "PROJECT VIDEO - FIRST", "anchor": "features", "content": { "windowTitle": "демо-видео.mp4", @@ -336,7 +464,8 @@ "eyebrow": "Деплой в один клик", "bodyHtml": "Нажмите и публикуйте кастомный код, написанный ИИ, чтобы он стал доступен всем посетителям сайта." } - ] + ], + "videoHeightPx": 620 }, "editableFields": [ { @@ -353,6 +482,18 @@ "renderAs": "attr", "group": "Видео-окно" }, + { + "path": "content.videoHeightPx", + "label": "Высота видео", + "kind": "number", + "renderAs": "number", + "group": "Видео-окно", + "min": 240, + "max": 900, + "step": 1, + "suffix": "px", + "defaultValue": 620 + }, { "path": "content.heading.muted", "label": "Заголовок: приглушённая строка", @@ -435,17 +576,17 @@ } }, { - "key": "cViz:componentLibrary:component-library.html", + "key": "cViz:componentLibrary:project-video-detail.html", "regionId": "cViz", "regionLabel": "Секции", - "title": "Компонентная библиотека и MCP", - "description": "component-library.html", + "title": "PROJECT VIDEO - DETAIL", + "description": "project-video-detail.html", "block": { "region": "cViz", "id": "component-library", "type": "componentLibrary", - "template": "component-library.html", - "adminLabel": "Компонентная библиотека и MCP", + "template": "project-video-detail.html", + "adminLabel": "PROJECT VIDEO - DETAIL", "anchor": null, "content": { "heading": { @@ -457,69 +598,45 @@ "sliderItems": [ { "videoSrc": "./assets/media/various-modes.mp4", - "title": "Переключение режимов ИИ" + "title": "Переключение режимов ИИ", + "enabled": true }, { "videoSrc": "./assets/media/project-flow.mp4", - "title": "Создание нового проекта" + "title": "Создание нового проекта", + "enabled": true }, { "videoSrc": "./assets/media/ai-dock-mode.mp4", - "title": "ИИ-док" + "title": "ИИ-док", + "enabled": true }, { "videoSrc": "./assets/media/account.mp4", - "title": "Аккаунт" + "title": "Аккаунт", + "enabled": true }, { "videoSrc": "./assets/media/code-edits.mp4", - "title": "Редактирование кода с ИИ" + "title": "Редактирование кода с ИИ", + "enabled": true }, { "videoSrc": "./assets/media/dev-server.mp4", - "title": "Dev-сервер с ИИ" + "title": "Dev-сервер с ИИ", + "enabled": true }, { "videoSrc": "./assets/media/webflow-mcp.mp4", - "title": "Webflow MCP" + "title": "Webflow MCP", + "enabled": true }, { "videoSrc": "./assets/media/publish-flow.mp4", - "title": "Публикация" + "title": "Публикация", + "enabled": true } ], - "oneClick": { - "heading": { - "muted": "Один", - "middle": "клик", - "main": "добавления." - }, - "introHtml": "С помощью расширения NODE.DC для Webflow. ", - "items": [ - { - "eyebrow": "Понимание компонентов", - "bodyHtml": "Используйте несколько агентов параллельно чтобы добавлять кастомный код, анимации и функциональность." - }, - { - "eyebrow": "Быстрый старт", - "bodyHtml": "Используйте быстрые Bun сборки и автообновление для вашего кастомного кода." - } - ] - }, - "mcp": { - "heading": { - "muted": "Получите", - "middle": "более сильный", - "main": "MCP." - }, - "introHtml": "Полностью переработанный и более быстрый MCP-сервер. ", - "items": [ - { - "eyebrow": "Понимание Webflow", - "bodyHtml": "Используйте несколько агентов параллельно чтобы добавлять кастомный код, анимации и функциональность." - } - ] - }, "scroll": { "speed": 0.55 } @@ -565,420 +682,30 @@ "group": "Заголовок" }, { - "path": "content.sliderItems.0.videoSrc", - "label": "Слайд 1: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.0.title", - "label": "Слайд 1: подпись", - "kind": "text", - "renderAs": "text", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.1.videoSrc", - "label": "Слайд 2: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.1.title", - "label": "Слайд 2: подпись", - "kind": "text", - "renderAs": "text", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.2.videoSrc", - "label": "Слайд 3: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.2.title", - "label": "Слайд 3: подпись", - "kind": "text", - "renderAs": "text", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.3.videoSrc", - "label": "Слайд 4: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.3.title", - "label": "Слайд 4: подпись", - "kind": "text", - "renderAs": "text", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.4.videoSrc", - "label": "Слайд 5: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.4.title", - "label": "Слайд 5: подпись", - "kind": "text", - "renderAs": "text", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.5.videoSrc", - "label": "Слайд 6: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.5.title", - "label": "Слайд 6: подпись", - "kind": "text", - "renderAs": "text", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.6.videoSrc", - "label": "Слайд 7: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.6.title", - "label": "Слайд 7: подпись", - "kind": "text", - "renderAs": "text", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.7.videoSrc", - "label": "Слайд 8: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.7.title", - "label": "Слайд 8: подпись", - "kind": "text", - "renderAs": "text", - "group": "Слайдер" - }, - { - "path": "content.oneClick.heading.muted", - "label": "Один клик: приглушённая строка", - "kind": "text", - "renderAs": "text", - "group": "Один клик" - }, - { - "path": "content.oneClick.heading.middle", - "label": "Один клик: средняя строка", - "kind": "text", - "renderAs": "text", - "group": "Один клик" - }, - { - "path": "content.oneClick.heading.main", - "label": "Один клик: основная строка", - "kind": "text", - "renderAs": "text", - "group": "Один клик" - }, - { - "path": "content.oneClick.introHtml", - "label": "Один клик: вводный текст", - "kind": "html", - "renderAs": "html", - "group": "Один клик" - }, - { - "path": "content.oneClick.items.0.eyebrow", - "label": "Один клик: пункт 1, подпись", - "kind": "text", - "renderAs": "text", - "group": "Один клик" - }, - { - "path": "content.oneClick.items.0.bodyHtml", - "label": "Один клик: пункт 1, текст", - "kind": "html", - "renderAs": "html", - "group": "Один клик" - }, - { - "path": "content.oneClick.items.1.eyebrow", - "label": "Один клик: пункт 2, подпись", - "kind": "text", - "renderAs": "text", - "group": "Один клик" - }, - { - "path": "content.oneClick.items.1.bodyHtml", - "label": "Один клик: пункт 2, текст", - "kind": "html", - "renderAs": "html", - "group": "Один клик" - }, - { - "path": "content.mcp.heading.muted", - "label": "MCP: приглушённая строка", - "kind": "text", - "renderAs": "text", - "group": "MCP" - }, - { - "path": "content.mcp.heading.middle", - "label": "MCP: средняя строка", - "kind": "text", - "renderAs": "text", - "group": "MCP" - }, - { - "path": "content.mcp.heading.main", - "label": "MCP: основная строка", - "kind": "text", - "renderAs": "text", - "group": "MCP" - }, - { - "path": "content.mcp.introHtml", - "label": "MCP: вводный текст", - "kind": "html", - "renderAs": "html", - "group": "MCP" - }, - { - "path": "content.mcp.items.0.eyebrow", - "label": "MCP: пункт 1, подпись", - "kind": "text", - "renderAs": "text", - "group": "MCP" - }, - { - "path": "content.mcp.items.0.bodyHtml", - "label": "MCP: пункт 1, текст", - "kind": "html", - "renderAs": "html", - "group": "MCP" - } - ], - "enabled": true - } - }, - { - "key": "cViz:aiModes:ai-modes.html", - "regionId": "cViz", - "regionLabel": "Секции", - "title": "Гибкие режимы ИИ", - "description": "ai-modes.html", - "block": { - "region": "cViz", - "id": "ai-modes", - "type": "aiModes", - "template": "ai-modes.html", - "adminLabel": "Гибкие режимы ИИ", - "anchor": null, - "content": { - "heading": { - "muted": "Гибкие", - "main": "режимы ИИ." - }, - "introHtml": "Разные режимы и модели для разных задач.", - "sliderItems": [ - { - "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" - }, - { + "path": "content.sliderItems", + "label": "Слайды", + "kind": "collection", + "renderAs": "collection", + "group": "Слайдер", + "itemTemplate": { + "enabled": true, "videoSrc": "./assets/media/publish-flow.mp4", - "title": "Публикация" - } - ], - "scroll": { - "speed": 0.55 - } - }, - "editableFields": [ - { - "path": "content.scroll.speed", - "label": "Скорость скролла", - "kind": "range", - "renderAs": "number", - "group": "Скролл", - "min": 0.2, - "max": 2, - "step": 0.05, - "suffix": "x" - }, - { - "path": "content.heading.muted", - "label": "Заголовок: приглушённая строка", - "kind": "text", - "renderAs": "text", - "group": "Заголовок" - }, - { - "path": "content.heading.main", - "label": "Заголовок: основная строка", - "kind": "text", - "renderAs": "text", - "group": "Заголовок" - }, - { - "path": "content.introHtml", - "label": "Вводный текст", - "kind": "html", - "renderAs": "html", - "group": "Заголовок" - }, - { - "path": "content.sliderItems.0.videoSrc", - "label": "Слайд 1: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.0.title", - "label": "Слайд 1: подпись", - "kind": "text", - "renderAs": "text", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.1.videoSrc", - "label": "Слайд 2: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.1.title", - "label": "Слайд 2: подпись", - "kind": "text", - "renderAs": "text", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.2.videoSrc", - "label": "Слайд 3: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.2.title", - "label": "Слайд 3: подпись", - "kind": "text", - "renderAs": "text", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.3.videoSrc", - "label": "Слайд 4: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.3.title", - "label": "Слайд 4: подпись", - "kind": "text", - "renderAs": "text", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.4.videoSrc", - "label": "Слайд 5: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.4.title", - "label": "Слайд 5: подпись", - "kind": "text", - "renderAs": "text", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.5.videoSrc", - "label": "Слайд 6: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.5.title", - "label": "Слайд 6: подпись", - "kind": "text", - "renderAs": "text", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.6.videoSrc", - "label": "Слайд 7: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.6.title", - "label": "Слайд 7: подпись", - "kind": "text", - "renderAs": "text", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.7.videoSrc", - "label": "Слайд 8: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.7.title", - "label": "Слайд 8: подпись", - "kind": "text", - "renderAs": "text", - "group": "Слайдер" + "title": "Новый слайд" + }, + "itemFields": [ + { + "path": "videoSrc", + "label": "Видео / картинка", + "kind": "media", + "renderAs": "attr" + }, + { + "path": "title", + "label": "Подпись", + "kind": "text", + "renderAs": "text" + } + ] } ], "enabled": true diff --git a/content/pages/home.json b/content/pages/home.json index 0659e69..a83b434 100644 --- a/content/pages/home.json +++ b/content/pages/home.json @@ -336,16 +336,16 @@ }, "regions": [ { - "id": "beforeCViz", - "label": "До c-viz wrapper", - "description": "Hero and first product section. These blocks live before the visual wrapper.", + "id": "sections", + "label": "Секции", + "description": "Единый порядок секций главной страницы.", "blocks": [ { "region": "beforeCViz", "id": "hero-waitlist", "type": "heroWaitlist", - "template": "hero-waitlist.html", - "adminLabel": "Первый экран и заявка", + "template": "theses-request-demo.html", + "adminLabel": "ТЕЗИСЫ + ЗАПРОС ДЕМО", "anchor": "hero", "content": { "heading": { @@ -632,39 +632,136 @@ ], "enabled": true }, + { + "region": "cViz", + "id": "ai-modes", + "type": "aiModes", + "template": "project-composition.html", + "adminLabel": "СОСТАВ ПРОЕКТА", + "anchor": null, + "content": { + "heading": { + "muted": "NODE.DC", + "main": "режимы / слои" + }, + "introHtml": "Строится из базовых слоев и прикладных модулей: вход, разработка, операционная\nработа, агенты, интеграции и инженерные данные.", + "sliderItems": [ + { + "enabled": true, + "videoSrc": "./assets/uploads/media/ai-modes/ndc_hub_base-mqv6lm07.mp4", + "title": "HUB - слой витрин и доступов" + }, + { + "enabled": true, + "videoSrc": "./assets/uploads/media/ai-modes/ndc_engine_base-mqv6lvyr.mp4", + "title": "ENGINE - среда разбаботки сервисов и автоматизаций" + }, + { + "enabled": true, + "videoSrc": "./assets/uploads/media/ai-modes/ndc_ops_blur-mqv6ss6k.mp4", + "title": "OPS - операционный / административный слой" + } + ], + "scroll": { + "speed": 0.55 + } + }, + "editableFields": [ + { + "path": "content.scroll.speed", + "label": "Скорость скролла", + "kind": "range", + "renderAs": "number", + "group": "Скролл", + "min": 0.2, + "max": 2, + "step": 0.05, + "suffix": "x" + }, + { + "path": "content.heading.muted", + "label": "Заголовок: приглушённая строка", + "kind": "text", + "renderAs": "text", + "group": "Заголовок" + }, + { + "path": "content.heading.main", + "label": "Заголовок: основная строка", + "kind": "text", + "renderAs": "text", + "group": "Заголовок" + }, + { + "path": "content.introHtml", + "label": "Вводный текст", + "kind": "html", + "renderAs": "html", + "group": "Заголовок" + }, + { + "path": "content.sliderItems", + "label": "Слайды", + "kind": "collection", + "renderAs": "collection", + "group": "Слайдер", + "itemTemplate": { + "enabled": true, + "videoSrc": "./assets/media/publish-flow.mp4", + "title": "Новый слайд" + }, + "itemFields": [ + { + "path": "videoSrc", + "label": "Видео", + "kind": "media", + "renderAs": "attr" + }, + { + "path": "title", + "label": "Подпись", + "kind": "text", + "renderAs": "text" + } + ] + } + ], + "enabled": true + }, { "region": "beforeCViz", "id": "feature-video", "type": "featureVideo", - "template": "feature-video.html", - "adminLabel": "Демо и базовая логика", + "template": "project-video-first.html", + "adminLabel": "HUB - PROJECT VIDEO - FIRST", "anchor": "features", "content": { - "windowTitle": "демо-видео.mp4", - "videoSrc": "./assets/media/publish-flow.mp4", + "windowTitle": "NDC_HUB.mp4", + "videoSrc": "./assets/uploads/media/feature-video/ndc_hub_base-mqv741eh.mp4", "heading": { - "muted": "Всё в одном", - "main": "приложение." + "muted": "HUB", + "main": "точка входа в платформу " }, "introHtml": "Универсальный инструмент для ИИ-агентов в вашем Webflow-проекте.", "features": [ { - "eyebrow": "Контекст проекта", - "bodyHtml": "Подключитесь к MCP чтобы агенты получили полный контекст вашей сборки." + "eyebrow": "Каталог приложений", + "bodyHtml": "Доступные модули отображаются по роли, группе и контуру предприятия." }, { - "eyebrow": "Группы агентов", - "bodyHtml": "Используйте несколько агентов параллельно чтобы добавлять кастомный код, анимации и функциональность." + "eyebrow": "Рабочие пространства", + "bodyHtml": "Проекты, отделы и enterprise-контуры разделяются по правилам доступа." }, { - "eyebrow": "Среда разработки", - "bodyHtml": "Используйте быстрые Bun сборки и автообновление для вашего кастомного кода." + "eyebrow": "Администрирование", + "bodyHtml": "Пользователи, группы, роли и права управляются из единой административной зоны." }, { - "eyebrow": "Деплой в один клик", - "bodyHtml": "Нажмите и публикуйте кастомный код, написанный ИИ, чтобы он стал доступен всем посетителям сайта." + "eyebrow": "Сквозной доступ", + "bodyHtml": "Переход к Engine, Ops и прикладным модулям сохраняет общий контекст платформы." } - ] + ], + "videoHeightPx": 700 }, "editableFields": [ { @@ -681,6 +778,18 @@ "renderAs": "attr", "group": "Видео-окно" }, + { + "path": "content.videoHeightPx", + "label": "Высота видео", + "kind": "number", + "renderAs": "number", + "group": "Видео-окно", + "min": 240, + "max": 900, + "step": 1, + "suffix": "px", + "defaultValue": 620 + }, { "path": "content.heading.muted", "label": "Заголовок: приглушённая строка", @@ -760,94 +869,38 @@ } ], "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": [ + }, { "region": "cViz", "id": "component-library", "type": "componentLibrary", - "template": "component-library.html", - "adminLabel": "Компонентная библиотека и MCP", + "template": "project-video-detail.html", + "adminLabel": "HUB - PROJECT VIDEO - DETAIL", "anchor": null, "content": { "heading": { - "muted": "Агентная", - "middle": "Компонентная", - "main": "библиотека." + "muted": "Hub.", + "middle": " Контур", + "main": "доступа." }, - "introHtml": "Готовая к работе с агентами компонентная библиотека: добавление в один клик через приложение NODE.DC для Webflow и дальнейшая настройка под проект.", + "introHtml": "Централизованный запуск приложений, управление ролями, группами и доступом к каждому рабочему модулю.", "sliderItems": [ { - "videoSrc": "./assets/media/various-modes.mp4", - "title": "Переключение режимов ИИ" + "videoSrc": "./assets/uploads/media/component-library/hub_vitrina-mqv7yt7k.mov", + "title": "Каталог приложений", + "enabled": true }, { - "videoSrc": "./assets/media/project-flow.mp4", - "title": "Создание нового проекта" + "videoSrc": "./assets/uploads/media/component-library/hub_audit-mqv7z4xo.mov", + "title": "Роли и контуры", + "enabled": true }, { - "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": "Публикация" + "videoSrc": "./assets/uploads/media/component-library/hub_dostup-mqv7zakv.mov", + "title": "Группы и доступы", + "enabled": true } ], - "oneClick": { - "heading": { - "muted": "Один", - "middle": "клик", - "main": "добавления." - }, - "introHtml": "С помощью расширения NODE.DC для Webflow. ", - "items": [ - { - "eyebrow": "Понимание компонентов", - "bodyHtml": "Используйте несколько агентов параллельно чтобы добавлять кастомный код, анимации и функциональность." - }, - { - "eyebrow": "Быстрый старт", - "bodyHtml": "Используйте быстрые Bun сборки и автообновление для вашего кастомного кода." - } - ] - }, - "mcp": { - "heading": { - "muted": "Получите", - "middle": "более сильный", - "main": "MCP." - }, - "introHtml": "Полностью переработанный и более быстрый MCP-сервер. ", - "items": [ - { - "eyebrow": "Понимание Webflow", - "bodyHtml": "Используйте несколько агентов параллельно чтобы добавлять кастомный код, анимации и функциональность." - } - ] - }, "scroll": { "speed": 0.2 } @@ -893,280 +946,95 @@ "group": "Заголовок" }, { - "path": "content.sliderItems.0.videoSrc", - "label": "Слайд 1: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.0.title", - "label": "Слайд 1: подпись", - "kind": "text", - "renderAs": "text", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.1.videoSrc", - "label": "Слайд 2: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.1.title", - "label": "Слайд 2: подпись", - "kind": "text", - "renderAs": "text", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.2.videoSrc", - "label": "Слайд 3: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.2.title", - "label": "Слайд 3: подпись", - "kind": "text", - "renderAs": "text", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.3.videoSrc", - "label": "Слайд 4: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.3.title", - "label": "Слайд 4: подпись", - "kind": "text", - "renderAs": "text", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.4.videoSrc", - "label": "Слайд 5: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.4.title", - "label": "Слайд 5: подпись", - "kind": "text", - "renderAs": "text", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.5.videoSrc", - "label": "Слайд 6: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.5.title", - "label": "Слайд 6: подпись", - "kind": "text", - "renderAs": "text", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.6.videoSrc", - "label": "Слайд 7: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.6.title", - "label": "Слайд 7: подпись", - "kind": "text", - "renderAs": "text", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.7.videoSrc", - "label": "Слайд 8: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.7.title", - "label": "Слайд 8: подпись", - "kind": "text", - "renderAs": "text", - "group": "Слайдер" - }, - { - "path": "content.oneClick.heading.muted", - "label": "Один клик: приглушённая строка", - "kind": "text", - "renderAs": "text", - "group": "Один клик" - }, - { - "path": "content.oneClick.heading.middle", - "label": "Один клик: средняя строка", - "kind": "text", - "renderAs": "text", - "group": "Один клик" - }, - { - "path": "content.oneClick.heading.main", - "label": "Один клик: основная строка", - "kind": "text", - "renderAs": "text", - "group": "Один клик" - }, - { - "path": "content.oneClick.introHtml", - "label": "Один клик: вводный текст", - "kind": "html", - "renderAs": "html", - "group": "Один клик" - }, - { - "path": "content.oneClick.items.0.eyebrow", - "label": "Один клик: пункт 1, подпись", - "kind": "text", - "renderAs": "text", - "group": "Один клик" - }, - { - "path": "content.oneClick.items.0.bodyHtml", - "label": "Один клик: пункт 1, текст", - "kind": "html", - "renderAs": "html", - "group": "Один клик" - }, - { - "path": "content.oneClick.items.1.eyebrow", - "label": "Один клик: пункт 2, подпись", - "kind": "text", - "renderAs": "text", - "group": "Один клик" - }, - { - "path": "content.oneClick.items.1.bodyHtml", - "label": "Один клик: пункт 2, текст", - "kind": "html", - "renderAs": "html", - "group": "Один клик" - }, - { - "path": "content.mcp.heading.muted", - "label": "MCP: приглушённая строка", - "kind": "text", - "renderAs": "text", - "group": "MCP" - }, - { - "path": "content.mcp.heading.middle", - "label": "MCP: средняя строка", - "kind": "text", - "renderAs": "text", - "group": "MCP" - }, - { - "path": "content.mcp.heading.main", - "label": "MCP: основная строка", - "kind": "text", - "renderAs": "text", - "group": "MCP" - }, - { - "path": "content.mcp.introHtml", - "label": "MCP: вводный текст", - "kind": "html", - "renderAs": "html", - "group": "MCP" - }, - { - "path": "content.mcp.items.0.eyebrow", - "label": "MCP: пункт 1, подпись", - "kind": "text", - "renderAs": "text", - "group": "MCP" - }, - { - "path": "content.mcp.items.0.bodyHtml", - "label": "MCP: пункт 1, текст", - "kind": "html", - "renderAs": "html", - "group": "MCP" + "path": "content.sliderItems", + "label": "Слайды", + "kind": "collection", + "renderAs": "collection", + "group": "Слайдер", + "itemTemplate": { + "enabled": true, + "videoSrc": "./assets/media/publish-flow.mp4", + "title": "Новый слайд" + }, + "itemFields": [ + { + "path": "videoSrc", + "label": "Видео / картинка", + "kind": "media", + "renderAs": "attr" + }, + { + "path": "title", + "label": "Подпись", + "kind": "text", + "renderAs": "text" + } + ] } ], "enabled": true }, { - "region": "cViz", - "id": "ai-modes", - "type": "aiModes", - "template": "ai-modes.html", - "adminLabel": "Гибкие режимы ИИ", - "anchor": null, + "region": "beforeCViz", + "id": "feature-video-copy", + "type": "featureVideo", + "template": "project-video-first.html", + "adminLabel": "ENGINE - PROJECT VIDEO - FIRST", + "anchor": "features", "content": { + "windowTitle": "ENGINE_HUB.mp4", + "videoSrc": "./assets/uploads/media/feature-video-copy/ndc_engine_base-mqv82irv.mp4", "heading": { - "muted": "Гибкие", - "main": "режимы ИИ." + "muted": "ENGINE", + "main": "Слой разработки" }, - "introHtml": "Разные режимы и модели для разных задач.", - "sliderItems": [ + "introHtml": "Engine используется для сборки workflow, прикладной логики, интеграций и интерфейсов внутри NODE.DC.", + "features": [ { - "videoSrc": "./assets/media/various-modes.mp4", - "title": "Переключение режимов ИИ" + "eyebrow": "Нодовый редактор", + "bodyHtml": "Процессы собираются из узлов, связей и готовых компонентов." }, { - "videoSrc": "./assets/media/project-flow.mp4", - "title": "Создание нового проекта" + "eyebrow": "Агентная сборка", + "bodyHtml": "Агент может сформировать workflow по текстовому заданию." }, { - "videoSrc": "./assets/media/ai-dock-mode.mp4", - "title": "ИИ-док" + "eyebrow": "Библиотека узлов", + "bodyHtml": "500+ компонентов для данных, логики, API, визуализации и внешних систем." }, { - "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": "Публикация" + "eyebrow": "Запуск в контур", + "bodyHtml": "Результат подключается к интерфейсу и передается в Ops." } ], - "scroll": { - "speed": 0.55 - } + "videoHeightPx": 820 }, "editableFields": [ { - "path": "content.scroll.speed", - "label": "Скорость скролла", - "kind": "range", + "path": "content.windowTitle", + "label": "Окно: название файла", + "kind": "text", + "renderAs": "text", + "group": "Видео-окно" + }, + { + "path": "content.videoSrc", + "label": "Окно: видео", + "kind": "media", + "renderAs": "attr", + "group": "Видео-окно" + }, + { + "path": "content.videoHeightPx", + "label": "Высота видео", + "kind": "number", "renderAs": "number", - "group": "Скролл", - "min": 0.2, - "max": 2, - "step": 0.05, - "suffix": "x" + "group": "Видео-окно", + "min": 240, + "max": 900, + "step": 1, + "suffix": "px", + "defaultValue": 620 }, { "path": "content.heading.muted", @@ -1190,116 +1058,592 @@ "group": "Заголовок" }, { - "path": "content.sliderItems.0.videoSrc", - "label": "Слайд 1: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.0.title", - "label": "Слайд 1: подпись", + "path": "content.features.0.eyebrow", + "label": "Пункт 1: подпись", "kind": "text", "renderAs": "text", - "group": "Слайдер" + "group": "Пункты" }, { - "path": "content.sliderItems.1.videoSrc", - "label": "Слайд 2: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" + "path": "content.features.0.bodyHtml", + "label": "Пункт 1: текст", + "kind": "html", + "renderAs": "html", + "group": "Пункты" }, { - "path": "content.sliderItems.1.title", - "label": "Слайд 2: подпись", + "path": "content.features.1.eyebrow", + "label": "Пункт 2: подпись", "kind": "text", "renderAs": "text", - "group": "Слайдер" + "group": "Пункты" }, { - "path": "content.sliderItems.2.videoSrc", - "label": "Слайд 3: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" + "path": "content.features.1.bodyHtml", + "label": "Пункт 2: текст", + "kind": "html", + "renderAs": "html", + "group": "Пункты" }, { - "path": "content.sliderItems.2.title", - "label": "Слайд 3: подпись", + "path": "content.features.2.eyebrow", + "label": "Пункт 3: подпись", "kind": "text", "renderAs": "text", - "group": "Слайдер" + "group": "Пункты" }, { - "path": "content.sliderItems.3.videoSrc", - "label": "Слайд 4: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" + "path": "content.features.2.bodyHtml", + "label": "Пункт 3: текст", + "kind": "html", + "renderAs": "html", + "group": "Пункты" }, { - "path": "content.sliderItems.3.title", - "label": "Слайд 4: подпись", + "path": "content.features.3.eyebrow", + "label": "Пункт 4: подпись", "kind": "text", "renderAs": "text", - "group": "Слайдер" + "group": "Пункты" }, { - "path": "content.sliderItems.4.videoSrc", - "label": "Слайд 5: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" + "path": "content.features.3.bodyHtml", + "label": "Пункт 4: текст", + "kind": "html", + "renderAs": "html", + "group": "Пункты" + } + ], + "enabled": true, + "scopeIds": true + }, + { + "region": "cViz", + "id": "component-library-copy", + "type": "componentLibrary", + "template": "project-video-detail.html", + "adminLabel": "ENGINE - PROJECT VIDEO - DETAIL", + "anchor": null, + "content": { + "heading": { + "muted": "Engine. ", + "middle": "Workflow ", + "main": "и агенты." + }, + "introHtml": "Workflow можно собрать вручную или через ассистента: от текстового задания до готовой нодовой схемы и подключенного интерфейса.", + "sliderItems": [ + { + "videoSrc": "./assets/uploads/media/component-library-copy/engine_coding_workflow-mqv96gya.mp4", + "title": "Сборка workflow", + "enabled": true + }, + { + "videoSrc": "./assets/uploads/media/component-library-copy/engine_coding_agent-mqv96lq3.mp4", + "title": "Режимы агентов", + "enabled": true + }, + { + "videoSrc": "./assets/uploads/media/component-library-copy/engine_monitor-mqv96tdn.mp4", + "title": "Создание рабочего модуля", + "enabled": true + } + ], + "scroll": { + "speed": 0.2 + } + }, + "editableFields": [ + { + "path": "content.scroll.speed", + "label": "Скорость скролла", + "kind": "range", + "renderAs": "number", + "group": "Скролл", + "min": 0.2, + "max": 2, + "step": 0.05, + "suffix": "x" }, { - "path": "content.sliderItems.4.title", - "label": "Слайд 5: подпись", + "path": "content.heading.muted", + "label": "Заголовок: приглушённая строка", "kind": "text", "renderAs": "text", - "group": "Слайдер" + "group": "Заголовок" }, { - "path": "content.sliderItems.5.videoSrc", - "label": "Слайд 6: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.5.title", - "label": "Слайд 6: подпись", + "path": "content.heading.middle", + "label": "Заголовок: средняя строка", "kind": "text", "renderAs": "text", - "group": "Слайдер" + "group": "Заголовок" }, { - "path": "content.sliderItems.6.videoSrc", - "label": "Слайд 7: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" - }, - { - "path": "content.sliderItems.6.title", - "label": "Слайд 7: подпись", + "path": "content.heading.main", + "label": "Заголовок: основная строка", "kind": "text", "renderAs": "text", - "group": "Слайдер" + "group": "Заголовок" }, { - "path": "content.sliderItems.7.videoSrc", - "label": "Слайд 8: видео", - "kind": "media", - "renderAs": "attr", - "group": "Слайдер" + "path": "content.introHtml", + "label": "Вводный текст", + "kind": "html", + "renderAs": "html", + "group": "Заголовок" }, { - "path": "content.sliderItems.7.title", - "label": "Слайд 8: подпись", + "path": "content.sliderItems", + "label": "Слайды", + "kind": "collection", + "renderAs": "collection", + "group": "Слайдер", + "itemTemplate": { + "enabled": true, + "videoSrc": "./assets/media/publish-flow.mp4", + "title": "Новый слайд" + }, + "itemFields": [ + { + "path": "videoSrc", + "label": "Видео / картинка", + "kind": "media", + "renderAs": "attr" + }, + { + "path": "title", + "label": "Подпись", + "kind": "text", + "renderAs": "text" + } + ] + } + ], + "enabled": true, + "scopeIds": true + }, + { + "region": "beforeCViz", + "id": "feature-video-copy-copy", + "type": "featureVideo", + "template": "project-video-first.html", + "adminLabel": "OPS - PROJECT VIDEO - FIRST", + "anchor": "features", + "content": { + "windowTitle": "OPS_HUB.mp4", + "videoSrc": "./assets/uploads/media/feature-video-copy-copy/ndc_ops_blur-mqv99k3r.mp4", + "heading": { + "muted": "OPS", + "main": "Операционный слой" + }, + "introHtml": "Ops собирает задачи, статусы, отчеты, согласования, рабочие материалы и результаты агентов в едином операционном контуре.", + "features": [ + { + "eyebrow": "Проектный контур", + "bodyHtml": "Задачи, карточки, этапы, статусы и контроль исполнения." + }, + { + "eyebrow": "Рабочие представления", + "bodyHtml": "Канбан, таблицы, календарь, графики, диаграммы и дашборды." + }, + { + "eyebrow": "Сквозной ассистент", + "bodyHtml": "Контекст по задачам, коду, моделям, данным и связанным сервисам." + }, + { + "eyebrow": "Результаты модулей", + "bodyHtml": "События, отчеты и действия из Engine и прикладных модулей попадают в Ops." + } + ], + "videoHeightPx": 820 + }, + "editableFields": [ + { + "path": "content.windowTitle", + "label": "Окно: название файла", "kind": "text", "renderAs": "text", - "group": "Слайдер" + "group": "Видео-окно" + }, + { + "path": "content.videoSrc", + "label": "Окно: видео", + "kind": "media", + "renderAs": "attr", + "group": "Видео-окно" + }, + { + "path": "content.videoHeightPx", + "label": "Высота видео", + "kind": "number", + "renderAs": "number", + "group": "Видео-окно", + "min": 240, + "max": 900, + "step": 1, + "suffix": "px", + "defaultValue": 620 + }, + { + "path": "content.heading.muted", + "label": "Заголовок: приглушённая строка", + "kind": "text", + "renderAs": "text", + "group": "Заголовок" + }, + { + "path": "content.heading.main", + "label": "Заголовок: основная строка", + "kind": "text", + "renderAs": "text", + "group": "Заголовок" + }, + { + "path": "content.introHtml", + "label": "Вводный текст", + "kind": "html", + "renderAs": "html", + "group": "Заголовок" + }, + { + "path": "content.features.0.eyebrow", + "label": "Пункт 1: подпись", + "kind": "text", + "renderAs": "text", + "group": "Пункты" + }, + { + "path": "content.features.0.bodyHtml", + "label": "Пункт 1: текст", + "kind": "html", + "renderAs": "html", + "group": "Пункты" + }, + { + "path": "content.features.1.eyebrow", + "label": "Пункт 2: подпись", + "kind": "text", + "renderAs": "text", + "group": "Пункты" + }, + { + "path": "content.features.1.bodyHtml", + "label": "Пункт 2: текст", + "kind": "html", + "renderAs": "html", + "group": "Пункты" + }, + { + "path": "content.features.2.eyebrow", + "label": "Пункт 3: подпись", + "kind": "text", + "renderAs": "text", + "group": "Пункты" + }, + { + "path": "content.features.2.bodyHtml", + "label": "Пункт 3: текст", + "kind": "html", + "renderAs": "html", + "group": "Пункты" + }, + { + "path": "content.features.3.eyebrow", + "label": "Пункт 4: подпись", + "kind": "text", + "renderAs": "text", + "group": "Пункты" + }, + { + "path": "content.features.3.bodyHtml", + "label": "Пункт 4: текст", + "kind": "html", + "renderAs": "html", + "group": "Пункты" + } + ], + "enabled": true, + "scopeIds": true + }, + { + "region": "cViz", + "id": "component-library-copy-copy", + "type": "componentLibrary", + "template": "project-video-detail.html", + "adminLabel": "OPS- PROJECT VIDEO - DETAIL", + "anchor": null, + "content": { + "heading": { + "muted": "OPS", + "middle": "Контроль ", + "main": "процессов." + }, + "introHtml": "Операционная среда для работы людей, агентов и прикладных контуров в рамках проектов, подразделений и процессов.", + "sliderItems": [ + { + "videoSrc": "./assets/uploads/media/component-library-copy-copy/cast_ops_dashboard-mqv9gga5.mp4", + "title": "Проектный дашборд", + "enabled": true + }, + { + "videoSrc": "./assets/uploads/media/component-library-copy-copy/cast_ops_cards-mqv9go9s.mp4", + "title": "Карточки задач и агентных отчетов", + "enabled": true + }, + { + "videoSrc": "./assets/uploads/media/component-library-copy-copy/cast_ops_bim-mqv9gwoe.mp4", + "title": "Инженерные данные", + "enabled": true + } + ], + "scroll": { + "speed": 0.2 + } + }, + "editableFields": [ + { + "path": "content.scroll.speed", + "label": "Скорость скролла", + "kind": "range", + "renderAs": "number", + "group": "Скролл", + "min": 0.2, + "max": 2, + "step": 0.05, + "suffix": "x" + }, + { + "path": "content.heading.muted", + "label": "Заголовок: приглушённая строка", + "kind": "text", + "renderAs": "text", + "group": "Заголовок" + }, + { + "path": "content.heading.middle", + "label": "Заголовок: средняя строка", + "kind": "text", + "renderAs": "text", + "group": "Заголовок" + }, + { + "path": "content.heading.main", + "label": "Заголовок: основная строка", + "kind": "text", + "renderAs": "text", + "group": "Заголовок" + }, + { + "path": "content.introHtml", + "label": "Вводный текст", + "kind": "html", + "renderAs": "html", + "group": "Заголовок" + }, + { + "path": "content.sliderItems", + "label": "Слайды", + "kind": "collection", + "renderAs": "collection", + "group": "Слайдер", + "itemTemplate": { + "enabled": true, + "videoSrc": "./assets/media/publish-flow.mp4", + "title": "Новый слайд" + }, + "itemFields": [ + { + "path": "videoSrc", + "label": "Видео / картинка", + "kind": "media", + "renderAs": "attr" + }, + { + "path": "title", + "label": "Подпись", + "kind": "text", + "renderAs": "text" + } + ] + } + ], + "enabled": true, + "scopeIds": true + }, + { + "region": "cViz", + "id": "component-library-copy-copy-copy", + "type": "componentLibrary", + "template": "project-video-detail.html", + "adminLabel": "МОДУЛИ- PROJECT VIDEO - DETAIL", + "anchor": null, + "content": { + "heading": { + "muted": "Модули. ", + "middle": "Прикладной ", + "main": "контур." + }, + "introHtml": "Прикладные модули закрывают функции подразделений: 1C, закупки, тендеры, юридический контур, цифровые двойники, инженерные модели и интеграции.", + "sliderItems": [ + { + "videoSrc": "./assets/uploads/media/component-library-copy-copy-copy/ndc_1c-mqvamdlq.jpg", + "title": "1С Ассистент", + "enabled": true + }, + { + "videoSrc": "./assets/uploads/media/component-library-copy-copy-copy/ndc_tender_p-mqvamj2p.jpeg", + "title": "Тендерный агент", + "enabled": true + }, + { + "videoSrc": "./assets/uploads/media/component-library-copy-copy-copy/ndc_dt_mmap-mqvamokr.jpg", + "title": "Цифровые двойники городов", + "enabled": true + } + ], + "scroll": { + "speed": 0.2 + } + }, + "editableFields": [ + { + "path": "content.scroll.speed", + "label": "Скорость скролла", + "kind": "range", + "renderAs": "number", + "group": "Скролл", + "min": 0.2, + "max": 2, + "step": 0.05, + "suffix": "x" + }, + { + "path": "content.heading.muted", + "label": "Заголовок: приглушённая строка", + "kind": "text", + "renderAs": "text", + "group": "Заголовок" + }, + { + "path": "content.heading.middle", + "label": "Заголовок: средняя строка", + "kind": "text", + "renderAs": "text", + "group": "Заголовок" + }, + { + "path": "content.heading.main", + "label": "Заголовок: основная строка", + "kind": "text", + "renderAs": "text", + "group": "Заголовок" + }, + { + "path": "content.introHtml", + "label": "Вводный текст", + "kind": "html", + "renderAs": "html", + "group": "Заголовок" + }, + { + "path": "content.sliderItems", + "label": "Слайды", + "kind": "collection", + "renderAs": "collection", + "group": "Слайдер", + "itemTemplate": { + "enabled": true, + "videoSrc": "./assets/media/publish-flow.mp4", + "title": "Новый слайд" + }, + "itemFields": [ + { + "path": "videoSrc", + "label": "Видео / картинка", + "kind": "media", + "renderAs": "attr" + }, + { + "path": "title", + "label": "Подпись", + "kind": "text", + "renderAs": "text" + } + ] + } + ], + "enabled": true, + "scopeIds": true + }, + { + "region": "cViz", + "id": "publish-cta", + "type": "publishCta", + "template": "publish-cta.html", + "adminLabel": "Финальный CTA перед тарифами", + "anchor": "hero", + "content": { + "cta": { + "heading": { + "muted": "Подключить.", + "middle": "Поставить задачу.", + "main": "Опубликовать." + }, + "intro": "Единый путь от разработки до продакшена.", + "detailHtml": "Настройка примерно в 12 кликов: от скачивания приложения до запуска в проекте.", + "href": "index.html#hero", + "buttonLabel": "Встать в лист ожидания" + } + }, + "editableFields": [ + { + "path": "content.cta.heading.muted", + "label": "CTA: приглушённая строка", + "kind": "text", + "renderAs": "text", + "group": "CTA" + }, + { + "path": "content.cta.heading.middle", + "label": "CTA: средняя строка", + "kind": "text", + "renderAs": "text", + "group": "CTA" + }, + { + "path": "content.cta.heading.main", + "label": "CTA: основная строка", + "kind": "text", + "renderAs": "text", + "group": "CTA" + }, + { + "path": "content.cta.intro", + "label": "CTA: вводный текст", + "kind": "text", + "renderAs": "text", + "group": "CTA" + }, + { + "path": "content.cta.detailHtml", + "label": "CTA: пояснение", + "kind": "html", + "renderAs": "html", + "group": "CTA" + }, + { + "path": "content.cta.href", + "label": "CTA: ссылка", + "kind": "url", + "renderAs": "attr", + "group": "CTA" + }, + { + "path": "content.cta.buttonLabel", + "label": "CTA: кнопка", + "kind": "text", + "renderAs": "text", + "group": "CTA" } ], "enabled": true @@ -1309,7 +1653,7 @@ "id": "faq", "type": "faqSection", "template": "faq.html", - "adminLabel": "Частые вопросы и ответы", + "adminLabel": "FAQ", "anchor": null, "content": { "heading": { @@ -1371,7 +1715,7 @@ "sentLabel": "Отправлено с iPhone" }, "scroll": { - "speed": 0.55 + "speed": 0.7 } }, "editableFields": [ @@ -1500,79 +1844,6 @@ ], "enabled": true }, - { - "region": "cViz", - "id": "publish-cta", - "type": "publishCta", - "template": "publish-cta.html", - "adminLabel": "Финальный CTA перед тарифами", - "anchor": "hero", - "content": { - "cta": { - "heading": { - "muted": "Подключить.", - "middle": "Поставить задачу.", - "main": "Опубликовать." - }, - "intro": "Единый путь от разработки до продакшена.", - "detailHtml": "Настройка примерно в 12 кликов: от скачивания приложения до запуска в проекте.", - "href": "index.html#hero", - "buttonLabel": "Встать в лист ожидания" - } - }, - "editableFields": [ - { - "path": "content.cta.heading.muted", - "label": "CTA: приглушённая строка", - "kind": "text", - "renderAs": "text", - "group": "CTA" - }, - { - "path": "content.cta.heading.middle", - "label": "CTA: средняя строка", - "kind": "text", - "renderAs": "text", - "group": "CTA" - }, - { - "path": "content.cta.heading.main", - "label": "CTA: основная строка", - "kind": "text", - "renderAs": "text", - "group": "CTA" - }, - { - "path": "content.cta.intro", - "label": "CTA: вводный текст", - "kind": "text", - "renderAs": "text", - "group": "CTA" - }, - { - "path": "content.cta.detailHtml", - "label": "CTA: пояснение", - "kind": "html", - "renderAs": "html", - "group": "CTA" - }, - { - "path": "content.cta.href", - "label": "CTA: ссылка", - "kind": "url", - "renderAs": "attr", - "group": "CTA" - }, - { - "path": "content.cta.buttonLabel", - "label": "CTA: кнопка", - "kind": "text", - "renderAs": "text", - "group": "CTA" - } - ], - "enabled": true - }, { "region": "cViz", "id": "pricing-table", @@ -2234,77 +2505,6 @@ ], "enabled": true }, - { - "region": "cViz", - "id": "pricing-intro", - "type": "pricingIntro", - "template": "pricing-intro.html", - "adminLabel": "Интро тарифов (устаревший дубль)", - "anchor": "waitlist", - "enabled": false, - "content": { - "eyebrow": "Тарифы", - "heading": { - "muted": "Код", - "middle": "для No-Code,", - "main": "усиленный ИИ." - }, - "introHtml": "Подписки на приложение с
включённым объёмом в каждом тарифе.
", - "tokenBillingHtml": "Токенная оплата сверх лимитов.", - "founderLimitHtml": "200 Founder мест с фиксированной ценой на первый год." - }, - "editableFields": [ - { - "path": "content.eyebrow", - "label": "Надзаголовок", - "kind": "text", - "renderAs": "text", - "group": "Тарифы" - }, - { - "path": "content.heading.muted", - "label": "Заголовок: приглушённая строка", - "kind": "text", - "renderAs": "text", - "group": "Тарифы" - }, - { - "path": "content.heading.middle", - "label": "Заголовок: средняя строка", - "kind": "text", - "renderAs": "text", - "group": "Тарифы" - }, - { - "path": "content.heading.main", - "label": "Заголовок: основная строка", - "kind": "text", - "renderAs": "text", - "group": "Тарифы" - }, - { - "path": "content.introHtml", - "label": "Вводный текст", - "kind": "html", - "renderAs": "html", - "group": "Тарифы" - }, - { - "path": "content.tokenBillingHtml", - "label": "Оплата сверх лимитов", - "kind": "html", - "renderAs": "html", - "group": "Тарифы" - }, - { - "path": "content.founderLimitHtml", - "label": "Founder-лимит", - "kind": "html", - "renderAs": "html", - "group": "Тарифы" - } - ] - }, { "region": "cViz", "id": "waitlist-files", diff --git a/index.html b/index.html index 905d7f7..71575b1 100644 --- a/index.html +++ b/index.html @@ -123,10 +123,304 @@ window.__API_ORIGIN__="https://ssscript-website.vercel.app"; visibility: hidden; }

Платформа для цифровых контуров предприятия

Hub, Engine, Ops и прикладные модули в единой экосистеме управления, разработки и -исполнения.

Полныйагентный контроль

Единый агентный слой с доступом к задачам, кодовой базе, данным, моделям и связанным сервисам.

Модульная архитектура

Подключаемые приложения, роли, контуры доступа, рабочие пространства и специализированные инструменты.

Разработка и запуск

Сборка workflow, интерфейсов и рабочих контуров с передачей результатов в операционный слой.
Логотип NODE.DC
Selfhost платформа

Запросить DEMO

NODE.DC - платформа для задач, процессов, данных, агентов и инженерных модулей.

Заявка принята. Спасибо.

Мы скоро свяжемся.

Подать заявку?

Или оформить ранний Founder-доступ

Оформить Founder-доступ 350€
До 200 мест

Не получилось

Что-то пошло не так.
Попробуйте ещё раз чуть позже.

демо-видео.mp4

Всё в одномприложение.

Универсальный инструмент для ИИ-агентов в вашем Webflow-проекте.
Контекст проекта
Подключитесь к MCP чтобы агенты получили полный контекст вашей сборки.
Группы агентов
Используйте несколько агентов параллельно чтобы добавлять кастомный код, анимации и функциональность.
Среда разработки
Используйте быстрые Bun сборки и автообновление для вашего кастомного кода.
Деплой в один клик
Нажмите и публикуйте кастомный код, написанный ИИ, чтобы он стал доступен всем посетителям сайта.

АгентнаяКомпонентнаябиблиотека.

Готовая к работе с агентами компонентная библиотека: добавление в один клик через приложение NODE.DC для Webflow и дальнейшая настройка под проект.

Переключение режимов ИИ

Создание нового проекта

ИИ-док

Аккаунт

Редактирование кода с ИИ

Dev-сервер с ИИ

Webflow MCP

Публикация

Одинкликдобавления.

С помощью расширения NODE.DC для Webflow.
Понимание компонентов
Используйте несколько агентов параллельно чтобы добавлять кастомный код, анимации и функциональность.
Быстрый старт
Используйте быстрые Bun сборки и автообновление для вашего кастомного кода.

Получитеболее сильныйMCP.

Полностью переработанный и более быстрый MCP-сервер.
Понимание Webflow
Используйте несколько агентов параллельно чтобы добавлять кастомный код, анимации и функциональность.
-

Гибкиережимы ИИ.

Разные режимы и модели для разных задач.

Переключение режимов ИИ

Создание нового проекта

ИИ-док

Аккаунт

Редактирование кода с ИИ

Dev-сервер с ИИ

Webflow MCP

Публикация

-

Частыевопросыи ответы

Подробный обзор возможностей, логики работы и внутреннего устройства.
FAQ
NODE.DC
Это влияет на производительность?
NODE.DC
Где размещаются мои файлы?
NODE.DC
Что делать, если я опубликовал не те файлы?
NODE.DC
Что такое ИИ-агент Webflow?
NODE.DC
Что такое собственная база знаний?
NODE.DC
Как использовать это с Webflow?
NODE.DC
Какие ИИ-модели доступны?
NODE.DC
Как это работает?
NODE.DC
Как работает публикация?
NODE.DC
Это работает с MCP?
NODE.DC
Ответить
Это влияет на производительность?

Как и любой кастомный код, это добавляет только ту нагрузку, которую несёт сам код.

Все файлы собираются, минифицируются и сжимаются, чтобы минимально влиять на проект и не плодить внешние библиотеки и CDN-скрипты.

Отправлено с iPhone
NODE.DC
Ответить
Где размещаются мои файлы?

После сборки файлы загружаются в Cloudflare R2 и отдаются через CDN-ссылку на выделенном домене проекта, чтобы получить быстрый и совместимый с Webflow результат.

Отправлено с iPhone
NODE.DC
Ответить
Что делать, если я опубликовал не те файлы?

В панели деплоя есть мгновенный откат.

Нажмите откат, верните предыдущую рабочую версию, исправьте локально и опубликуйте заново.

Отправлено с iPhone
NODE.DC
Ответить
Что такое ИИ-агент Webflow?

Помимо @plan, @build и @explore стандартных режимов агентов мы готовим @webflow режим, специально заточенный под Webflow: структуру проекта, взаимодействие с ним и эффективную разработку.

Отправлено с iPhone
NODE.DC
Ответить
Что такое собственная база знаний?

Основано на многолетнем опыте написания кастомного кода для Webflow-анимаций.

WebGL, Three.js, GSAP и кастомная функциональность собраны в набор практик, пайплайнов и модульную систему, чтобы всё работало согласованно.

Также доступна библиотека компонентов: выберите компонент, добавьте в проект и попросите ИИ адаптировать его под задачу.

Отправлено с iPhone
NODE.DC
Ответить
Как использовать это с Webflow?

При запуске NODE.DC доступна отдельная вкладка для встраивания.

В зависимости от задачи можно получить скрипты для конкретной страницы или единый app.js для всего проекта.

Сгенерированная ссылка содержит и продакшен-версию, следующую за URL деплоя, и локальную версию для предпросмотра без влияния на посетителей сайта.

Отправлено с iPhone
NODE.DC
Ответить
Какие ИИ-модели доступны?

Мы используем основные доступные модели: OpenAI до Claude до Gemini.

Оркестратор выбирает модель под задачу автоматически, но вы можете вручную переключиться на нужную модель.

Отправлено с iPhone
NODE.DC
Ответить
Как это работает?

Во время работы NODE.DC выполняет две задачи параллельно.

Поднимает локальную среду разработки на Bun, синхронизирует JavaScript и TypeScript, включает HMR и держит webhook для автоматического обновления сайта при изменениях.

Одновременно работает оркестратор ИИ-агентов: обрабатывает запросы, меняет код, проверяет состояние сборки и при необходимости обновляет проект Webflow.

Отправлено с iPhone
NODE.DC
Ответить
Как работает публикация?

Процесс многоэтапный: сначала Bun собирает, сжимает, минифицирует и подготавливает проект вместе с библиотеками.

Затем файлы отправляются в CDN: cdn.node.dc/{USERNAME}/{PROJECT-NAME}. Ссылка уже содержит нужную структуру: вы видите локальную версию, а пользователи получают опубликованный скрипт.

Отправлено с iPhone
NODE.DC
Ответить
Это работает с MCP?

Система изначально рассчитана на работу с Webflow MCP.

Подключите проект и авторизацию, чтобы ИИ-агенты получили доступ к Webflow-сборке. Подключение приложения в Webflow Designer даёт агентам доступ к контенту, элементам, стилям и CSS, а кастомный код получает полный контекст проекта.

Отправлено с iPhone
-

Подключить.Поставить задачу.Опубликовать.

Единый путь от разработки до продакшена.
Настройка примерно в 12 кликов: от скачивания приложения до запуска в проекте.
Встать в лист ожидания
Тарифы

Коддля No-Code,усиленный ИИ.

Подписки на приложение с
включённым объёмом в каждом тарифе.
Токенная оплата сверх лимитов.
200 Founder мест с фиксированной ценой на первый год.
Тарифы.app

Бета

Бесплатно
1 месяц

Founder

310
/ год, навсегда
Скидка 50%

Basic

20
/ месяц

Advanced

50
/ месяц
Старт
Старт
Старт
Всего доступно
Расширение (20M)
Расширение (10M)
Глубина рассуждения
Базовый интеллект
Рабочее пространство
Объём токенов
Старт
Объём токенов
Объём токенов
Рабочее пространство
Всего доступно
Расширение (20M)
Расширение (10M)
Глубина рассуждения
Базовый интеллект
Рабочее пространство
Базовый интеллект
Глубина рассуждения
Расширение (10M)
Расширение (20M)
Всего доступно
Июнь 2026
Июнь 2026
Июнь 2026
Июнь 2026
Кредиты тарифа
Без ограничений
40 000 включено
12 000 включено
40 000 включено
Рабочее пространство
Без ограничений
Без ограничений
10 проектов
Без ограничений
Базовый интеллект
Все модели
Все быстрые
Все быстрые
Все быстрые
Глубина рассуждения
Полный
Полный
Ограниченный
Полный
Расширение SM 10€
Обновления без лимита
+ 10 000 кредитов
+ 8 000 кредитов
+ 10 000 кредитов
Расширение MD 20€
Обновления без лимита
+ 26 000 кредитов
+ 20 000 кредитов
+ 26 000 кредитов
Всего доступно
40 тестировщиков
200 мест
--
--
Подать заявку в бету
Оформить Founder-доступ
(*) Условия могут меняться по ходу тестирования, но мы постараемся сохранить заявленные обещания.
Enterprise BYOK-тарифы будут доступны с сентября 2026.
публикация-в-один-клик.png
Иконка MOV-файла.
публикация-в-один-клик.png
полноценный-редактор-кода.webp
Иконка MOV-файла.
полноценный-редактор-кода.webp
ИИ-док-режим.jpg
Иконка MOV-файла.
ИИ-док-режим.jpg
глубокая-интеграция-webflow.webp
Иконка MOV-файла.
глубокая-интеграция-webflow.webp
установка-настройка-запуск.jpg
Иконка MOV-файла.
установка-настройка-запуск.jpg
Логотип NODE.DC
Бета-релиз: июль 2026

Ранний доступ

Кастомный код, MCP и ИИ-агенты для Webflow в одном рабочем контуре.

Заявка принята. Спасибо.

Мы скоро свяжемся.

Подать заявку в бету?
Иконка TXT-файла
fff
fff
Иконка TXT-файла
Ergtrr
Ggg
Иконка TXT-файла
Paul
только что нашёл ваш сайт — выглядит отлично! +исполнения.

Полныйагентный контроль

Единый агентный слой с доступом к задачам, кодовой базе, данным, моделям и связанным сервисам.

Модульная архитектура

Подключаемые приложения, роли, контуры доступа, рабочие пространства и специализированные инструменты.

Разработка и запуск

Сборка workflow, интерфейсов и рабочих контуров с передачей результатов в операционный слой.
Логотип NODE.DC
Selfhost платформа

Запросить DEMO

NODE.DC - платформа для задач, процессов, данных, агентов и инженерных модулей.

Заявка принята. Спасибо.

Мы скоро свяжемся.

Подать заявку?

Или оформить ранний Founder-доступ

Оформить Founder-доступ 350€
До 200 мест

Не получилось

Что-то пошло не так.
Попробуйте ещё раз чуть позже.

+
+
+

+ NODE.DC + режимы / слои +

+
Строится из базовых слоев и прикладных модулей: вход, разработка, операционная +работа, агенты, интеграции и инженерные данные.
+
+
+
+
+
+ + +
+
+
+ +
+
+
+

HUB - слой витрин и доступов

+
+
+ + + +
+
+
+ +
+
+
+

ENGINE - среда разбаботки сервисов и автоматизаций

+
+
+ + + +
+
+
+ +
+
+
+

OPS - операционный / административный слой

+
+
+ + +
+
+
+
+
+
+
+
NDC_HUB.mp4

HUBточка входа в платформу

Универсальный инструмент для ИИ-агентов в вашем Webflow-проекте.
Каталог приложений
Доступные модули отображаются по роли, группе и контуру предприятия.
Рабочие пространства
Проекты, отделы и enterprise-контуры разделяются по правилам доступа.
Администрирование
Пользователи, группы, роли и права управляются из единой административной зоны.
Сквозной доступ
Переход к Engine, Ops и прикладным модулям сохраняет общий контекст платформы.
+
+
+
+

+ Hub. + Контур + доступа. +

+
Централизованный запуск приложений, управление ролями, группами и доступом к каждому рабочему модулю.
+
+
+
+
+
+ + +
+
+
+
+
+
+
+

Каталог приложений

+
+
+ + + +
+
+
+
+
+
+
+

Роли и контуры

+
+
+ + + +
+
+
+
+
+
+
+

Группы и доступы

+
+
+ + +
+
+
+
+
ENGINE_HUB.mp4

ENGINEСлой разработки

Engine используется для сборки workflow, прикладной логики, интеграций и интерфейсов внутри NODE.DC.
Нодовый редактор
Процессы собираются из узлов, связей и готовых компонентов.
Агентная сборка
Агент может сформировать workflow по текстовому заданию.
Библиотека узлов
500+ компонентов для данных, логики, API, визуализации и внешних систем.
Запуск в контур
Результат подключается к интерфейсу и передается в Ops.
+
+
+
+

+ Engine. + Workflow + и агенты. +

+
Workflow можно собрать вручную или через ассистента: от текстового задания до готовой нодовой схемы и подключенного интерфейса.
+
+
+
+
+
+ + +
+
+
+
+
+
+
+

Сборка workflow

+
+
+ + + +
+
+
+
+
+
+
+

Режимы агентов

+
+
+ + + +
+
+
+
+
+
+
+

Создание рабочего модуля

+
+
+ + +
+
+
+
+
OPS_HUB.mp4

OPSОперационный слой

Ops собирает задачи, статусы, отчеты, согласования, рабочие материалы и результаты агентов в едином операционном контуре.
Проектный контур
Задачи, карточки, этапы, статусы и контроль исполнения.
Рабочие представления
Канбан, таблицы, календарь, графики, диаграммы и дашборды.
Сквозной ассистент
Контекст по задачам, коду, моделям, данным и связанным сервисам.
Результаты модулей
События, отчеты и действия из Engine и прикладных модулей попадают в Ops.
+
+
+
+

+ OPS + Контроль + процессов. +

+
Операционная среда для работы людей, агентов и прикладных контуров в рамках проектов, подразделений и процессов.
+
+
+
+
+
+ + +
+
+
+
+
+
+
+

Проектный дашборд

+
+
+ + + +
+
+
+
+
+
+
+

Карточки задач и агентных отчетов

+
+
+ + + +
+
+
+
+
+
+
+

Инженерные данные

+
+
+ + +
+
+
+
+
+
+
+

+ Модули. + Прикладной + контур. +

+
Прикладные модули закрывают функции подразделений: 1C, закупки, тендеры, юридический контур, цифровые двойники, инженерные модели и интеграции.
+
+
+
+
+
+ + +
+
+
+
+
+
+
+

1С Ассистент

+
+
+ + + +
+
+
+
+
+
+
+

Тендерный агент

+
+
+ + + +
+
+
+
+
+
+
+

Цифровые двойники городов

+
+
+ + +
+
+
+
+

Подключить.Поставить задачу.Опубликовать.

Единый путь от разработки до продакшена.
Настройка примерно в 12 кликов: от скачивания приложения до запуска в проекте.
Встать в лист ожидания

Частыевопросыи ответы

Подробный обзор возможностей, логики работы и внутреннего устройства.
FAQ
NODE.DC
Это влияет на производительность?
NODE.DC
Где размещаются мои файлы?
NODE.DC
Что делать, если я опубликовал не те файлы?
NODE.DC
Что такое ИИ-агент Webflow?
NODE.DC
Что такое собственная база знаний?
NODE.DC
Как использовать это с Webflow?
NODE.DC
Какие ИИ-модели доступны?
NODE.DC
Как это работает?
NODE.DC
Как работает публикация?
NODE.DC
Это работает с MCP?
NODE.DC
Ответить
Это влияет на производительность?

Как и любой кастомный код, это добавляет только ту нагрузку, которую несёт сам код.

Все файлы собираются, минифицируются и сжимаются, чтобы минимально влиять на проект и не плодить внешние библиотеки и CDN-скрипты.

Отправлено с iPhone
NODE.DC
Ответить
Где размещаются мои файлы?

После сборки файлы загружаются в Cloudflare R2 и отдаются через CDN-ссылку на выделенном домене проекта, чтобы получить быстрый и совместимый с Webflow результат.

Отправлено с iPhone
NODE.DC
Ответить
Что делать, если я опубликовал не те файлы?

В панели деплоя есть мгновенный откат.

Нажмите откат, верните предыдущую рабочую версию, исправьте локально и опубликуйте заново.

Отправлено с iPhone
NODE.DC
Ответить
Что такое ИИ-агент Webflow?

Помимо @plan, @build и @explore стандартных режимов агентов мы готовим @webflow режим, специально заточенный под Webflow: структуру проекта, взаимодействие с ним и эффективную разработку.

Отправлено с iPhone
NODE.DC
Ответить
Что такое собственная база знаний?

Основано на многолетнем опыте написания кастомного кода для Webflow-анимаций.

WebGL, Three.js, GSAP и кастомная функциональность собраны в набор практик, пайплайнов и модульную систему, чтобы всё работало согласованно.

Также доступна библиотека компонентов: выберите компонент, добавьте в проект и попросите ИИ адаптировать его под задачу.

Отправлено с iPhone
NODE.DC
Ответить
Как использовать это с Webflow?

При запуске NODE.DC доступна отдельная вкладка для встраивания.

В зависимости от задачи можно получить скрипты для конкретной страницы или единый app.js для всего проекта.

Сгенерированная ссылка содержит и продакшен-версию, следующую за URL деплоя, и локальную версию для предпросмотра без влияния на посетителей сайта.

Отправлено с iPhone
NODE.DC
Ответить
Какие ИИ-модели доступны?

Мы используем основные доступные модели: OpenAI до Claude до Gemini.

Оркестратор выбирает модель под задачу автоматически, но вы можете вручную переключиться на нужную модель.

Отправлено с iPhone
NODE.DC
Ответить
Как это работает?

Во время работы NODE.DC выполняет две задачи параллельно.

Поднимает локальную среду разработки на Bun, синхронизирует JavaScript и TypeScript, включает HMR и держит webhook для автоматического обновления сайта при изменениях.

Одновременно работает оркестратор ИИ-агентов: обрабатывает запросы, меняет код, проверяет состояние сборки и при необходимости обновляет проект Webflow.

Отправлено с iPhone
NODE.DC
Ответить
Как работает публикация?

Процесс многоэтапный: сначала Bun собирает, сжимает, минифицирует и подготавливает проект вместе с библиотеками.

Затем файлы отправляются в CDN: cdn.node.dc/{USERNAME}/{PROJECT-NAME}. Ссылка уже содержит нужную структуру: вы видите локальную версию, а пользователи получают опубликованный скрипт.

Отправлено с iPhone
NODE.DC
Ответить
Это работает с MCP?

Система изначально рассчитана на работу с Webflow MCP.

Подключите проект и авторизацию, чтобы ИИ-агенты получили доступ к Webflow-сборке. Подключение приложения в Webflow Designer даёт агентам доступ к контенту, элементам, стилям и CSS, а кастомный код получает полный контекст проекта.

Отправлено с iPhone
+
Тарифы

Коддля No-Code,усиленный ИИ.

Подписки на приложение с
включённым объёмом в каждом тарифе.
Токенная оплата сверх лимитов.
200 Founder мест с фиксированной ценой на первый год.
Тарифы.app

Бета

Бесплатно
1 месяц

Founder

310
/ год, навсегда
Скидка 50%

Basic

20
/ месяц

Advanced

50
/ месяц
Старт
Старт
Старт
Всего доступно
Расширение (20M)
Расширение (10M)
Глубина рассуждения
Базовый интеллект
Рабочее пространство
Объём токенов
Старт
Объём токенов
Объём токенов
Рабочее пространство
Всего доступно
Расширение (20M)
Расширение (10M)
Глубина рассуждения
Базовый интеллект
Рабочее пространство
Базовый интеллект
Глубина рассуждения
Расширение (10M)
Расширение (20M)
Всего доступно
Июнь 2026
Июнь 2026
Июнь 2026
Июнь 2026
Кредиты тарифа
Без ограничений
40 000 включено
12 000 включено
40 000 включено
Рабочее пространство
Без ограничений
Без ограничений
10 проектов
Без ограничений
Базовый интеллект
Все модели
Все быстрые
Все быстрые
Все быстрые
Глубина рассуждения
Полный
Полный
Ограниченный
Полный
Расширение SM 10€
Обновления без лимита
+ 10 000 кредитов
+ 8 000 кредитов
+ 10 000 кредитов
Расширение MD 20€
Обновления без лимита
+ 26 000 кредитов
+ 20 000 кредитов
+ 26 000 кредитов
Всего доступно
40 тестировщиков
200 мест
--
--
Подать заявку в бету
Оформить Founder-доступ
(*) Условия могут меняться по ходу тестирования, но мы постараемся сохранить заявленные обещания.
Enterprise BYOK-тарифы будут доступны с сентября 2026.
публикация-в-один-клик.png
Иконка MOV-файла.
публикация-в-один-клик.png
полноценный-редактор-кода.webp
Иконка MOV-файла.
полноценный-редактор-кода.webp
ИИ-док-режим.jpg
Иконка MOV-файла.
ИИ-док-режим.jpg
глубокая-интеграция-webflow.webp
Иконка MOV-файла.
глубокая-интеграция-webflow.webp
установка-настройка-запуск.jpg
Иконка MOV-файла.
установка-настройка-запуск.jpg
Логотип NODE.DC
Бета-релиз: июль 2026

Ранний доступ

Кастомный код, MCP и ИИ-агенты для Webflow в одном рабочем контуре.

Заявка принята. Спасибо.

Мы скоро свяжемся.

Подать заявку в бету?
Иконка TXT-файла
fff
fff
Иконка TXT-файла
Ergtrr
Ggg
Иконка TXT-файла
Paul
только что нашёл ваш сайт — выглядит отлично!
Иконка TXT-файла
s
s
Иконка TXT-файла
rkxkr
пока не уверен, но да, интересно
Иконка TXT-файла
Ali Awan
Дайте, пожалуйста, посмотреть продукт!
Иконка TXT-файла
rox del toro
ХОЧУ ДОСТУП
Иконка TXT-файла
Flavio
Привет!
Иконка TXT-файла
Daniel Clydesdale
Мы работаем с Webflow с 2016 года и используем его постоянно. Сейчас внедряем ИИ в процессы, поэтому ваш продукт нам очень интересен. Удачи с запуском!
Иконка TXT-файла
Carter Ogunsola
Для всех разработчиков: я нашёл это очень рано. Уже один из фаворитов.
Иконка TXT-файла
Marin Trajkovski
Здравствуйте, можно получить ранний доступ? Очень хочу протестировать ИИ, это сейчас действительно нужно.
Иконка TXT-файла
Arul
Интересно. Хотелось бы узнать больше.
Иконка TXT-файла
Jeremy
Вау! -Пожалуйста, пустите меня в бету!
Иконка TXT-файла
Safin
С удовольствием протестировал бы продукт. Выглядит очень интересно.
Иконка TXT-файла
Ilja van Eck
Я теперь первый Founder? Берите деньги и запускайте это.
Иконка TXT-файла
Jordan Gilroy
Я ваш главный фанат
Иконка TXT-файла
hyun
привет
Иконка TXT-файла
kris
тестирую
Иконка TXT-файла
Syed Irfan
Интересно
Иконка TXT-файла
ramyar atlasi
Apm pflege schule Düsseldorf
Иконка TXT-файла
Muhammad Ashar
vv
Иконка TXT-файла
:DD
ka tu nx
Иконка TXT-файла
test
test
Иконка TXT-файла
Filip
Я знаю Fed!
Иконка TXT-файла
Arinbjørn
Пустите меня!
Иконка TXT-файла
Janarthanan
жду
Иконка TXT-файла
Jane Smith
Очень хочу ранний доступ к продукту!
Иконка TXT-файла
Daniel Apro
Привет, сайт огонь.
Иконка TXT-файла
Allonelabs
Нам очень нравится ваш дизайн. Спасибо!
Иконка TXT-файла
nicola romei
G.O.A.T. часто используют как сокращение от Greatest of All Time — «лучший за всё время».
Иконка TXT-файла
fff
.txt
Иконка TXT-файла
Ergtrr
.txt
Иконка TXT-файла
Paul
.txt
Иконка TXT-файла
s
.txt
Иконка TXT-файла
rkxkr
.txt
Иконка TXT-файла
Ali Awan
.txt
Иконка TXT-файла
rox del toro
.txt
Иконка TXT-файла
Flavio
.txt
Иконка TXT-файла
Daniel Clydesdale
.txt
Иконка TXT-файла
Carter Ogunsola
.txt
Иконка TXT-файла
Marin Trajkovski
.txt
Иконка TXT-файла
Arul
.txt
Иконка TXT-файла
Jeremy
.txt
Иконка TXT-файла
Safin
.txt
Иконка TXT-файла
Ilja van Eck
.txt
Иконка TXT-файла
Jordan Gilroy
.txt
Иконка TXT-файла
hyun
.txt
Иконка TXT-файла
kris
.txt
Иконка TXT-файла
Syed Irfan
.txt
Иконка TXT-файла
ramyar atlasi
.txt
Иконка TXT-файла
Muhammad Ashar
.txt
Иконка TXT-файла
:DD
.txt
Иконка TXT-файла
test
.txt
Иконка TXT-файла
Filip
.txt
Иконка TXT-файла
Arinbjørn
.txt
Иконка TXT-файла
Janarthanan
.txt
Иконка TXT-файла
Jane Smith
.txt
Иконка TXT-файла
Daniel Apro
.txt
Иконка TXT-файла
Allonelabs
.txt
Иконка TXT-файла
nicola romei
.txt
Иконка MOV-файла.
демо-видео.txt
ДАЛЕЕ — ИЮЛЬ 2026
Открытая бета
Открываем доступ всем. Будем собирать и отлаживать вместе.
ДАЛЕЕ ИЮЛЬ 2026
Альфа для Founder-доступа
На второй фазе тестирования бета открыта для всех подписчиков Founder-тарифа.
СЕЙЧАС
Инвайты в закрытую бету уже отправлены.
Сейчас тестируем базовую функциональность и ИИ-интеграцию.
+Пожалуйста, пустите меня в бету!
Иконка TXT-файла
Safin
С удовольствием протестировал бы продукт. Выглядит очень интересно.
Иконка TXT-файла
Ilja van Eck
Я теперь первый Founder? Берите деньги и запускайте это.
Иконка TXT-файла
Jordan Gilroy
Я ваш главный фанат
Иконка TXT-файла
hyun
привет
Иконка TXT-файла
kris
тестирую
Иконка TXT-файла
Syed Irfan
Интересно
Иконка TXT-файла
ramyar atlasi
Apm pflege schule Düsseldorf
Иконка TXT-файла
Muhammad Ashar
vv
Иконка TXT-файла
:DD
ka tu nx
Иконка TXT-файла
test
test
Иконка TXT-файла
Filip
Я знаю Fed!
Иконка TXT-файла
Arinbjørn
Пустите меня!
Иконка TXT-файла
Janarthanan
жду
Иконка TXT-файла
Jane Smith
Очень хочу ранний доступ к продукту!
Иконка TXT-файла
Daniel Apro
Привет, сайт огонь.
Иконка TXT-файла
Allonelabs
Нам очень нравится ваш дизайн. Спасибо!
Иконка TXT-файла
nicola romei
G.O.A.T. часто используют как сокращение от Greatest of All Time — «лучший за всё время».
Иконка TXT-файла
fff
.txt
Иконка TXT-файла
Ergtrr
.txt
Иконка TXT-файла
Paul
.txt
Иконка TXT-файла
s
.txt
Иконка TXT-файла
rkxkr
.txt
Иконка TXT-файла
Ali Awan
.txt
Иконка TXT-файла
rox del toro
.txt
Иконка TXT-файла
Flavio
.txt
Иконка TXT-файла
Daniel Clydesdale
.txt
Иконка TXT-файла
Carter Ogunsola
.txt
Иконка TXT-файла
Marin Trajkovski
.txt
Иконка TXT-файла
Arul
.txt
Иконка TXT-файла
Jeremy
.txt
Иконка TXT-файла
Safin
.txt
Иконка TXT-файла
Ilja van Eck
.txt
Иконка TXT-файла
Jordan Gilroy
.txt
Иконка TXT-файла
hyun
.txt
Иконка TXT-файла
kris
.txt
Иконка TXT-файла
Syed Irfan
.txt
Иконка TXT-файла
ramyar atlasi
.txt
Иконка TXT-файла
Muhammad Ashar
.txt
Иконка TXT-файла
:DD
.txt
Иконка TXT-файла
test
.txt
Иконка TXT-файла
Filip
.txt
Иконка TXT-файла
Arinbjørn
.txt
Иконка TXT-файла
Janarthanan
.txt
Иконка TXT-файла
Jane Smith
.txt
Иконка TXT-файла
Daniel Apro
.txt
Иконка TXT-файла
Allonelabs
.txt
Иконка TXT-файла
nicola romei
.txt
Иконка MOV-файла.
демо-видео.txt
ДАЛЕЕ — ИЮЛЬ 2026
Открытая бета
Открываем доступ всем. Будем собирать и отлаживать вместе.
ДАЛЕЕ ИЮЛЬ 2026
Альфа для Founder-доступа
На второй фазе тестирования бета открыта для всех подписчиков Founder-тарифа.
СЕЙЧАС
Инвайты в закрытую бету уже отправлены.
Сейчас тестируем базовую функциональность и ИИ-интеграцию.
Белая иконка корзины.
Сб, 10 июня
09:41
diff --git a/templates/pages/home/blocks/ai-modes.html b/templates/pages/home/blocks/ai-modes.html deleted file mode 100644 index 7fc3155..0000000 --- a/templates/pages/home/blocks/ai-modes.html +++ /dev/null @@ -1 +0,0 @@ -

{{text:content.heading.muted}}{{text:content.heading.main}}

{{html:content.introHtml}}

{{text:content.sliderItems.0.title}}

{{text:content.sliderItems.1.title}}

{{text:content.sliderItems.2.title}}

{{text:content.sliderItems.3.title}}

{{text:content.sliderItems.4.title}}

{{text:content.sliderItems.5.title}}

{{text:content.sliderItems.6.title}}

{{text:content.sliderItems.7.title}}

diff --git a/templates/pages/home/blocks/component-library.html b/templates/pages/home/blocks/component-library.html deleted file mode 100644 index e54faa5..0000000 --- a/templates/pages/home/blocks/component-library.html +++ /dev/null @@ -1 +0,0 @@ -

{{text:content.heading.muted}}{{text:content.heading.middle}}{{text:content.heading.main}}

{{html:content.introHtml}}

{{text:content.sliderItems.0.title}}

{{text:content.sliderItems.1.title}}

{{text:content.sliderItems.2.title}}

{{text:content.sliderItems.3.title}}

{{text:content.sliderItems.4.title}}

{{text:content.sliderItems.5.title}}

{{text:content.sliderItems.6.title}}

{{text:content.sliderItems.7.title}}

{{text:content.oneClick.heading.muted}}{{text:content.oneClick.heading.middle}}{{text:content.oneClick.heading.main}}

{{html:content.oneClick.introHtml}}
{{text:content.oneClick.items.0.eyebrow}}
{{html:content.oneClick.items.0.bodyHtml}}
{{text:content.oneClick.items.1.eyebrow}}
{{html:content.oneClick.items.1.bodyHtml}}

{{text:content.mcp.heading.muted}}{{text:content.mcp.heading.middle}}{{text:content.mcp.heading.main}}

{{html:content.mcp.introHtml}}
{{text:content.mcp.items.0.eyebrow}}
{{html:content.mcp.items.0.bodyHtml}}
diff --git a/templates/pages/home/blocks/feature-video.html b/templates/pages/home/blocks/feature-video.html deleted file mode 100644 index 6d77af3..0000000 --- a/templates/pages/home/blocks/feature-video.html +++ /dev/null @@ -1 +0,0 @@ -
{{text:content.windowTitle}}

{{text:content.heading.muted}}{{text:content.heading.main}}

{{html:content.introHtml}}
{{text:content.features.0.eyebrow}}
{{html:content.features.0.bodyHtml}}
{{text:content.features.1.eyebrow}}
{{html:content.features.1.bodyHtml}}
{{text:content.features.2.eyebrow}}
{{html:content.features.2.bodyHtml}}
{{text:content.features.3.eyebrow}}
{{html:content.features.3.bodyHtml}}
\ No newline at end of file diff --git a/templates/pages/home/blocks/project-composition.html b/templates/pages/home/blocks/project-composition.html new file mode 100644 index 0000000..c430425 --- /dev/null +++ b/templates/pages/home/blocks/project-composition.html @@ -0,0 +1,34 @@ +
+
+
+

+ {{text:content.heading.muted}} + {{text:content.heading.main}} +

+
{{html:content.introHtml}}
+
+
+
+
+
+{{#each content.sliderItems}} +{{#if enabled}} +
+
+
+ +
+
+
+

{{text:title}}

+
+
+{{/if}} +{{/each}} +
+
+
+
+
+
+
diff --git a/templates/pages/home/blocks/project-video-detail.html b/templates/pages/home/blocks/project-video-detail.html new file mode 100644 index 0000000..701f1eb --- /dev/null +++ b/templates/pages/home/blocks/project-video-detail.html @@ -0,0 +1,32 @@ +
+
+
+

+ {{text:content.heading.muted}} + {{text:content.heading.middle}} + {{text:content.heading.main}} +

+
{{html:content.introHtml}}
+
+
+
+
+
+{{#each content.sliderItems}} +{{#if enabled}} +
+
+
+
+
+
+
+

{{text:title}}

+
+
+{{/if}} +{{/each}} +
+
+
+
diff --git a/templates/pages/home/blocks/project-video-first.html b/templates/pages/home/blocks/project-video-first.html new file mode 100644 index 0000000..41fa85f --- /dev/null +++ b/templates/pages/home/blocks/project-video-first.html @@ -0,0 +1 @@ +
{{text:content.windowTitle}}

{{text:content.heading.muted}}{{text:content.heading.main}}

{{html:content.introHtml}}
{{text:content.features.0.eyebrow}}
{{html:content.features.0.bodyHtml}}
{{text:content.features.1.eyebrow}}
{{html:content.features.1.bodyHtml}}
{{text:content.features.2.eyebrow}}
{{html:content.features.2.bodyHtml}}
{{text:content.features.3.eyebrow}}
{{html:content.features.3.bodyHtml}}
diff --git a/templates/pages/home/blocks/hero-waitlist.html b/templates/pages/home/blocks/theses-request-demo.html similarity index 100% rename from templates/pages/home/blocks/hero-waitlist.html rename to templates/pages/home/blocks/theses-request-demo.html diff --git a/templates/pages/home/shell.html b/templates/pages/home/shell.html index 6b9c15a..62e9ee4 100644 --- a/templates/pages/home/shell.html +++ b/templates/pages/home/shell.html @@ -122,4 +122,4 @@ window.__API_ORIGIN__="https://ssscript-website.vercel.app"; [debug] { visibility: hidden; } -
{{NODEDC_BLOCKS_BEFORE_CVIZ}}
{{NODEDC_BLOCKS_CVIZ}}
{{#each staticElements.content.notifications}}
{{html:eyebrowHtml}}
{{text:title}}
{{text:body}}
{{/each}}
{{#each staticElements.content.dock.items}}{{#if enabled}}{{#if dividerBefore}}
{{/if}}{{attr:iconAlt}}{{/if}}{{/each}}
{{text:staticElements.content.navigation.brandLabel}}
{{#each staticElements.content.navigation.links}}{{/each}}
Сб, 10 июня
09:41
+
{{NODEDC_BLOCKS_SECTIONS}}
{{#each staticElements.content.notifications}}
{{html:eyebrowHtml}}
{{text:title}}
{{text:body}}
{{/each}}
{{#each staticElements.content.dock.items}}{{#if enabled}}{{#if dividerBefore}}
{{/if}}{{attr:iconAlt}}{{/if}}{{/each}}
{{text:staticElements.content.navigation.brandLabel}}
{{#each staticElements.content.navigation.links}}{{/each}}
Сб, 10 июня
09:41
diff --git a/tools/admin-server.mjs b/tools/admin-server.mjs index c12c801..7189492 100644 --- a/tools/admin-server.mjs +++ b/tools/admin-server.mjs @@ -48,12 +48,16 @@ function sendJson(res, status, payload) { send(res, status, `${JSON.stringify(payload, null, 2)}\n`, "application/json; charset=utf-8"); } -async function readBody(req) { +async function readBodyBuffer(req) { const chunks = []; for await (const chunk of req) { chunks.push(chunk); } - return Buffer.concat(chunks).toString("utf8"); + return Buffer.concat(chunks); +} + +async function readBody(req) { + return (await readBodyBuffer(req)).toString("utf8"); } function runNodeScript(scriptName) { @@ -146,6 +150,29 @@ function buildStoredFileName(fileName, mimeType) { return `${safeBase}-${Date.now().toString(36)}${extension}`; } +async function saveUploadedAssetBuffer({ fileName, mimeType, bucket, fileBuffer }) { + if (typeof fileName !== "string" || !fileName || !Buffer.isBuffer(fileBuffer)) { + throw new Error("Некорректный файл загрузки"); + } + + const resolvedMimeType = mimeType || "application/octet-stream"; + const resolvedBucket = sanitizeUploadBucket(bucket || "media"); + const storedName = buildStoredFileName(fileName, resolvedMimeType); + const directory = join(uploadRoot, ...resolvedBucket.split("/")); + + await mkdir(directory, { recursive: true }); + await writeFile(join(directory, storedName), fileBuffer); + + return { + ok: true, + url: `./assets/uploads/${resolvedBucket}/${storedName}`, + fileName: storedName, + originalFileName: fileName, + mimeType: resolvedMimeType, + bucket: resolvedBucket, + }; +} + async function saveUploadedAsset(payload) { if (!isUploadPayload(payload)) { throw new Error("Некорректный payload загрузки"); @@ -158,21 +185,99 @@ async function saveUploadedAsset(payload) { } const mimeType = payload.mimeType || match[1] || "application/octet-stream"; - const bucket = sanitizeUploadBucket(payload.bucket || "media"); - const storedName = buildStoredFileName(payload.fileName, mimeType); const fileBuffer = Buffer.from(match[2], "base64"); - const directory = join(uploadRoot, ...bucket.split("/")); - await mkdir(directory, { recursive: true }); - await writeFile(join(directory, storedName), fileBuffer); + return saveUploadedAssetBuffer({ + fileName: payload.fileName, + mimeType, + bucket: payload.bucket || "media", + fileBuffer, + }); +} + +function isMultipartRequest(req) { + return String(req.headers["content-type"] || "").toLowerCase().startsWith("multipart/form-data"); +} + +function multipartBoundary(req) { + const contentType = String(req.headers["content-type"] || ""); + const match = /boundary=(?:"([^"]+)"|([^;]+))/i.exec(contentType); + return match?.[1] || match?.[2] || ""; +} + +function parseHeaderParams(value) { + const params = {}; + const parts = String(value || "").split(";"); + + for (const part of parts.slice(1)) { + const [rawKey, ...rawValueParts] = part.trim().split("="); + if (!rawKey || !rawValueParts.length) continue; + const rawValue = rawValueParts.join("=").trim(); + params[rawKey.toLowerCase()] = rawValue.replace(/^"|"$/g, ""); + } + + return params; +} + +function parseMultipartUpload(req, bodyBuffer) { + const boundary = multipartBoundary(req); + if (!boundary) { + throw new Error("Не найден multipart boundary"); + } + + const fields = {}; + let filePart = null; + const body = bodyBuffer.toString("latin1"); + const delimiter = `--${boundary}`; + + for (const rawPart of body.split(delimiter)) { + let part = rawPart; + if (!part || part === "--" || part === "--\r\n") continue; + if (part.startsWith("\r\n")) part = part.slice(2); + if (part.endsWith("--")) part = part.slice(0, -2); + if (part.endsWith("\r\n")) part = part.slice(0, -2); + + const headerEnd = part.indexOf("\r\n\r\n"); + if (headerEnd < 0) continue; + + const headerLines = part.slice(0, headerEnd).split("\r\n"); + const headers = Object.fromEntries( + headerLines + .map((line) => { + const separator = line.indexOf(":"); + if (separator < 0) return null; + return [line.slice(0, separator).trim().toLowerCase(), line.slice(separator + 1).trim()]; + }) + .filter(Boolean), + ); + const disposition = headers["content-disposition"]; + const params = parseHeaderParams(disposition); + const fieldName = params.name; + const value = part.slice(headerEnd + 4); + + if (!fieldName) continue; + + if (Object.prototype.hasOwnProperty.call(params, "filename")) { + filePart = { + fileName: params.filename || fields.fileName || "asset.bin", + mimeType: headers["content-type"] || fields.mimeType || "application/octet-stream", + fileBuffer: Buffer.from(value, "latin1"), + }; + continue; + } + + fields[fieldName] = Buffer.from(value, "latin1").toString("utf8"); + } + + if (!filePart) { + throw new Error("Файл не найден в multipart payload"); + } return { - ok: true, - url: `./assets/uploads/${bucket}/${storedName}`, - fileName: storedName, - originalFileName: payload.fileName, - mimeType, - bucket, + fileName: fields.fileName || filePart.fileName, + mimeType: fields.mimeType || filePart.mimeType, + bucket: fields.bucket || "media", + fileBuffer: filePart.fileBuffer, }; } @@ -244,8 +349,9 @@ const server = createServer(async (req, res) => { } if (req.method === "POST" && url.pathname === "/api/upload/asset") { - const body = await readBody(req); - const result = await saveUploadedAsset(JSON.parse(body)); + const result = isMultipartRequest(req) + ? await saveUploadedAssetBuffer(parseMultipartUpload(req, await readBodyBuffer(req))) + : await saveUploadedAsset(JSON.parse(await readBody(req))); sendJson(res, 200, result); return; } diff --git a/tools/extract-home-blocks.mjs b/tools/extract-home-blocks.mjs index c0234ff..4ecb951 100644 --- a/tools/extract-home-blocks.mjs +++ b/tools/extract-home-blocks.mjs @@ -63,8 +63,8 @@ const blocks = [ region: "beforeCViz", id: "hero-waitlist", type: "staticHtml", - template: "hero-waitlist.html", - adminLabel: "Первый экран и заявка", + template: "theses-request-demo.html", + adminLabel: "ТЕЗИСЫ + ЗАПРОС ДЕМО", anchor: "hero", html: html.slice(heroStart, featuresStart), }, @@ -72,8 +72,8 @@ const blocks = [ region: "beforeCViz", id: "feature-video", type: "staticHtml", - template: "feature-video.html", - adminLabel: "Демо и базовая логика", + template: "project-video-first.html", + adminLabel: "PROJECT VIDEO - FIRST", anchor: "features", html: html.slice(featuresStart, cVizStart), }, @@ -81,8 +81,8 @@ const blocks = [ region: "cViz", id: "component-library", type: "staticHtml", - template: "component-library.html", - adminLabel: "Компонентная библиотека и MCP", + template: "project-video-detail.html", + adminLabel: "PROJECT VIDEO - DETAIL", anchor: null, html: productSections[0], }, @@ -90,8 +90,8 @@ const blocks = [ region: "cViz", id: "ai-modes", type: "staticHtml", - template: "ai-modes.html", - adminLabel: "Гибкие режимы ИИ", + template: "project-composition.html", + adminLabel: "СОСТАВ ПРОЕКТА", anchor: null, html: productSections[1], }, diff --git a/tools/render-home.mjs b/tools/render-home.mjs index a6c18db..5c8e89a 100644 --- a/tools/render-home.mjs +++ b/tools/render-home.mjs @@ -11,6 +11,7 @@ const blockRoot = join(templateRoot, "blocks"); const REGION_TOKEN = { beforeCViz: "{{NODEDC_BLOCKS_BEFORE_CVIZ}}", cViz: "{{NODEDC_BLOCKS_CVIZ}}", + sections: "{{NODEDC_BLOCKS_SECTIONS}}", }; const page = JSON.parse(await readFile(pagePath, "utf8")); @@ -176,14 +177,48 @@ async function renderRegion(region) { return blocks.join(""); } -let output = renderTemplate(shell, { id: page.slug, ...page }); -for (const region of page.regions ?? []) { - const token = REGION_TOKEN[region.id]; - if (!token) { - throw new Error(`Unknown region: ${region.id}`); +async function renderSections() { + const renderCounts = new Map(); + const output = []; + let cVizRun = []; + + const flushCVizRun = () => { + if (!cVizRun.length) return; + output.push(`
${cVizRun.join("")}
`); + cVizRun = []; + }; + + for (const region of page.regions ?? []) { + for (const block of region.blocks ?? []) { + const html = await renderBlock(block, renderCounts); + if (!html) continue; + + if (block.region === "cViz") { + cVizRun.push(html); + } else { + flushCVizRun(); + output.push(html); + } + } } - output = output.replace(token, await renderRegion(region)); + flushCVizRun(); + return output.join(""); +} + +let output = renderTemplate(shell, { id: page.slug, ...page }); + +if (output.includes(REGION_TOKEN.sections)) { + output = output.replace(REGION_TOKEN.sections, await renderSections()); +} else { + for (const region of page.regions ?? []) { + const token = REGION_TOKEN[region.id]; + if (!token) { + throw new Error(`Unknown region: ${region.id}`); + } + + output = output.replace(token, await renderRegion(region)); + } } for (const token of Object.values(REGION_TOKEN)) {