From e9c0dd56c21825fc0f2d8a1af6ee06d4259e89d9 Mon Sep 17 00:00:00 2001 From: dcconstructions Date: Sat, 27 Jun 2026 17:47:46 +0300 Subject: [PATCH] Milestone admin and frontend stage --- admin/admin.js | 25 +++++++-- assets/custom/css/app.css | 1 + assets/custom/css/out.css | 1 + assets/custom/js/interaction-overrides.js | 10 +++- assets/uploads/favicon/apple-touch-icon.png | Bin 0 -> 4142 bytes assets/uploads/favicon/favicon.ico | Bin 0 -> 16958 bytes assets/uploads/favicon/icon-192.png | Bin 0 -> 4386 bytes assets/uploads/favicon/icon-512.png | Bin 0 -> 13039 bytes assets/uploads/favicon/icon-adaptive.svg | 13 +++++ .../uploads/favicon/manifest.webmanifest.json | 29 ++++++++++ content/pages/home.json | 50 +++++++++++++++++- index.html | 4 +- templates/pages/home/shell.html | 4 +- tools/admin-server.mjs | 24 +++++++-- tools/extract-home-blocks.mjs | 45 ++++++++++++++++ tools/render-home.mjs | 21 ++++++++ 16 files changed, 215 insertions(+), 12 deletions(-) create mode 100644 assets/uploads/favicon/apple-touch-icon.png create mode 100644 assets/uploads/favicon/favicon.ico create mode 100644 assets/uploads/favicon/icon-192.png create mode 100644 assets/uploads/favicon/icon-512.png create mode 100644 assets/uploads/favicon/icon-adaptive.svg create mode 100644 assets/uploads/favicon/manifest.webmanifest.json diff --git a/admin/admin.js b/admin/admin.js index 19fbd2b..8e55bbf 100644 --- a/admin/admin.js +++ b/admin/admin.js @@ -220,6 +220,12 @@ const STATIC_FALLBACK = { appIconAlt: "Иконка NODE.DC", appWordmarkSrc: "./assets/webflow/images/69ad96a28b06b7552a339137_logtype.svg?v=node-dc", appWordmarkAlt: "Логотип NODE.DC", + faviconSvg: "./assets/uploads/favicon/icon-adaptive.svg", + faviconIco: "./assets/uploads/favicon/favicon.ico", + faviconAppleTouchIcon: "./assets/uploads/favicon/apple-touch-icon.png", + faviconPng192: "./assets/uploads/favicon/icon-192.png", + faviconPng512: "./assets/uploads/favicon/icon-512.png", + faviconManifest: "./assets/uploads/favicon/manifest.webmanifest.json", }, }, editableFields: [ @@ -289,6 +295,12 @@ const STATIC_FALLBACK = { field("content.assets.appIconAlt", "Иконка приложения: alt", "text", "text", "Модель и логотипы"), field("content.assets.appWordmarkSrc", "Wordmark приложения", "media", "attr", "Модель и логотипы"), field("content.assets.appWordmarkAlt", "Wordmark приложения: alt", "text", "text", "Модель и логотипы"), + field("content.assets.faviconSvg", "Favicon SVG", "media", "attr", "Модель и логотипы"), + field("content.assets.faviconIco", "Favicon ICO", "media", "attr", "Модель и логотипы"), + field("content.assets.faviconAppleTouchIcon", "Favicon Apple 180", "media", "attr", "Модель и логотипы"), + field("content.assets.faviconPng192", "Favicon PNG 192", "media", "attr", "Модель и логотипы"), + field("content.assets.faviconPng512", "Favicon PNG 512", "media", "attr", "Модель и логотипы"), + field("content.assets.faviconManifest", "Favicon manifest", "media", "attr", "Модель и логотипы"), ], }; @@ -297,7 +309,7 @@ const GROUP_COPY = { "Шапка: ссылки": "Пункты навигации верхней mac-панели. Каждый пункт можно добавить, удалить и переставить.", Уведомления: "Карточки уведомлений справа сверху. Порядок в админке соответствует порядку на сайте.", Dock: "Нижняя системная панель: приложения, корзина, ссылки и подписи файлов.", - "Модель и логотипы": "Базовые ассеты статичного слоя: GLB-модель, PNG/SVG-логотипы и alt-тексты.", + "Модель и логотипы": "Базовые ассеты статичного слоя: GLB-модель, PNG/SVG-логотипы, favicon-комплект и alt-тексты.", Заголовок: "Главный заголовок и вводный текст выбранной секции.", "Верхний информационный блок": "Верхняя пара: длинный текст и крупный заголовок над медиаслайдером.", "Нижний информационный блок": "Нижняя зеркальная пара: крупный заголовок и длинный текст под медиаслайдером.", @@ -328,7 +340,8 @@ const GROUP_COPY = { Контент: "Остальные typed-поля выбранного блока.", }; -const MEDIA_ACCEPT = "image/*,video/*,.gif,.webm,.mov,.mp4,.m4v,.avi,.mkv,.glb,.gltf,.svg,.avif,.webp,.woff,.woff2"; +const MEDIA_ACCEPT = + "image/*,video/*,.gif,.webm,.mov,.mp4,.m4v,.avi,.mkv,.glb,.gltf,.svg,.ico,.avif,.webp,.webmanifest,.json,.woff,.woff2"; const MEDIA_DRAG_MIME = "application/x-nodedc-media-entry"; const MEDIA_KIND_FILTERS = [ ["all", "Все"], @@ -598,6 +611,11 @@ function ensureStaticElements() { notification.eyebrowClass ||= STATIC_FALLBACK.content.notifications[index]?.eyebrowClass || "red-txt gray"; }); state.page.staticElements.content.assets ||= clone(STATIC_FALLBACK.content.assets); + for (const [key, value] of Object.entries(STATIC_FALLBACK.content.assets)) { + if (!Object.prototype.hasOwnProperty.call(state.page.staticElements.content.assets, key)) { + state.page.staticElements.content.assets[key] = value; + } + } state.page.staticElements.content.dock ||= clone(STATIC_FALLBACK.content.dock); normalizeDock(state.page.staticElements.content.dock, state.page.staticElements.content.assets); state.page.staticElements.editableFields = clone(STATIC_FALLBACK.editableFields); @@ -715,7 +733,7 @@ function adminPreviewUrl(value) { function mediaKindFromValue(value) { if (!value) return null; if (/\.(mp4|webm|mov|m4v|avi|mkv)(\?.*)?$/i.test(value)) return "video"; - if (/\.(png|jpe?g|gif|svg|avif|webp)(\?.*)?$/i.test(value)) return "image"; + if (/\.(png|jpe?g|gif|svg|ico|avif|webp)(\?.*)?$/i.test(value)) return "image"; if (/\.(glb|gltf)(\?.*)?$/i.test(value)) return "model"; if (/\.(woff2?|ttf|otf)(\?.*)?$/i.test(value)) return "font"; return null; @@ -781,6 +799,7 @@ function uploadBucketForField(block, editableField) { if (path.includes("webgl") || currentValue.endsWith(".glb") || currentValue.endsWith(".gltf")) root = "models"; if (path.includes("video") || path.includes("media") || path.includes("sliders") || path.includes("slideritems")) root = "media"; if (path.includes("font") || /\.(woff2?|ttf|otf)(\?.*)?$/i.test(currentValue)) root = "fonts"; + if (path.includes("favicon")) return "favicon"; return `${root}/${blockSegment}`; } diff --git a/assets/custom/css/app.css b/assets/custom/css/app.css index 5415318..7b6e6d3 100644 --- a/assets/custom/css/app.css +++ b/assets/custom/css/app.css @@ -5,3 +5,4 @@ .pricing-table-section .pricing-table-shell{padding-top:12svh;padding-bottom:12svh}@media screen and (max-width:767px){.pricing-table-section .pricing-table-shell{padding-top:8svh;padding-bottom:8svh}} [data-module=notification-w][data-notification-stack-ready]{height:auto;max-height:calc(100svh - 3.2rem);overflow:visible;scrollbar-width:none;overscroll-behavior:contain}[data-module=notification-w][data-notification-stack-ready]::-webkit-scrollbar{display:none}[data-module=notification-w][data-notification-stack-ready]:not(:hover):not([data-notification-expanded=true]){height:var(--notification-collapsed-height,6.6rem);max-height:var(--notification-collapsed-height,6.6rem)}[data-module=notification-w][data-notification-stack-ready]:is(:hover,[data-notification-expanded=true]){overflow-x:visible;overflow-y:auto;padding-bottom:.2rem}[data-module=notification-w][data-notification-stack-ready]>[data-module=notification]{position:relative;z-index:var(--notification-z,1);transform:translateY(var(--notification-stack-y,0));transition:transform .3s cubic-bezier(.22,1,.36,1)}[data-module=notification-w][data-notification-stack-ready]:is(:hover,[data-notification-expanded=true])>[data-module=notification]{transform:translateY(0)} .dock-icon.ssscript{overflow:hidden;background-color:transparent;background-image:none}.dock-icon.ssscript>img{display:block;width:100%;height:100%;max-width:none;object-fit:cover;border-radius:inherit} +[data-module=dock]{overflow:visible}[data-module=dock] .dock-icon{position:relative;overflow:visible}[data-module=dock] .dock-icon.ssscript{overflow:visible}[data-module=dock] .dock-icon::after{content:attr(data-dock-title);position:absolute;z-index:30;left:50%;bottom:calc(100% + .64rem);max-width:14rem;transform:translate(-50%,.28rem) scale(.96);transform-origin:50% 100%;opacity:0;pointer-events:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border:1px solid rgba(255,255,255,.12);border-radius:.72rem;background:linear-gradient(180deg,rgba(255,255,255,.07),rgba(255,255,255,.018)),rgba(34,34,38,.86);box-shadow:inset 0 1px 0 rgba(255,255,255,.08),0 20px 60px rgba(0,0,0,.42);-webkit-backdrop-filter:blur(22px) saturate(1.15);backdrop-filter:blur(22px) saturate(1.15);color:rgba(255,255,255,.92);padding:.42rem .68rem;font-size:.68rem;font-weight:760;line-height:1;letter-spacing:0;transition:opacity .16s ease,transform .16s ease}[data-module=dock] .dock-icon:is(:hover,:focus-visible){z-index:20}[data-module=dock] .dock-icon:is(:hover,:focus-visible)::after{opacity:1;transform:translate(-50%,0) scale(1)}[data-module=dock] .dock-icon[data-dock-title=""]::after{display:none} diff --git a/assets/custom/css/out.css b/assets/custom/css/out.css index b6cec2b..107628c 100644 --- a/assets/custom/css/out.css +++ b/assets/custom/css/out.css @@ -5,3 +5,4 @@ .pricing-table-section .pricing-table-shell{padding-top:12svh;padding-bottom:12svh}@media screen and (max-width:767px){.pricing-table-section .pricing-table-shell{padding-top:8svh;padding-bottom:8svh}} [data-module=notification-w][data-notification-stack-ready]{height:auto;max-height:calc(100svh - 3.2rem);overflow:visible;scrollbar-width:none;overscroll-behavior:contain}[data-module=notification-w][data-notification-stack-ready]::-webkit-scrollbar{display:none}[data-module=notification-w][data-notification-stack-ready]:not(:hover):not([data-notification-expanded=true]){height:var(--notification-collapsed-height,6.6rem);max-height:var(--notification-collapsed-height,6.6rem)}[data-module=notification-w][data-notification-stack-ready]:is(:hover,[data-notification-expanded=true]){overflow-x:visible;overflow-y:auto;padding-bottom:.2rem}[data-module=notification-w][data-notification-stack-ready]>[data-module=notification]{position:relative;z-index:var(--notification-z,1);transform:translateY(var(--notification-stack-y,0));transition:transform .3s cubic-bezier(.22,1,.36,1)}[data-module=notification-w][data-notification-stack-ready]:is(:hover,[data-notification-expanded=true])>[data-module=notification]{transform:translateY(0)} .dock-icon.ssscript{overflow:hidden;background-color:transparent;background-image:none}.dock-icon.ssscript>img{display:block;width:100%;height:100%;max-width:none;object-fit:cover;border-radius:inherit} +[data-module=dock]{overflow:visible}[data-module=dock] .dock-icon{position:relative;overflow:visible}[data-module=dock] .dock-icon.ssscript{overflow:visible}[data-module=dock] .dock-icon::after{content:attr(data-dock-title);position:absolute;z-index:30;left:50%;bottom:calc(100% + .64rem);max-width:14rem;transform:translate(-50%,.28rem) scale(.96);transform-origin:50% 100%;opacity:0;pointer-events:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border:1px solid rgba(255,255,255,.12);border-radius:.72rem;background:linear-gradient(180deg,rgba(255,255,255,.07),rgba(255,255,255,.018)),rgba(34,34,38,.86);box-shadow:inset 0 1px 0 rgba(255,255,255,.08),0 20px 60px rgba(0,0,0,.42);-webkit-backdrop-filter:blur(22px) saturate(1.15);backdrop-filter:blur(22px) saturate(1.15);color:rgba(255,255,255,.92);padding:.42rem .68rem;font-size:.68rem;font-weight:760;line-height:1;letter-spacing:0;transition:opacity .16s ease,transform .16s ease}[data-module=dock] .dock-icon:is(:hover,:focus-visible){z-index:20}[data-module=dock] .dock-icon:is(:hover,:focus-visible)::after{opacity:1;transform:translate(-50%,0) scale(1)}[data-module=dock] .dock-icon[data-dock-title=""]::after{display:none} diff --git a/assets/custom/js/interaction-overrides.js b/assets/custom/js/interaction-overrides.js index 3e945cd..fca4aaf 100644 --- a/assets/custom/js/interaction-overrides.js +++ b/assets/custom/js/interaction-overrides.js @@ -372,7 +372,15 @@ const hasAppTarget = Boolean(targetId && document.getElementById(targetId)); const isInertHref = !href || href === "#" || href === "#hero" || href.toLowerCase().startsWith("javascript:"); - if (!hasAppTarget && !isInertHref) return; + if (!isInertHref) { + event.preventDefault(); + event.stopImmediatePropagation(); + link.setAttribute("target", "_blank"); + link.setAttribute("rel", "noopener noreferrer"); + window.open(href, "_blank", "noopener,noreferrer"); + window.requestAnimationFrame(() => link.blur()); + return; + } event.preventDefault(); window.requestAnimationFrame(() => link.blur()); diff --git a/assets/uploads/favicon/apple-touch-icon.png b/assets/uploads/favicon/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5078c01653bd8fca4c6ff0d98769b86f54f17743 GIT binary patch literal 4142 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 literal 0 HcmV?d00001 diff --git a/assets/uploads/favicon/favicon.ico b/assets/uploads/favicon/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..973f3b2db97f00b686175edb7497021fe0f1421d GIT binary patch literal 16958 zcmeI4U5Hd=7{}jfidmD~6|IX*6f)Wnf+Q)(+Cdbxi;&QZK)Oj&H18Baw&oDs3EBsE z;e{-Scp-ux3t3&XQc8hzBS{xa4J68D#MTCGvCjDW&p9uw!#VG1XXmV^nfF=$=b7_9 z&v`!nXU==xGc#yRfxmLuuw8EY*BbMLF{U3dWCEb^hG1JXKSVg-x1IsdfM>un;2H1? zcm_NJo&nE*XTUQsM+|&Sp20cFmA5pjC};{yfE9TwF5`NN#Qz%9Bb?4SU*5v+oYuSL z;LW@hnNdAY5_c@7|53*JGRm2!u*YM1x9@>Nc`7u6S~d`~=IDPC+v*JR<{{*x6mbpM zbDg9w;iLJn)8qphO6s|Jd{2ipzmk6boBpM&OeoVI_W6Pl-oZ7uX} zx!L$X=iqVb|8*Aq|H!eA<&1+~>e&Hy0j=Yjv7504Kynn=a~Y?9!v1rLPk>#r7`z9X z^J!nTyaVQgtmQKj#PwpwhOz7ZwFcaE`0Cl`W$=c>M=>>TYW|!Dt^k`SPU{|%rMyOP z)g6~xV}F3Bz$4%)Nb=M85U_HA7cSo~Bgf~_cO1Ur>bc0b-KvLxX+7rbCkB-Qs1wwI!WXB>VyUhnlJNxQy}MF+l)PO3%YRj>%OLv&8R0m%^jz=LezuOr|$c%7~f(SSFOrP4s^vo z8wFm4)*RsSZKv%|a;=DRt4CLCany=z<{($xaqQ0k%@3DTd|h!`wb^;}MRMzNlcYF$ zmRtg6A`5X&yOuSNNwnTg%ANJNz4i{J)G&m7Ch`f+uG+N@DgmuqT->{2w8Qp0`85wN z0Iu_;`_5YMUt|qVQy{56*Pr z=WlBh2iv=-cX2QZ^f{J9YlBqZYX-?F&@DYr?^Dl5T03fOpx507J!|S7=EXDM z8So5v20R0v0ndPEz%$?(@CcG94$rVuPA(nwDs=p)jIDe|vg5Bf^g9wO-fMtE4d zA@!Om>l@8V%>?=tO+{b!pa&)!zo-cY^ko;aOlg!TEV9~L)h^$OjWm=g1)(lR+WsEL N>d?wIY~{gi?*Ti^k*5Fv literal 0 HcmV?d00001 diff --git a/assets/uploads/favicon/icon-192.png b/assets/uploads/favicon/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..d553a58e81dd92630500cf4bdc2f104993412841 GIT binary patch literal 4386 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 + + + + \ No newline at end of file diff --git a/assets/uploads/favicon/manifest.webmanifest.json b/assets/uploads/favicon/manifest.webmanifest.json new file mode 100644 index 0000000..f7256e8 --- /dev/null +++ b/assets/uploads/favicon/manifest.webmanifest.json @@ -0,0 +1,29 @@ +{ + "theme_color": "#eeeff4", + "background_color": "#eeeff4", + "display": "browser", + "scope": "/", + "start_url": "/", + "icons": [ + { + "src": "icon-adaptive.svg", + "type": "image/svg+xml", + "sizes": "any" + }, + { + "src": "icon-192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "icon-512.png", + "type": "image/png", + "sizes": "512x512" + }, + { + "src": "apple-touch-icon.png", + "type": "image/png", + "sizes": "180x180" + } + ] +} diff --git a/content/pages/home.json b/content/pages/home.json index 0f17250..f696377 100644 --- a/content/pages/home.json +++ b/content/pages/home.json @@ -150,7 +150,13 @@ "appIconSrc": "./assets/uploads/images/Logo/dclogo_white_alpha-mqus5if2.png", "appIconAlt": "Иконка NODE.DC", "appWordmarkSrc": "./assets/webflow/images/69ad96a28b06b7552a339137_logtype.svg?v=node-dc", - "appWordmarkAlt": "Логотип NODE.DC" + "appWordmarkAlt": "Логотип NODE.DC", + "faviconSvg": "./assets/uploads/favicon/icon-adaptive.svg", + "faviconIco": "./assets/uploads/favicon/favicon.ico", + "faviconAppleTouchIcon": "./assets/uploads/favicon/apple-touch-icon.png", + "faviconPng192": "./assets/uploads/favicon/icon-192.png", + "faviconPng512": "./assets/uploads/favicon/icon-512.png", + "faviconManifest": "./assets/uploads/favicon/manifest.webmanifest.json" } }, "editableFields": [ @@ -382,6 +388,48 @@ "kind": "text", "renderAs": "text", "group": "Модель и логотипы" + }, + { + "path": "content.assets.faviconSvg", + "label": "Favicon SVG", + "kind": "media", + "renderAs": "attr", + "group": "Модель и логотипы" + }, + { + "path": "content.assets.faviconIco", + "label": "Favicon ICO", + "kind": "media", + "renderAs": "attr", + "group": "Модель и логотипы" + }, + { + "path": "content.assets.faviconAppleTouchIcon", + "label": "Favicon Apple 180", + "kind": "media", + "renderAs": "attr", + "group": "Модель и логотипы" + }, + { + "path": "content.assets.faviconPng192", + "label": "Favicon PNG 192", + "kind": "media", + "renderAs": "attr", + "group": "Модель и логотипы" + }, + { + "path": "content.assets.faviconPng512", + "label": "Favicon PNG 512", + "kind": "media", + "renderAs": "attr", + "group": "Модель и логотипы" + }, + { + "path": "content.assets.faviconManifest", + "label": "Favicon manifest", + "kind": "media", + "renderAs": "attr", + "group": "Модель и логотипы" } ] }, diff --git a/index.html b/index.html index fb7e5a2..f5dba77 100644 --- a/index.html +++ b/index.html @@ -1,4 +1,4 @@ -NODE.DC — ИИ-платформа для Webflow +
Логотип NODE.DC
Selfhost платформа

Запросить DEMO

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

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

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

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

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

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

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

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

Иконка TXT-файла
Hub Access

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

Иконка TXT-файла
Engine Workflows

Нодовые workflow, обработчики данных, интеграционная логика и сборка прикладных контуров.

Иконка TXT-файла
Ops Cards

Операционные карточки, статусы, согласования, отчеты агентов и результаты выполнения задач.

Иконка TXT-файла
1C Assistant

Связка с 1C, разбор документов, поиск данных, сценарии согласований и передача результата в Ops.

Иконка TXT-файла
Tender Agent

Тендерные подборки, проверка требований, документы, дедлайны и агентные отчеты по процедурам.

Иконка TXT-файла
Digital Twin

Цифровые модели объектов, версии, проектный контекст, инженерные данные и связанные рабочие процессы.

Иконка TXT-файла
Model Viewer

Просмотр BIM, STEP, STL, 3DGS, облаков точек и инженерных материалов внутри единого контура.

Иконка TXT-файла
Engineering Core

Обработка технических форматов, связка моделей с задачами, версиями, отчетами и согласованиями.

Иконка TXT-файла
MCP Gateway

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

Иконка TXT-файла
API Contracts

Контракты API, схемы данных, webhooks, события, интеграции и контроль совместимости между модулями.

Иконка TXT-файла
Kubernetes

Оркестрация сервисов, изоляция окружений, scaling policies, probes, secrets и управляемый rollout.

Иконка TXT-файла
Cloudflare Edge

CDN, WAF, R2-хранилище, edge routing, cache policies и защита публичного периметра.

Иконка TXT-файла
Zero Trust

Доступ по принципу least privilege: SSO, MFA, device posture, audit trail и сегментация контуров.

Иконка TXT-файла
Security Matrix

Ролевая матрица, группы, политики доступа, ограничения функций и контроль административных действий.

Иконка TXT-файла
RBAC Policies

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

Иконка TXT-файла
Audit Log

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

Иконка TXT-файла
Secrets Vault

Хранение токенов, ключей API, webhook-секретов и конфигураций интеграций в защищенном контуре.

Иконка TXT-файла
Postgres Cluster

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

Иконка TXT-файла
Redis Queue

Очереди фоновых задач, статусы выполнения, debounce событий, кэширование и быстрые агентные операции.

Иконка TXT-файла
Vector Index

Индексация проектных материалов, документации, отчетов и контекста для ассистента и поиска.

Иконка TXT-файла
Local Models

Контур локальных моделей для приватных сценариев, инженерных данных и корпоративных ограничений.

Иконка TXT-файла
Model Routing

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

Иконка TXT-файла
Agent Runtime

Запуск ассистентов, выполнение действий, контроль инструментов, журналирование и передача результата в Ops.

Иконка TXT-файла
Observability

Метрики, логи, трассировка, health checks, алерты и диагностика сервисов платформы.

Иконка TXT-файла
Backups

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

Иконка TXT-файла
CI CD

Сборка, проверки, preview-окружения, controlled rollout, rollback и публикация клиентских модулей.

Иконка TXT-файла
Terraform

Описание инфраструктуры кодом: окружения, сети, хранилища, политики, сервисы и переменные.

Иконка TXT-файла
Object Storage

Хранилище файлов, изображений, моделей, отчетов, txt-материалов и артефактов рабочих процессов.

Иконка TXT-файла
BIM Pipeline

Загрузка BIM-моделей, версии, разметка, связи с задачами и публикация в рабочие пространства.

Иконка TXT-файла
STEP Import

Импорт STEP-моделей, технические материалы, карточки проверки и привязка к инженерным задачам.

Иконка TXT-файла
STL Review

Проверка STL-геометрии, заметки, версии, визуальный контроль и подготовка производственных материалов.

Иконка TXT-файла
3DGS Assets

Gaussian Splatting сцены, визуальные реконструкции объектов и связь с проектным контекстом.

Иконка TXT-файла
LAS LAZ Clouds

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

Иконка TXT-файла
Webhooks

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

Иконка TXT-файла
Docs Portal

Документация, регламенты, инструкции, технические заметки и материалы для команд внедрения.

Иконка TXT-файла
Stack Map

Карта платформенного стека: Hub, Engine, Ops, агенты, данные, интеграции, безопасность и инфраструктура.

Иконка TXT-файла
Hub Access
.txt
Иконка TXT-файла
Engine Workflows
.txt
Иконка TXT-файла
Ops Cards
.txt
Иконка TXT-файла
1C Assistant
.txt
Иконка TXT-файла
Tender Agent
.txt
Иконка TXT-файла
Digital Twin
.txt
Иконка TXT-файла
Model Viewer
.txt
Иконка TXT-файла
Engineering Core
.txt
Иконка TXT-файла
MCP Gateway
.txt
Иконка TXT-файла
API Contracts
.txt
Иконка TXT-файла
Kubernetes
.txt
Иконка TXT-файла
Cloudflare Edge
.txt
Иконка TXT-файла
Zero Trust
.txt
Иконка TXT-файла
Security Matrix
.txt
Иконка TXT-файла
RBAC Policies
.txt
Иконка TXT-файла
Audit Log
.txt
Иконка TXT-файла
Secrets Vault
.txt
Иконка TXT-файла
Postgres Cluster
.txt
Иконка TXT-файла
Redis Queue
.txt
Иконка TXT-файла
Vector Index
.txt
Иконка TXT-файла
Local Models
.txt
Иконка TXT-файла
Model Routing
.txt
Иконка TXT-файла
Agent Runtime
.txt
Иконка TXT-файла
Observability
.txt
Иконка TXT-файла
Backups
.txt
Иконка TXT-файла
CI CD
.txt
Иконка TXT-файла
Terraform
.txt
Иконка TXT-файла
Object Storage
.txt
Иконка TXT-файла
BIM Pipeline
.txt
Иконка TXT-файла
STEP Import
.txt
Иконка TXT-файла
STL Review
.txt
Иконка TXT-файла
3DGS Assets
.txt
Иконка TXT-файла
LAS LAZ Clouds
.txt
Иконка TXT-файла
Webhooks
.txt
Иконка TXT-файла
Docs Portal
.txt
Иконка TXT-файла
Stack Map
.txt
РАНЕЕ — МАЙ 2026
Engineering Core выделен в отдельный модуль
Единый контур инженерных моделей, версий, согласований и рабочих материалов.
РАНЕЕ ИЮНЬ 2026
Knowledge Graph добавлен в состав экосистемы
Связи между задачами, агентами, кодовой базой, документами и инженерными сущностями собраны в общем графе.
СЕЙЧАС
Открыт alpha-доступ к Procurement Intelligence
Агентный контур для закупок, сопоставления условий и контроля поставок.
ДАЛЕЕ СЕНТЯБРЬ 2026
Field Ops включён в план релиза
Мобильный операционный модуль для выездных команд, инспекций, фотофиксации и полевых отчётов.
diff --git a/templates/pages/home/shell.html b/templates/pages/home/shell.html index 14e23f8..b614de5 100644 --- a/templates/pages/home/shell.html +++ b/templates/pages/home/shell.html @@ -1,4 +1,4 @@ -NODE.DC — ИИ-платформа для Webflow{{#if staticElements.content.assets.faviconSvg}}{{/if}}{{#if staticElements.content.assets.faviconIco}}{{/if}}{{#if staticElements.content.assets.faviconAppleTouchIcon}}{{/if}}{{#if staticElements.content.assets.faviconPng192}}{{/if}}{{#if staticElements.content.assets.faviconPng512}}{{/if}}{{#if staticElements.content.assets.faviconManifest}}{{/if}} +
{{NODEDC_BLOCKS_SECTIONS}}
{{#each staticElements.content.notifications}}{{#if enabled}}
{{html:eyebrowHtml}}
{{text:title}}
{{text:body}}
{{/if}}{{/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}}{{#if enabled}}{{/if}}{{/each}}
Сб, 10 июня
09:41
diff --git a/tools/admin-server.mjs b/tools/admin-server.mjs index a0decf3..b0a87a7 100644 --- a/tools/admin-server.mjs +++ b/tools/admin-server.mjs @@ -66,6 +66,8 @@ const MEDIA_EXTENSIONS = new Set([ ".gif", ".glb", ".gltf", + ".ico", + ".json", ".jpeg", ".jpg", ".m4v", @@ -73,6 +75,7 @@ const MEDIA_EXTENSIONS = new Set([ ".mp4", ".png", ".svg", + ".webmanifest", ".webm", ".webp", ".woff", @@ -91,6 +94,7 @@ const REFERENCE_TEXT_EXTENSIONS = new Set([ ".ts", ".tsx", ".txt", + ".webmanifest", ".xml", ]); @@ -100,6 +104,8 @@ const MIME = { ".js": "text/javascript; charset=utf-8", ".json": "application/json; charset=utf-8", ".svg": "image/svg+xml", + ".ico": "image/x-icon", + ".webmanifest": "application/manifest+json; charset=utf-8", ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", @@ -150,6 +156,15 @@ function sendStream(req, res, status, stream, headers) { stream.pipe(res); } +function contentTypeForPath(filePath) { + const normalizedPath = String(filePath || "").toLowerCase(); + if (normalizedPath.endsWith(".webmanifest") || normalizedPath.endsWith(".webmanifest.json")) { + return "application/manifest+json; charset=utf-8"; + } + + return MIME[extname(filePath).toLowerCase()] || "application/octet-stream"; +} + function sendJson(res, status, payload) { send(res, status, `${JSON.stringify(payload, null, 2)}\n`, "application/json; charset=utf-8"); } @@ -225,6 +240,8 @@ function sanitizeUploadBucket(value) { function extensionFromMimeType(mimeType) { const map = { "image/svg+xml": ".svg", + "image/x-icon": ".ico", + "image/vnd.microsoft.icon": ".ico", "image/png": ".png", "image/jpeg": ".jpg", "image/gif": ".gif", @@ -233,6 +250,8 @@ function extensionFromMimeType(mimeType) { "video/mp4": ".mp4", "video/webm": ".webm", "video/quicktime": ".mov", + "application/manifest+json": ".webmanifest", + "application/json": ".json", "model/gltf-binary": ".glb", "model/gltf+json": ".gltf", "font/woff": ".woff", @@ -503,7 +522,7 @@ function serializedAssetReplacements(replacements) { } function mediaKindFromExtension(extension) { - if ([".avif", ".gif", ".jpeg", ".jpg", ".png", ".svg", ".webp"].includes(extension)) return "image"; + if ([".avif", ".gif", ".ico", ".jpeg", ".jpg", ".png", ".svg", ".webp"].includes(extension)) return "image"; if ([".m4v", ".mov", ".mp4", ".webm"].includes(extension)) return "video"; if ([".glb", ".gltf"].includes(extension)) return "model"; if ([".woff", ".woff2"].includes(extension)) return "font"; @@ -1383,8 +1402,7 @@ async function serveStatic(req, res) { return; } - const ext = extname(filePath); - const contentType = MIME[ext] || "application/octet-stream"; + const contentType = contentTypeForPath(filePath); const range = req.headers.range; if (range) { diff --git a/tools/extract-home-blocks.mjs b/tools/extract-home-blocks.mjs index fc93791..a68b3de 100644 --- a/tools/extract-home-blocks.mjs +++ b/tools/extract-home-blocks.mjs @@ -720,6 +720,12 @@ const staticElements = { appIconAlt: "Иконка NODE.DC", appWordmarkSrc: "./assets/webflow/images/69ad96a28b06b7552a339137_logtype.svg?v=node-dc", appWordmarkAlt: "Логотип NODE.DC", + faviconSvg: "./assets/uploads/favicon/icon-adaptive.svg", + faviconIco: "./assets/uploads/favicon/favicon.ico", + faviconAppleTouchIcon: "./assets/uploads/favicon/apple-touch-icon.png", + faviconPng192: "./assets/uploads/favicon/icon-192.png", + faviconPng512: "./assets/uploads/favicon/icon-512.png", + faviconManifest: "./assets/uploads/favicon/manifest.webmanifest.json", }, }, editableFields: [ @@ -753,6 +759,12 @@ const staticElements = { field("content.assets.appIconAlt", "Иконка приложения: alt"), field("content.assets.appWordmarkSrc", "Wordmark приложения", "media", "attr"), field("content.assets.appWordmarkAlt", "Wordmark приложения: alt"), + field("content.assets.faviconSvg", "Favicon SVG", "media", "attr"), + field("content.assets.faviconIco", "Favicon ICO", "media", "attr"), + field("content.assets.faviconAppleTouchIcon", "Favicon Apple 180", "media", "attr"), + field("content.assets.faviconPng192", "Favicon PNG 192", "media", "attr"), + field("content.assets.faviconPng512", "Favicon PNG 512", "media", "attr"), + field("content.assets.faviconManifest", "Favicon manifest", "media", "attr"), ], }; @@ -772,6 +784,39 @@ function tokenizeStaticShell(source, definition) { const { navigation, notifications, dock } = definition.content; let tokenized = source; + const faviconHead = [ + `{{#if staticElements.content.assets.faviconSvg}}{{/if}}`, + `{{#if staticElements.content.assets.faviconIco}}{{/if}}`, + `{{#if staticElements.content.assets.faviconAppleTouchIcon}}{{/if}}`, + `{{#if staticElements.content.assets.faviconPng192}}{{/if}}`, + `{{#if staticElements.content.assets.faviconPng512}}{{/if}}`, + `{{#if staticElements.content.assets.faviconManifest}}{{/if}}`, + ].join(""); + + tokenized = replaceShellLiteral( + tokenized, + ``, + faviconHead, + "favicon links", + ); tokenized = replaceShellLiteral( tokenized, diff --git a/tools/render-home.mjs b/tools/render-home.mjs index 36a7b97..c98fe2a 100644 --- a/tools/render-home.mjs +++ b/tools/render-home.mjs @@ -78,6 +78,11 @@ function isExternalHref(value) { return /^(https?:)?\/\//i.test(String(value || "")) || /^(mailto:|tel:)/i.test(String(value || "")); } +function isInertHref(value) { + const href = String(value || "").trim(); + return !href || href === "#" || href === "#hero" || href.toLowerCase().startsWith("javascript:"); +} + function normalizeLinkForRender(item) { if (!item || typeof item !== "object") return; @@ -103,12 +108,28 @@ function normalizeLinkForRender(item) { item.resolvedTarget = mode === "href" ? linkTarget : ""; } +function normalizeDockItemForRender(item) { + if (!item || typeof item !== "object") return; + + const href = String(item.href || "").trim(); + const opensWindow = !isInertHref(href); + + item.resolvedDockHref = href || "#"; + item.resolvedDockTarget = opensWindow ? "_blank" : ""; + item.resolvedDockRel = opensWindow ? "noopener noreferrer" : ""; +} + function applyRenderableLinks(model) { const staticLinks = model.staticElements?.content?.navigation?.links; if (Array.isArray(staticLinks)) { staticLinks.forEach(normalizeLinkForRender); } + const dockItems = model.staticElements?.content?.dock?.items; + if (Array.isArray(dockItems)) { + dockItems.forEach(normalizeDockItemForRender); + } + for (const block of allBlocks(model)) { const footer = block.content?.footer; if (!footer) continue;