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 0000000..5078c01 Binary files /dev/null and b/assets/uploads/favicon/apple-touch-icon.png differ diff --git a/assets/uploads/favicon/favicon.ico b/assets/uploads/favicon/favicon.ico new file mode 100644 index 0000000..973f3b2 Binary files /dev/null and b/assets/uploads/favicon/favicon.ico differ diff --git a/assets/uploads/favicon/icon-192.png b/assets/uploads/favicon/icon-192.png new file mode 100644 index 0000000..d553a58 Binary files /dev/null and b/assets/uploads/favicon/icon-192.png differ diff --git a/assets/uploads/favicon/icon-512.png b/assets/uploads/favicon/icon-512.png new file mode 100644 index 0000000..a6f47b9 Binary files /dev/null and b/assets/uploads/favicon/icon-512.png differ diff --git a/assets/uploads/favicon/icon-adaptive.svg b/assets/uploads/favicon/icon-adaptive.svg new file mode 100644 index 0000000..fee636f --- /dev/null +++ b/assets/uploads/favicon/icon-adaptive.svg @@ -0,0 +1,13 @@ + + + + + \ 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 включён в план релиза
Мобильный операционный модуль для выездных команд, инспекций, фотофиксации и полевых отчётов.
Белая иконка корзины.
Сб, 10 июня
09:41
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;