From 9cfbb95b1e5968ab2831c45799fae16331ae0134 Mon Sep 17 00:00:00 2001
From: chermack <505360893@qq.com>
Date: Sat, 22 Aug 2026 21:49:25 +0800
Subject: [PATCH] feat: launch Roncarve Harness product page
---
.dockerignore | 11 +-
.gitignore | 7 +
Dockerfile | 13 +-
baidu_verify_codeva-Jnq0QS7SFC.html | 1 +
docker-compose.yml | 6 +
index.html | 108 +-
nginx.conf | 9 +
package-lock.json | 1115 +++++++++++++++++++
package.json | 22 +
products/roncarve-harness/index.html | 40 +
scripts/copy-static.mjs | 20 +
sitemap.xml | 8 +-
src/harness/HarnessProductPage.jsx | 471 ++++++++
src/harness/ParticleStage.jsx | 402 +++++++
src/harness/harness.css | 590 ++++++++++
src/harness/main.jsx | 10 +
vite.config.js | 24 +
荣刻驭境-Roncarve-Harness-产品页设计文档.md | 717 ++++++++++++
18 files changed, 3561 insertions(+), 13 deletions(-)
create mode 100644 baidu_verify_codeva-Jnq0QS7SFC.html
create mode 100644 package-lock.json
create mode 100644 package.json
create mode 100644 products/roncarve-harness/index.html
create mode 100644 scripts/copy-static.mjs
create mode 100644 src/harness/HarnessProductPage.jsx
create mode 100644 src/harness/ParticleStage.jsx
create mode 100644 src/harness/harness.css
create mode 100644 src/harness/main.jsx
create mode 100644 vite.config.js
create mode 100644 荣刻驭境-Roncarve-Harness-产品页设计文档.md
diff --git a/.dockerignore b/.dockerignore
index fd4083b..5d44b91 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,8 +1,17 @@
.git
+.gitignore
.idea
qa-evidence
design-references
+.docker-config
+.npm-cache
+.npm-cache-*
+node_modules
+dist
+design-qa-artifacts
+deploy
Dockerfile
-docker-compose.yml
.dockerignore
*.md
+docker-compose*.yml
+Caddyfile.server
diff --git a/.gitignore b/.gitignore
index a7e4258..5be7482 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,3 +43,10 @@ buildNumber.properties
# JDT-specific (Eclipse Java Development Tools)
.classpath
+node_modules/
+dist/
+.npm-cache/
+.npm-cache-*/
+.docker-config/
+design-qa-artifacts/
+design-qa.md
diff --git a/Dockerfile b/Dockerfile
index f2d3e1b..58507f9 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,6 +1,15 @@
-FROM nginx:1.27-alpine
+FROM node:22-alpine AS build
+
+WORKDIR /app
+COPY package.json package-lock.json ./
+RUN npm ci --no-audit --no-fund
+
+COPY . .
+RUN npm run build
+
+FROM nginx:alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
-COPY . /usr/share/nginx/html
+COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
diff --git a/baidu_verify_codeva-Jnq0QS7SFC.html b/baidu_verify_codeva-Jnq0QS7SFC.html
new file mode 100644
index 0000000..76a54fb
--- /dev/null
+++ b/baidu_verify_codeva-Jnq0QS7SFC.html
@@ -0,0 +1 @@
+94f23ded66ca5b5c5003812faeacf00d
diff --git a/docker-compose.yml b/docker-compose.yml
index 3390180..0db5356 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -5,3 +5,9 @@ services:
restart: unless-stopped
ports:
- "127.0.0.1:8081:80"
+ healthcheck:
+ test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1/products/roncarve-harness/"]
+ interval: 10s
+ timeout: 3s
+ retries: 5
+ start_period: 5s
diff --git a/index.html b/index.html
index 8ce6b98..a9a01b3 100644
--- a/index.html
+++ b/index.html
@@ -233,6 +233,52 @@
.nav-link:hover, .nav-link.active { color: var(--white); }
.nav-link.active::after { opacity: 1; transform: translateX(-50%) scale(1); }
+ .product-nav { position: relative; }
+ .product-trigger {
+ display: block;
+ border: 0;
+ background: transparent;
+ font-family: inherit;
+ cursor: pointer;
+ }
+ .product-nav.open .product-trigger { color: var(--white); }
+ .product-nav.open .product-trigger::after { opacity: 1; transform: translateX(-50%) scale(1); }
+ .product-popover {
+ position: absolute;
+ top: calc(100% + 17px);
+ left: 50%;
+ z-index: 1100;
+ width: 430px;
+ padding: 8px;
+ border: 1px solid var(--line-strong);
+ border-radius: 16px;
+ background: color-mix(in srgb, var(--ink-900) 96%, transparent);
+ box-shadow: 0 24px 70px rgba(0, 0, 0, .32), inset 0 1px 0 rgba(255, 255, 255, .035);
+ backdrop-filter: blur(20px) saturate(135%);
+ -webkit-backdrop-filter: blur(20px) saturate(135%);
+ opacity: 0;
+ pointer-events: none;
+ transform: translate(-50%, -8px) scale(.98);
+ transform-origin: top center;
+ transition: opacity .2s ease, transform .2s var(--ease);
+ }
+ .product-nav.open .product-popover { opacity: 1; pointer-events: auto; transform: translate(-50%, 0) scale(1); }
+ .product-card {
+ display: block;
+ padding: 20px;
+ border: 1px solid var(--line);
+ border-radius: 12px;
+ background: linear-gradient(140deg, color-mix(in srgb, var(--cyan) 8%, transparent), rgba(255, 255, 255, .018));
+ transition: border-color .22s ease, background .22s ease, transform .22s var(--ease);
+ }
+ .product-card:hover { transform: translateY(-2px); border-color: var(--line-strong); background: color-mix(in srgb, var(--cyan) 10%, var(--ink-900)); }
+ .product-card-meta { display: flex; align-items: center; justify-content: space-between; gap: 18px; }
+ .product-card-meta span { color: var(--cyan); font: 700 9px/1 var(--mono); letter-spacing: .13em; }
+ .product-card-meta strong { padding: 6px 8px; border: 1px solid var(--line); border-radius: 999px; color: var(--text-soft); background: rgba(255, 255, 255, .025); font: 650 9px/1 var(--mono); }
+ .product-card h3 { margin: 20px 0 4px; color: var(--white); font-size: 22px; letter-spacing: -.03em; }
+ .product-card small { color: var(--text-faint); font: 550 10px/1 var(--mono); letter-spacing: .1em; }
+ .product-card p { margin: 14px 0 0; color: var(--text-soft); font-size: 13px; line-height: 1.65; }
+
.nav-actions { display: flex; align-items: center; gap: 10px; }
.preference-menu { position: relative; }
.preference-trigger {
@@ -784,13 +830,6 @@
.hero-spatial { width: min(100%, 760px); min-height: 420px; margin-left: auto; }
}
- /* Let desktop layouts use the available canvas on ultrawide screens. */
- @media (min-width: 1440px) {
- :root { --max-width: 88vw; }
- .hero-layout { gap: clamp(64px, 6vw, 132px); }
- .section-header { gap: clamp(48px, 6vw, 120px); }
- }
-
@media (max-width: 860px) {
:root[data-theme="light"] .nav-links { background: rgba(250, 252, 255, .98); }
}
@@ -859,6 +898,24 @@
.nav-links.open { opacity: 1; pointer-events: auto; transform: translateY(0); }
.nav-link { padding: 14px 6px; border-bottom: 1px solid var(--line); font-size: 15px; }
.nav-link::after { display: none; }
+ .product-nav { width: 100%; }
+ .product-trigger { width: 100%; text-align: left; }
+ .product-popover {
+ position: relative;
+ top: auto;
+ left: auto;
+ width: 100%;
+ max-height: 0;
+ margin: 0;
+ padding: 0 8px;
+ overflow: hidden;
+ border-color: transparent;
+ opacity: 1;
+ transform: none;
+ transition: max-height .25s var(--ease), margin .25s ease, padding .25s ease, border-color .25s ease;
+ }
+ .product-nav.open .product-popover { max-height: 250px; margin: 8px 0 12px; padding: 8px; border-color: var(--line); transform: none; }
+ .product-card { padding: 17px; }
.nav-actions { padding-top: 14px; justify-content: space-between; }
.nav-cta { min-height: 42px; }
.hero { padding-top: 126px; }
@@ -954,6 +1011,17 @@
+
能力链路
解决方案
核心优势
@@ -1187,7 +1255,7 @@
zh: {
title: '海南荣刻科技有限公司 - 数据筑基 · AI 落地', skip: '跳到主要内容', brand: '荣刻科技', nav_label: '主导航', home_label: '荣刻科技首页', lang_label: '语言切换', menu_open: '打开导航菜单', menu_close: '关闭导航菜单',
theme_label: '外观模式选择', theme_system: '跟随系统', theme_light: '浅色外观', theme_dark: '深色外观', theme_system_short: 'Auto', theme_light_short: 'Light', theme_dark_short: 'Dark', language_zh: '简体中文', language_en: 'English',
- nav_about: '能力链路', nav_services: '解决方案', nav_advantages: '核心优势', nav_offices: '交付网络', nav_contact: '预约咨询',
+ nav_products: '产品', product_list_label: '产品列表', product_status: '即将开放', product_harness_name: '荣刻驭境', product_harness_desc: '本地客户端连接本地项目,平台共享工作空间承载团队协同,并内置精准插件检索与高效热插拔。', nav_about: '能力链路', nav_services: '解决方案', nav_advantages: '核心优势', nav_offices: '交付网络', nav_contact: '预约咨询',
hero_eyebrow: '海南荣刻科技有限公司 · AI 与数据基础设施', hero_line1: '让企业数据', hero_line2: '成为 AI 生产力',
hero_desc: '从数据治理、模型优化到私有化部署,荣刻科技为企业构建可落地、可持续、可掌控的 AI 能力体系。',
hero_primary: '预约 AI 场景诊断', hero_secondary: '查看解决方案', proof_data: '7 年数据技术积累', proof_private: '支持私有化部署', proof_delivery: '成都 / 海口双城交付',
@@ -1221,7 +1289,7 @@
en: {
title: 'Hainan Roncarve Technology - Data Foundation · AI Delivery', skip: 'Skip to main content', brand: 'Roncarve Tech', nav_label: 'Main navigation', home_label: 'Roncarve Tech home', lang_label: 'Language selector', menu_open: 'Open navigation menu', menu_close: 'Close navigation menu',
theme_label: 'Appearance selector', theme_system: 'Follow System', theme_light: 'Light appearance', theme_dark: 'Dark appearance', theme_system_short: 'Auto', theme_light_short: 'Light', theme_dark_short: 'Dark', language_zh: '简体中文', language_en: 'English',
- nav_about: 'Capabilities', nav_services: 'Solutions', nav_advantages: 'Advantages', nav_offices: 'Delivery', nav_contact: 'Book a Call',
+ nav_products: 'Products', product_list_label: 'Product list', product_status: 'Coming Soon', product_harness_name: 'Roncarve Harness', product_harness_desc: 'A local client for browser-based local projects and a shared workspace for team collaboration, with precise plugin discovery and hot swapping.', nav_about: 'Capabilities', nav_services: 'Solutions', nav_advantages: 'Advantages', nav_offices: 'Delivery', nav_contact: 'Book a Call',
hero_eyebrow: 'Hainan Roncarve Technology Co., Ltd. · AI + DATA INFRASTRUCTURE', hero_line1: 'Turn enterprise data', hero_line2: 'into AI productivity',
hero_desc: 'From data governance and model optimization to private deployment, Roncarve builds practical, sustainable and controllable enterprise AI systems.',
hero_primary: 'Book an AI Discovery Call', hero_secondary: 'Explore Solutions', proof_data: '7 years in data technology', proof_private: 'Private deployment ready', proof_delivery: 'Chengdu / Haikou delivery',
@@ -1257,6 +1325,8 @@
const navbar = document.getElementById('navbar');
const menuBtn = document.getElementById('menuBtn');
const navLinks = document.getElementById('navLinks');
+ const productNav = document.getElementById('productNav');
+ const productTrigger = document.getElementById('productTrigger');
const langSwitch = document.getElementById('langSwitch');
const themeSwitch = document.getElementById('themeSwitch');
const themeQuery = window.matchMedia('(prefers-color-scheme: dark)');
@@ -1336,6 +1406,12 @@
menuBtn.setAttribute('aria-expanded', 'false');
menuBtn.setAttribute('aria-label', i18n[currentLanguage].menu_open);
document.body.classList.remove('menu-open');
+ closeProductMenu();
+ }
+
+ function closeProductMenu() {
+ productNav.classList.remove('open');
+ productTrigger.setAttribute('aria-expanded', 'false');
}
function closePreferenceMenus(except) {
@@ -1351,12 +1427,24 @@
const trigger = menu.querySelector('.preference-trigger');
trigger.addEventListener('click', function () {
const open = !menu.classList.contains('open');
+ closeProductMenu();
closePreferenceMenus(menu);
menu.classList.toggle('open', open);
trigger.setAttribute('aria-expanded', String(open));
});
});
+ productTrigger.addEventListener('click', function () {
+ const open = !productNav.classList.contains('open');
+ closePreferenceMenus();
+ productNav.classList.toggle('open', open);
+ productTrigger.setAttribute('aria-expanded', String(open));
+ if (open && window.innerWidth > 860) {
+ const firstProduct = productNav.querySelector('[role="menuitem"]');
+ if (firstProduct) requestAnimationFrame(function () { firstProduct.focus(); });
+ }
+ });
+
langSwitch.addEventListener('click', function (event) {
const button = event.target.closest('[data-lang]');
if (!button) return;
@@ -1393,10 +1481,12 @@
if (event.key === 'Escape') {
closeMenu();
closePreferenceMenus();
+ closeProductMenu();
}
});
document.addEventListener('click', function (event) {
if (!event.target.closest('.preference-menu')) closePreferenceMenus();
+ if (!event.target.closest('.product-nav')) closeProductMenu();
});
window.addEventListener('scroll', function () { navbar.classList.toggle('scrolled', window.scrollY > 24); }, { passive: true });
diff --git a/nginx.conf b/nginx.conf
index fba2069..d9b5a91 100644
--- a/nginx.conf
+++ b/nginx.conf
@@ -5,10 +5,19 @@ server {
root /usr/share/nginx/html;
index index.html;
+ location = /products/roncarve-harness {
+ return 301 /products/roncarve-harness/;
+ }
+
location / {
try_files $uri $uri/ /index.html;
}
+ location ~* \.html$ {
+ add_header Cache-Control "no-cache";
+ try_files $uri =404;
+ }
+
location ~* \.(?:css|js|png|jpg|jpeg|gif|svg|webp|ico|woff2?)$ {
expires 7d;
add_header Cache-Control "public, max-age=604800, immutable";
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..91f8516
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,1115 @@
+{
+ "name": "roncarve-official-web",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "roncarve-official-web",
+ "version": "1.0.0",
+ "dependencies": {
+ "@phosphor-icons/react": "^2.1.10",
+ "@react-three/fiber": "^9.7.0",
+ "gsap": "^3.15.0",
+ "react": "^19.2.8",
+ "react-dom": "^19.2.8",
+ "three": "^0.185.1"
+ },
+ "devDependencies": {
+ "vite": "^8.2.2"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz",
+ "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@oxc-project/types": {
+ "version": "0.146.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.146.0.tgz",
+ "integrity": "sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
+ "node_modules/@phosphor-icons/react": {
+ "version": "2.1.10",
+ "resolved": "https://registry.npmjs.org/@phosphor-icons/react/-/react-2.1.10.tgz",
+ "integrity": "sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8",
+ "react-dom": ">= 16.8"
+ }
+ },
+ "node_modules/@react-three/fiber": {
+ "version": "9.7.0",
+ "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.7.0.tgz",
+ "integrity": "sha512-EWm9FwcaOZQu/ExFW5rggoCMM1NJet5YbxVxKaOE+KSncrjU0Wx7017qSyGFvupviK89nMYGCWU3BIK4dI1clw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.17.8",
+ "@types/webxr": "*",
+ "base64-js": "^1.5.1",
+ "buffer": "^6.0.3",
+ "its-fine": "^2.0.0",
+ "react-use-measure": "^2.1.7",
+ "scheduler": "^0.27.0",
+ "suspend-react": "^0.1.3",
+ "use-sync-external-store": "^1.4.0",
+ "zustand": "^5.0.3"
+ },
+ "peerDependencies": {
+ "expo": ">=43.0",
+ "expo-asset": ">=8.4",
+ "expo-file-system": ">=11.0",
+ "expo-gl": ">=11.0",
+ "react": ">=19 <19.3",
+ "react-dom": ">=19 <19.3",
+ "react-native": ">=0.78",
+ "three": ">=0.156"
+ },
+ "peerDependenciesMeta": {
+ "expo": {
+ "optional": true
+ },
+ "expo-asset": {
+ "optional": true
+ },
+ "expo-file-system": {
+ "optional": true
+ },
+ "expo-gl": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm-eabi": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.5.tgz",
+ "integrity": "sha512-DLe/i+l8ynIBY7XEQ191TeZvCoowIGa18R+dIV30GW7DiOtp74i/xX8hs8GUjW5ARV7VZuie3d6AumSmCwbeRA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.5.tgz",
+ "integrity": "sha512-zXcwKlQApYAOELHd8PwKDFkagYF9Wy4e0RJ+0qnzl9Pjnpj75TEG8ufv40p2J7kCEfwZAsNiuzRIyNNMWT38ig==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.5.tgz",
+ "integrity": "sha512-dK4QakI42nzWgJT5sm4y4y/O//D4OxM75/cH28RLV+nzIN9AY+YsbuUVrUTjlLjXR6vpyxFbSsbmNuJ6BP9sww==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.5.tgz",
+ "integrity": "sha512-fqSALaUu1Wjd1nK2uW2kJDWdLCc8lx1IcY+MTY26Aurfdx19anlzhqXOgCFbBFQnlFDTn4TC1/7Nz4Bl2mLP3A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.5.tgz",
+ "integrity": "sha512-/vCnNxlkxs9tKxNDcyWUePpJ/PgTzxIaVhoM5SmG8UV+GR/IcPam4VYxi7GIMo7PSDuNqlJqvprqii9NqqVCMw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.5.tgz",
+ "integrity": "sha512-abk0NLA519LxRCszmbE0jYKuQ9YPocOXTiOXOo6Yr+YAT95VH+PtqYAjOJvGKt3viEd/x4qzabAlwd5bHOOARg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.5.tgz",
+ "integrity": "sha512-Y7eALiJ8lr0M2HH103Js+g7V34wf6snlpZLAsHI90uLhr3PVlNsbFVAXJC9d/V6BnPyKtpSwI+NcB/RLxsQxuA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.5.tgz",
+ "integrity": "sha512-xMvZgnbZg4YVnR/AX2b3oOPDTFYJvUVaJg5FedA/LuvexAtXibZQej4cnTkw3rjsJ/ggUROB64TdtETiim+FYA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.5.tgz",
+ "integrity": "sha512-GRjeqTUDHTo5GwntsLaAMcBahG3nlpjftXWZLN73HiYQlhwEowvarFgQnRnQZtIp4keXX7quXFbG38uPZBa2EA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.5.tgz",
+ "integrity": "sha512-vLNTR45F2Uwc8AufkNXPmB4VliaXs+FvcheEogIzOXzO4l+LzieXF5A/TWxLy5HtqpsRCHUfd0lPVrrdgXdLHQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.5.tgz",
+ "integrity": "sha512-Mgj59/HTuYeK9Gz2MA+mBWKnHsAgkBSec15ZMb1st3oIfFbX7gCjOae7GydHhzcyQi9Z/7M1QuN9bR3oFqF0jQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.5.tgz",
+ "integrity": "sha512-mY8AP0/ichsbhAxGnLa3d3+MwV0EfgrPND2bplI3Ym8T6R2pJ0N87bvrKVwNXmdy3jnr6eQBecdqx/HMknBmpA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.5.tgz",
+ "integrity": "sha512-8SLssA2oweAxyRgDp789ACfRb/3P+zNRJpzZxSizxF9m8NUDQ4+3xjo8ttjhVGGw6Qxb70oZiEtIjaKikCO7Yw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.5.tgz",
+ "integrity": "sha512-vGbruD5zquhoc8D9SViXgN2FBJtNdTyQ4DtG+SWiEGlJiAzoKcZ2xp+xuXCffhubVdt0NJlTZqkeRuERy7g8Cw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.5.tgz",
+ "integrity": "sha512-e/SXpgISz+IoqVcSSI0rx/d/he8zqLex+/rCWpnHpmVfmPIUjag9H6P7zotf0gJHwPUhQxZ/mF8tr6acebT9yw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
+ "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.18",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz",
+ "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-reconciler": {
+ "version": "0.28.9",
+ "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz",
+ "integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/webxr": {
+ "version": "0.5.24",
+ "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz",
+ "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==",
+ "license": "MIT"
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/gsap": {
+ "version": "3.15.0",
+ "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.15.0.tgz",
+ "integrity": "sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==",
+ "license": "Standard 'no charge' license: https://gsap.com/standard-license."
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/its-fine": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-2.0.0.tgz",
+ "integrity": "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/react-reconciler": "^0.28.9"
+ },
+ "peerDependencies": {
+ "react": "^19.0.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
+ "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.33.0",
+ "lightningcss-darwin-arm64": "1.33.0",
+ "lightningcss-darwin-x64": "1.33.0",
+ "lightningcss-freebsd-x64": "1.33.0",
+ "lightningcss-linux-arm-gnueabihf": "1.33.0",
+ "lightningcss-linux-arm64-gnu": "1.33.0",
+ "lightningcss-linux-arm64-musl": "1.33.0",
+ "lightningcss-linux-x64-gnu": "1.33.0",
+ "lightningcss-linux-x64-musl": "1.33.0",
+ "lightningcss-win32-arm64-msvc": "1.33.0",
+ "lightningcss-win32-x64-msvc": "1.33.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
+ "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz",
+ "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz",
+ "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz",
+ "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz",
+ "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz",
+ "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz",
+ "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz",
+ "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz",
+ "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz",
+ "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz",
+ "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.18",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
+ "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.26",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
+ "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.17",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.8",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
+ "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.8",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
+ "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.8"
+ }
+ },
+ "node_modules/react-use-measure": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz",
+ "integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">=16.13",
+ "react-dom": ">=16.13"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/rolldown": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.5.tgz",
+ "integrity": "sha512-VD2IE5PUG4Oj8zz2VGykiYd5wbnjdIiSsNQb8Qu5B+noEp+A78mu2iVvpp27g8es14Tk9rofNs5Tku9iQCS4fA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.146.0",
+ "@rolldown/pluginutils": "^1.0.0"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm-eabi": "1.2.5",
+ "@rolldown/binding-android-arm64": "1.2.5",
+ "@rolldown/binding-darwin-arm64": "1.2.5",
+ "@rolldown/binding-darwin-x64": "1.2.5",
+ "@rolldown/binding-freebsd-x64": "1.2.5",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.2.5",
+ "@rolldown/binding-linux-arm64-gnu": "1.2.5",
+ "@rolldown/binding-linux-arm64-musl": "1.2.5",
+ "@rolldown/binding-linux-ppc64-gnu": "1.2.5",
+ "@rolldown/binding-linux-s390x-gnu": "1.2.5",
+ "@rolldown/binding-linux-x64-gnu": "1.2.5",
+ "@rolldown/binding-linux-x64-musl": "1.2.5",
+ "@rolldown/binding-openharmony-arm64": "1.2.5",
+ "@rolldown/binding-win32-arm64-msvc": "1.2.5",
+ "@rolldown/binding-win32-x64-msvc": "1.2.5"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/suspend-react": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz",
+ "integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">=17.0"
+ }
+ },
+ "node_modules/three": {
+ "version": "0.185.1",
+ "resolved": "https://registry.npmjs.org/three/-/three-0.185.1.tgz",
+ "integrity": "sha512-5aojFCXKwnjBRZvUnt3WFfEcvUJgkN5LlijRFN95hMy8WVkG4I0QNcJE+OuWvuJ0bOdStrbfXn0pkd6/QyiAlg==",
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "8.2.2",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz",
+ "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lightningcss": "^1.33.0",
+ "picomatch": "^4.0.5",
+ "postcss": "^8.5.26",
+ "rolldown": "~1.2.4",
+ "tinyglobby": "^0.2.17"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.4.0 || ^0.5.0",
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/zustand": {
+ "version": "5.0.15",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.15.tgz",
+ "integrity": "sha512-MpSEjRiBkA9crSYeOUH32rJC7SVqAbm0Fqcqge/bUi2PPoLcBWKOsG+C8mevmpr8TwXHBVkChbbJiyvkE+i/3A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18.0.0",
+ "immer": ">=9.0.6",
+ "react": ">=18.0.0",
+ "use-sync-external-store": ">=1.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "use-sync-external-store": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..3d5dea9
--- /dev/null
+++ b/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "roncarve-official-web",
+ "version": "1.0.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build && node scripts/copy-static.mjs",
+ "preview": "vite preview --host 0.0.0.0"
+ },
+ "dependencies": {
+ "@phosphor-icons/react": "^2.1.10",
+ "@react-three/fiber": "^9.7.0",
+ "gsap": "^3.15.0",
+ "react": "^19.2.8",
+ "react-dom": "^19.2.8",
+ "three": "^0.185.1"
+ },
+ "devDependencies": {
+ "vite": "^8.2.2"
+ }
+}
diff --git a/products/roncarve-harness/index.html b/products/roncarve-harness/index.html
new file mode 100644
index 0000000..1d63d74
--- /dev/null
+++ b/products/roncarve-harness/index.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
荣刻驭境 Roncarve Harness|插件驱动的 Agent 运行环境 - 荣刻科技
+
+
+
+
+
+
+
diff --git a/scripts/copy-static.mjs b/scripts/copy-static.mjs
new file mode 100644
index 0000000..ea24d9c
--- /dev/null
+++ b/scripts/copy-static.mjs
@@ -0,0 +1,20 @@
+import { cp, copyFile, mkdir } from 'node:fs/promises';
+import { resolve } from 'node:path';
+
+const root = process.cwd();
+const output = resolve(root, 'dist');
+
+await mkdir(resolve(output, 'assets'), { recursive: true });
+await cp(resolve(root, 'assets'), resolve(output, 'assets'), {
+ recursive: true,
+ force: true
+});
+
+await Promise.all([
+ copyFile(resolve(root, 'robots.txt'), resolve(output, 'robots.txt')),
+ copyFile(resolve(root, 'sitemap.xml'), resolve(output, 'sitemap.xml')),
+ copyFile(
+ resolve(root, 'baidu_verify_codeva-Jnq0QS7SFC.html'),
+ resolve(output, 'baidu_verify_codeva-Jnq0QS7SFC.html')
+ )
+]);
diff --git a/sitemap.xml b/sitemap.xml
index 6c076b4..161eb58 100644
--- a/sitemap.xml
+++ b/sitemap.xml
@@ -2,8 +2,14 @@
https://www.roncarve.com/
- 2026-08-16
+ 2026-08-22
monthly
1.0
+
+ https://www.roncarve.com/products/roncarve-harness/
+ 2026-08-22
+ weekly
+ 0.9
+
diff --git a/src/harness/HarnessProductPage.jsx b/src/harness/HarnessProductPage.jsx
new file mode 100644
index 0000000..211d7f6
--- /dev/null
+++ b/src/harness/HarnessProductPage.jsx
@@ -0,0 +1,471 @@
+import {
+ ArrowRight,
+ Brain,
+ Check,
+ Code,
+ Database,
+ Globe,
+ Lightning,
+ List,
+ MagnifyingGlass,
+ Plug,
+ ShieldCheck,
+ Stack,
+ UsersThree,
+ X
+} from '@phosphor-icons/react';
+import gsap from 'gsap';
+import { ScrollTrigger } from 'gsap/ScrollTrigger';
+import { useLayoutEffect, useMemo, useRef, useState } from 'react';
+import { ParticleStage } from './ParticleStage';
+
+gsap.registerPlugin(ScrollTrigger);
+
+const copy = {
+ zh: {
+ brand: '荣刻科技',
+ navProduct: '荣刻驭境',
+ navEngine: '插件引擎',
+ navEditions: '版本能力',
+ navWorkflow: '使用路径',
+ email: '邮件咨询',
+ language: 'EN',
+ menu: '打开导航',
+ close: '关闭导航',
+ eyebrow: 'RONCARVE HARNESS / 荣刻驭境',
+ status: '即将开放',
+ title: '让 Agent 驾驭\n每一种能力',
+ summary: '面向大语言模型的可组合 Harness,以定制插件引擎实现精准检索与高效热插拔;个人版在浏览器中操作本地项目,平台版以共享工作空间连接团队协作。',
+ primary: '邮件咨询',
+ secondary: '查看版本能力',
+ proof: ['精准插件检索', '高效热插拔', '个人 / 平台双版本'],
+ formula: 'PLUGIN IS ALL YOU NEED',
+ valueTitle: '插件构成能力,\nHarness 组织运行',
+ valueSummary: '把插件发现、装配、替换和协作放进同一套运行环境,让 Agent 能力持续扩展,而不必反复重建。',
+ valueCards: [
+ ['海量插件精准检索', '按任务语义、能力类型、兼容性与工作空间策略定位合适插件。'],
+ ['更高效率热插拔', '在明确依赖与生命周期的前提下快速加载、卸载、替换和组合。'],
+ ['本地项目到共享协作', '个人版通过本地客户端操作本地项目;平台版让所有成员进入同一个共享工作空间协同。']
+ ],
+ scroll: '滚动展开空间',
+ engineKicker: 'CUSTOM PLUGIN ENGINE',
+ engineTitle: '为插件规模化而优化的引擎',
+ engineSummary: '从定制插件数据库中发现能力,在运行时完成装配;个人版作用于本地项目,平台版把有效组合沉淀为团队共享能力。',
+ demoTitle: '插件检索演示',
+ demoStatus: '交互演示',
+ searchPlaceholder: '搜索能力、插件或场景',
+ categories: ['全部', '知识', '数据', '开发', '网络'],
+ candidates: '个候选插件',
+ compose: '装配',
+ composed: '已装配',
+ runtime: '当前运行组合',
+ hotSwap: '点击任一插件可模拟热插拔',
+ engineSteps: [
+ ['01', '发现', '连接定制插件数据库,建立能力、版本、依赖与兼容性索引。'],
+ ['02', '匹配', '结合任务语义、运行环境与工作空间策略完成候选筛选。'],
+ ['03', '装配', '按需加载、卸载和替换插件,降低重建 Agent 的成本。'],
+ ['04', '观测', '记录插件状态、调用与异常,让有效组合可以复用。']
+ ],
+ editionsKicker: 'PERSONAL / PLATFORM',
+ editionsTitle: '从本地项目客户端,\n到团队共享工作空间',
+ editionsSummary: '同一套插件引擎,两种工作形态:个人版连接本地项目,平台版让所有成员在共享工作空间中协同。产品当前处于即将开放阶段。',
+ personal: '个人版',
+ personalEn: 'PERSONAL',
+ personalDesc: '作为本地客户端运行,并通过浏览器界面操作当前设备上的本地项目。',
+ personalFeatures: ['本地客户端部署', '浏览器操作本地项目', '本地文件与运行环境保持在当前设备', '完整插件检索与热插拔', '本地项目预设与运行记录'],
+ platform: '平台版',
+ platformEn: 'PLATFORM',
+ platformDesc: '面向团队的共享工作空间,让所有成员围绕同一项目、插件和运行上下文协同。',
+ platformFeatures: ['团队共享工作空间', '成员与角色协同', '共享项目、插件组合与配置', '组织级插件目录与版本管理', '共享活动记录与运行上下文'],
+ askPersonal: '邮件咨询个人版',
+ askPlatform: '邮件咨询平台版',
+ workflowKicker: 'FROM TASK TO TEAM CAPABILITY',
+ workflowTitle: '从一个任务,生长为团队能力',
+ workflowSummary: '一次有效的插件组合,不应止步于一次会话。荣刻驭境让它成为可以保存、复用和协作的工作资产。',
+ workflowSteps: [
+ ['01', '描述目标', '从具体任务、数据边界和运行环境开始。'],
+ ['02', '检索插件', '从定制数据库中找到符合语义与策略的能力。'],
+ ['03', '运行装配', '在运行中加载、替换并验证插件组合。'],
+ ['04', '共享协同', '个人版保存到本地项目;平台版同步到团队共享工作空间。']
+ ],
+ ctaKicker: 'RONCARVE HARNESS / COMING SOON',
+ ctaTitle: '让下一套 Agent 能力,\n从可组合开始',
+ ctaSummary: '告诉我们你的本地项目形态、团队协作方式与部署环境,一起定义最合适的个人版或平台版方案。',
+ ctaButton: '发送咨询邮件',
+ originLine: '基于开源 DeepSeek Harness 二次开发的独立产品。',
+ copyright: '海南荣刻科技有限公司',
+ footerSignal: '数据驱动 · AI 赋能 · 安全交付',
+ policeAlt: '公安备案图标'
+ },
+ en: {
+ brand: 'Roncarve Tech',
+ navProduct: 'Harness',
+ navEngine: 'Plugin Engine',
+ navEditions: 'Editions',
+ navWorkflow: 'Workflow',
+ email: 'Email Us',
+ language: '中文',
+ menu: 'Open navigation',
+ close: 'Close navigation',
+ eyebrow: 'RONCARVE HARNESS',
+ status: 'COMING SOON',
+ title: 'Harness every capability\nfor your Agent',
+ summary: 'A composable Harness for LLMs with precise plugin discovery and efficient hot swapping. Personal is a local client for browser-based work on local projects; Platform gives the whole team one shared workspace.',
+ primary: 'Email Us',
+ secondary: 'Compare Editions',
+ proof: ['Precise plugin discovery', 'Efficient hot swapping', 'Personal / Platform'],
+ formula: 'PLUGIN IS ALL YOU NEED',
+ valueTitle: 'Plugins provide capability.\nHarness organizes the work.',
+ valueSummary: 'Bring discovery, composition, replacement, and collaboration into one runtime so Agents can keep evolving without being rebuilt.',
+ valueCards: [
+ ['Precise discovery at scale', 'Find the right plugin by task, capability, compatibility, and workspace policy.'],
+ ['Efficient hot swapping', 'Load, unload, replace, and compose plugins with explicit lifecycle and dependency rules.'],
+ ['Local projects to shared collaboration', 'Personal works with local projects through a local client; Platform brings every member into one shared workspace.']
+ ],
+ scroll: 'Scroll to expand',
+ engineKicker: 'CUSTOM PLUGIN ENGINE',
+ engineTitle: 'An engine optimized for plugin scale',
+ engineSummary: 'Discover and compose capabilities at runtime. Personal applies them to local projects, while Platform turns effective combinations into shared team capabilities.',
+ demoTitle: 'Plugin discovery demo',
+ demoStatus: 'Interactive demo',
+ searchPlaceholder: 'Search capabilities, plugins, or use cases',
+ categories: ['All', 'Knowledge', 'Data', 'Developer', 'Web'],
+ candidates: 'candidate plugins',
+ compose: 'Compose',
+ composed: 'Composed',
+ runtime: 'Active runtime',
+ hotSwap: 'Select a plugin to simulate hot swapping',
+ engineSteps: [
+ ['01', 'Discover', 'Index capabilities, versions, dependencies, and compatibility.'],
+ ['02', 'Match', 'Filter candidates by intent, environment, and workspace policy.'],
+ ['03', 'Compose', 'Load, unload, or replace plugins without rebuilding the Agent.'],
+ ['04', 'Observe', 'Record plugin state, calls, and errors for repeatable operation.']
+ ],
+ editionsKicker: 'PERSONAL / PLATFORM',
+ editionsTitle: 'From a local project client\nto a shared team workspace',
+ editionsSummary: 'One plugin engine, two operating models: Personal connects to local projects; Platform brings every member into one shared workspace. Roncarve Harness is coming soon.',
+ personal: 'Personal',
+ personalEn: 'PERSONAL',
+ personalDesc: 'Runs as a local client with a browser interface for working on projects stored on the current device.',
+ personalFeatures: ['Local client deployment', 'Browser-based control of local projects', 'Local files and runtime stay on the current device', 'Full discovery and hot swapping', 'Local project presets and run history'],
+ platform: 'Platform',
+ platformEn: 'PLATFORM',
+ platformDesc: 'A shared workspace where every team member collaborates around the same projects, plugins, and runtime context.',
+ platformFeatures: ['Shared team workspace', 'Member and role collaboration', 'Shared projects, plugin sets, and configs', 'Organization plugin catalog and version management', 'Shared activity and runtime context'],
+ askPersonal: 'Ask about Personal',
+ askPlatform: 'Ask about Platform',
+ workflowKicker: 'FROM TASK TO TEAM CAPABILITY',
+ workflowTitle: 'Grow one task into\na team capability',
+ workflowSummary: 'A successful plugin composition should outlive a single session. Preserve it as a reusable and collaborative working asset.',
+ workflowSteps: [
+ ['01', 'Describe the goal', 'Start with a real task, data boundary, and runtime.'],
+ ['02', 'Discover plugins', 'Find capabilities that fit intent and policy.'],
+ ['03', 'Compose at runtime', 'Load, replace, and validate a working plugin set.'],
+ ['04', 'Share and collaborate', 'Save to a local project in Personal, or sync into the shared team workspace in Platform.']
+ ],
+ ctaKicker: 'RONCARVE HARNESS / COMING SOON',
+ ctaTitle: 'Start your next Agent\nwith composability',
+ ctaSummary: 'Tell us about your local project model, collaboration workflow, and deployment environment. We will help shape the right Personal or Platform setup.',
+ ctaButton: 'Send an email',
+ originLine: 'An independently developed product based on the open-source DeepSeek Harness.',
+ copyright: 'Hainan Roncarve Technology Co., Ltd',
+ footerSignal: 'DATA-DRIVEN · AI-POWERED · SECURE DELIVERY',
+ policeAlt: 'Public security filing icon'
+ }
+};
+
+const pluginCatalog = [
+ { id: 'knowledge', category: 1, icon: Brain, zh: ['知识检索增强', '连接知识库与语义索引,为 Agent 提供可追溯的上下文。', '知识'], en: ['Knowledge Retrieval', 'Connect knowledge bases and semantic indexes with traceable context.', 'Knowledge'] },
+ { id: 'database', category: 2, icon: Database, zh: ['数据桥接器', '以受控连接访问结构化数据和业务数据源。', '数据'], en: ['Data Bridge', 'Access structured and business data through governed connections.', 'Data'] },
+ { id: 'code', category: 3, icon: Code, zh: ['代码执行环境', '在隔离环境中组合代码、命令与文件操作。', '开发'], en: ['Code Runtime', 'Compose code, commands, and file operations in an isolated runtime.', 'Developer'] },
+ { id: 'web', category: 4, icon: Globe, zh: ['网页研究工具', '检索、读取并整理可验证的公开网页信息。', '网络'], en: ['Web Research', 'Search, read, and organize verifiable public web information.', 'Web'] },
+ { id: 'workflow', category: 3, icon: Stack, zh: ['工作流编排', '把多轮工具调用组合为稳定、可复用的执行路径。', '开发'], en: ['Workflow Composer', 'Turn multi-step tool calls into reusable execution paths.', 'Developer'] },
+ { id: 'policy', category: 2, icon: ShieldCheck, zh: ['策略边界', '按工作空间控制插件可见范围与运行权限。', '数据'], en: ['Policy Boundary', 'Control plugin visibility and runtime permissions by workspace.', 'Data'] }
+];
+
+function emailHref(language, edition = '') {
+ const subject = language === 'zh'
+ ? `[荣刻驭境] ${edition ? `${edition}咨询` : '产品咨询'}`
+ : `[Roncarve Harness] ${edition ? `${edition} inquiry` : 'Product inquiry'}`;
+ const body = language === 'zh'
+ ? '您好,我想了解荣刻驭境。\n\n关注版本:\n使用场景:\n部署环境:\n团队规模:\n联系人:'
+ : 'Hello, I would like to learn more about Roncarve Harness.\n\nEdition:\nUse case:\nDeployment:\nTeam size:\nContact:';
+ return `mailto:contact@roncarve.com?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
+}
+
+function ProductNav({ language, setLanguage }) {
+ const t = copy[language];
+ const [open, setOpen] = useState(false);
+ const close = () => setOpen(false);
+
+ return (
+
+ );
+}
+
+function PluginDemo({ language }) {
+ const t = copy[language];
+ const [query, setQuery] = useState('');
+ const [category, setCategory] = useState(0);
+ const [activePlugin, setActivePlugin] = useState('knowledge');
+ const filtered = pluginCatalog.filter((plugin) => {
+ const translated = plugin[language];
+ const matchesCategory = category === 0 || plugin.category === category;
+ const haystack = translated.join(' ').toLowerCase();
+ return matchesCategory && haystack.includes(query.trim().toLowerCase());
+ });
+ const active = pluginCatalog.find((plugin) => plugin.id === activePlugin);
+
+ return (
+
+
+
{t.demoStatus}
+
RONCARVE PLUGIN INDEX
+
+
+
+
+ {t.categories.map((label, index) => (
+
+ ))}
+
+
+
{filtered.length} {t.candidates}
+
+ {filtered.map((plugin) => {
+ const [name, description, tag] = plugin[language];
+ const Icon = plugin.icon;
+ const isActive = activePlugin === plugin.id;
+ return (
+
+
+
+
+
+ );
+ })}
+ {filtered.length === 0 &&
{language === 'zh' ? '没有匹配结果,请尝试其他关键词。' : 'No matches. Try another keyword.'}
}
+
+
+
{t.runtime}{active?.[language][0]}
+
{t.hotSwap}
+
+
+ );
+}
+
+function EditionCard({ title, code, description, features, href, featured, cta }) {
+ return (
+
+ {code}{featured && }
+ {title}
+ {description}
+ {features.map((feature) => - {feature}
)}
+ {cta}
+
+ );
+}
+
+export function HarnessProductPage() {
+ const [language, setLanguageState] = useState(() => {
+ try { return localStorage.getItem('site_lang') === 'en' ? 'en' : 'zh'; } catch { return 'zh'; }
+ });
+ const storyRef = useRef(null);
+ const pageRef = useRef(null);
+ const progressRef = useRef(0);
+ const t = copy[language];
+
+ const setLanguage = (next) => {
+ setLanguageState(next);
+ try { localStorage.setItem('site_lang', next); } catch { /* storage may be disabled */ }
+ };
+
+ const meta = useMemo(() => language === 'zh' ? {
+ title: '荣刻驭境 Roncarve Harness|插件驱动的 Agent 运行环境 - 荣刻科技',
+ description: '荣刻驭境是一套面向大语言模型与 Agent 的可组合 Harness,通过定制插件引擎提供精准插件检索、高效热插拔,支持浏览器操作本地项目的个人客户端与团队共享工作空间。'
+ } : {
+ title: 'Roncarve Harness | A plugin-driven runtime for Agents',
+ description: 'A composable Harness for LLMs and Agents with precise plugin discovery, efficient hot swapping, a local client for browser-based local projects, and a shared team workspace.'
+ }, [language]);
+
+ useLayoutEffect(() => {
+ document.documentElement.lang = language === 'zh' ? 'zh-CN' : 'en';
+ document.title = meta.title;
+ document.querySelector('meta[name="description"]')?.setAttribute('content', meta.description);
+ }, [language, meta]);
+
+ useLayoutEffect(() => {
+ const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
+ const context = gsap.context(() => {
+ if (reduced) {
+ progressRef.current = 0;
+ gsap.set(['.hero-copy', '.value-copy'], { opacity: 1, y: 0 });
+ return;
+ }
+
+ const timeline = gsap.timeline({
+ scrollTrigger: {
+ trigger: storyRef.current,
+ start: 'top top',
+ end: 'bottom bottom',
+ scrub: 0.85,
+ onUpdate: (self) => { progressRef.current = self.progress; }
+ }
+ });
+ timeline
+ .to('.hero-copy', { opacity: 0, y: -92, duration: 0.2, ease: 'none' }, 0.11)
+ .fromTo('.value-copy', { opacity: 0, y: 80 }, { opacity: 1, y: 0, duration: 0.2, ease: 'none' }, 0.26)
+ .fromTo('.value-card', { opacity: 0, y: 44 }, { opacity: 1, y: 0, stagger: 0.035, duration: 0.15, ease: 'none' }, 0.39)
+ .to('.story-scroll-cue', { opacity: 0, duration: 0.08 }, 0.05)
+ .to('.value-copy', { opacity: 0.82, y: -18, duration: 0.13, ease: 'none' }, 0.84);
+
+ gsap.utils.toArray('.reveal-section').forEach((section) => {
+ gsap.fromTo(section, { opacity: 0, y: 52 }, {
+ opacity: 1,
+ y: 0,
+ duration: 0.85,
+ ease: 'power3.out',
+ scrollTrigger: { trigger: section, start: 'top 82%', once: true }
+ });
+ });
+ }, pageRef);
+
+ return () => context.revert();
+ }, [language]);
+
+ return (
+
+
{language === 'zh' ? '跳到主要内容' : 'Skip to main content'}
+
+
+
+
+
+
+
+
+
+
{t.eyebrow}{t.status}
+
{t.title.split('\n').map((line) => {line})}
+
{t.summary}
+
+
{t.proof.map((item) => {item})}
+
+
+
{t.formula}
+
{t.valueTitle.split('\n').map((line) => {line})}
+
{t.valueSummary}
+
+ {t.valueCards.map(([title, description], index) => {
+ const Icon = [MagnifyingGlass, Plug, UsersThree][index];
+ return
0{index + 1}{title}
{description}
;
+ })}
+
+
+
+
{t.scroll}
+
+
+
+
+
+
+
+
+
+
+ {t.engineSteps.map(([number, title, description]) => (
+
{number}
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+ {t.workflowSteps.map(([number, title, description], index) => (
+
{number}{title}
{description}
+ ))}
+
+
+
+
+
+
+
+
{t.ctaKicker}
+
{t.ctaTitle.split('\n').map((line) => {line})}
+
{t.ctaSummary}
+
{t.ctaButton}
+
contact@roncarve.com
+
+
+
+
+
+ );
+}
diff --git a/src/harness/ParticleStage.jsx b/src/harness/ParticleStage.jsx
new file mode 100644
index 0000000..e443cc7
--- /dev/null
+++ b/src/harness/ParticleStage.jsx
@@ -0,0 +1,402 @@
+import { Canvas, useFrame } from '@react-three/fiber';
+import gsap from 'gsap';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import * as THREE from 'three';
+
+const vertexShader = `
+ precision highp float;
+
+ attribute vec3 aLogoPosition;
+ attribute vec3 aSpreadPosition;
+ attribute vec3 aIntroPosition;
+ attribute float aIntroDelay;
+ attribute float aIntroBend;
+ attribute float aSeed;
+ attribute float aScale;
+ attribute float aColorMix;
+
+ uniform float uTime;
+ uniform float uScroll;
+ uniform float uIntro;
+ uniform float uGather;
+ uniform float uPixelRatio;
+ uniform float uPointerActive;
+ uniform float uAspect;
+ uniform vec2 uPointer;
+
+ varying float vAlpha;
+ varying float vBrightness;
+ varying float vColorMix;
+
+ mat3 rotateX(float angle) {
+ float s = sin(angle);
+ float c = cos(angle);
+ return mat3(1.0, 0.0, 0.0, 0.0, c, -s, 0.0, s, c);
+ }
+
+ mat3 rotateY(float angle) {
+ float s = sin(angle);
+ float c = cos(angle);
+ return mat3(c, 0.0, s, 0.0, 1.0, 0.0, -s, 0.0, c);
+ }
+
+ mat3 rotateZ(float angle) {
+ float s = sin(angle);
+ float c = cos(angle);
+ return mat3(c, -s, 0.0, s, c, 0.0, 0.0, 0.0, 1.0);
+ }
+
+ void main() {
+ float expansion = smoothstep(0.055, 0.72, uScroll);
+ float rotationStrength = smoothstep(0.09, 0.48, uScroll);
+ float settle = smoothstep(0.74, 1.0, uScroll);
+ float introLinear = clamp((uIntro - aIntroDelay) / 0.38, 0.0, 1.0);
+ float introProgress = 1.0 - pow(1.0 - introLinear, 3.0);
+ vec2 introTravel = normalize(aLogoPosition.xy - aIntroPosition.xy + vec2(0.0001));
+ vec2 introTangent = vec2(-introTravel.y, introTravel.x);
+ float introArc = sin(introLinear * 3.14159265) * aIntroBend * (1.0 - introProgress * 0.32);
+ vec3 introPosition = mix(aIntroPosition, aLogoPosition, introProgress);
+ introPosition.xy += introTangent * introArc;
+ introPosition.z += sin(introLinear * 3.14159265) * abs(aIntroBend) * 0.68;
+ float idleStrength = (1.0 - smoothstep(0.045, 0.24, uScroll)) * introProgress;
+
+ vec3 position = mix(introPosition, aSpreadPosition, expansion * introProgress);
+ vec2 logoRadial = normalize(aLogoPosition.xy + vec2(0.0001));
+ vec2 logoTangent = vec2(-logoRadial.y, logoRadial.x);
+ float idleBreath = sin(uTime * 0.62) * 0.011;
+ float idleFloat = sin(uTime * 0.78 + aSeed * 21.0) * 0.032;
+ float idleSway = cos(uTime * 0.46 + aSeed * 13.0) * 0.016;
+ position.xy *= 1.0 + idleBreath * idleStrength;
+ position.xy += (logoRadial * idleFloat + logoTangent * idleSway) * idleStrength;
+ position.z += sin(uTime * 0.58 + aSeed * 17.0) * 0.11 * idleStrength;
+ position = rotateZ(sin(uTime * 0.28) * 0.007 * idleStrength) * position;
+
+ float drift = sin(uTime * 0.42 + aSeed * 18.0) * 0.12 * rotationStrength;
+ position += normalize(vec3(position.xy + 0.001, 0.35)) * drift;
+
+ float spinY = (uTime * 0.085 + uScroll * 1.18) * rotationStrength;
+ float spinX = (0.16 + sin(uTime * 0.14) * 0.08) * rotationStrength;
+ float spinZ = sin(uTime * 0.11) * 0.08 * rotationStrength;
+ position = rotateY(spinY) * rotateX(spinX) * rotateZ(spinZ) * position;
+
+ position.xy *= mix(1.0, 0.82, settle);
+ position.z -= settle * 0.85;
+ position = mix(position, aLogoPosition, uGather);
+
+ vec4 viewPosition = modelViewMatrix * vec4(position, 1.0);
+ vec4 clipPosition = projectionMatrix * viewPosition;
+ vec2 particleNdc = clipPosition.xy / max(clipPosition.w, 0.0001);
+ vec2 pointerDelta = particleNdc - uPointer;
+ vec2 aspectPointerDelta = vec2(pointerDelta.x * uAspect, pointerDelta.y);
+ float pointerDistance = length(aspectPointerDelta);
+ float repel = smoothstep(0.3, 0.0, pointerDistance) * uPointerActive * (1.0 - uGather);
+ vec2 repelDirection = normalize(aspectPointerDelta + vec2(0.0001));
+ vec2 ndcDirection = vec2(repelDirection.x / max(uAspect, 0.0001), repelDirection.y);
+ clipPosition.xy += ndcDirection * repel * 0.16 * clipPosition.w;
+
+ float nearFactor = clamp((position.z + 4.5) / 9.0, 0.0, 1.0);
+ float perspectiveSize = 10.0 / max(3.0, -viewPosition.z);
+ gl_PointSize = aScale * mix(1.75, 4.35, nearFactor) * uPixelRatio * perspectiveSize * (1.0 + repel * 0.12);
+ gl_Position = clipPosition;
+
+ float contentFade = 1.0 - smoothstep(0.22, 0.48, uScroll) * 0.76;
+ float idleShimmer = 1.0 + sin(uTime * 0.92 + aSeed * 29.0) * 0.07 * idleStrength;
+ float introFade = smoothstep(0.0, 0.2, introLinear);
+ vAlpha = mix(0.28, 0.78, nearFactor) * contentFade * (1.0 - settle * 0.18) * idleShimmer * introFade;
+ vBrightness = mix(0.46, 1.0, nearFactor) * (1.0 + sin(uTime * 0.54 + aSeed * 9.0) * 0.045 * idleStrength);
+ vColorMix = aColorMix;
+ }
+`;
+
+const fragmentShader = `
+ precision highp float;
+
+ varying float vAlpha;
+ varying float vBrightness;
+ varying float vColorMix;
+
+ void main() {
+ vec2 point = gl_PointCoord - 0.5;
+ float distanceToCenter = length(point);
+ float body = 1.0 - smoothstep(0.28, 0.5, distanceToCenter);
+ float core = 1.0 - smoothstep(0.0, 0.22, distanceToCenter);
+ if (body <= 0.001) discard;
+
+ vec3 cyan = vec3(0.278, 0.898, 0.937);
+ vec3 blue = vec3(0.259, 0.647, 1.0);
+ vec3 violet = vec3(0.537, 0.510, 1.0);
+ vec3 color = mix(cyan, blue, smoothstep(0.18, 0.62, vColorMix));
+ color = mix(color, violet, smoothstep(0.66, 1.0, vColorMix));
+ color *= vBrightness + core * 0.22;
+
+ gl_FragColor = vec4(color, body * vAlpha);
+ }
+`;
+
+function randomUnit(seed) {
+ const value = Math.sin(seed * 12.9898 + 78.233) * 43758.5453;
+ return value - Math.floor(value);
+}
+
+function buildGeometry(image, count) {
+ const sampleSize = 256;
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d', { willReadFrequently: true });
+ canvas.width = sampleSize;
+ canvas.height = sampleSize;
+ context.clearRect(0, 0, sampleSize, sampleSize);
+ context.drawImage(image, 0, 0, sampleSize, sampleSize);
+ const pixels = context.getImageData(0, 0, sampleSize, sampleSize).data;
+ const candidates = [];
+
+ for (let y = 0; y < sampleSize; y += 1) {
+ for (let x = 0; x < sampleSize; x += 1) {
+ const alpha = pixels[(y * sampleSize + x) * 4 + 3];
+ if (alpha > 34) candidates.push([x, y, alpha / 255]);
+ }
+ }
+
+ const positions = new Float32Array(count * 3);
+ const logoPositions = new Float32Array(count * 3);
+ const spreadPositions = new Float32Array(count * 3);
+ const introPositions = new Float32Array(count * 3);
+ const introDelays = new Float32Array(count);
+ const introBends = new Float32Array(count);
+ const seeds = new Float32Array(count);
+ const scales = new Float32Array(count);
+ const colors = new Float32Array(count);
+
+ for (let index = 0; index < count; index += 1) {
+ const seed = randomUnit(index + 1);
+ const candidate = candidates[Math.floor(seed * candidates.length)] || [sampleSize / 2, sampleSize / 2, 1];
+ const jitterX = (randomUnit(index * 3.17 + 4) - 0.5) * 0.032;
+ const jitterY = (randomUnit(index * 5.23 + 7) - 0.5) * 0.032;
+ const x = ((candidate[0] / sampleSize) - 0.5) * 7.35 + jitterX;
+ const y = (0.5 - (candidate[1] / sampleSize)) * 7.35 + jitterY;
+ const radius = Math.hypot(x, y);
+ const angle = Math.atan2(y, x);
+ const orbit = randomUnit(index * 7.91 + 11);
+ const depth = (randomUnit(index * 11.41 + 3) - 0.5) * 7.8;
+ const spreadRadius = radius * (0.58 + orbit * 0.58) + orbit * 1.25;
+ const spreadAngle = angle + (orbit - 0.5) * 1.3;
+
+ const offset = index * 3;
+ positions[offset] = x;
+ positions[offset + 1] = y;
+ positions[offset + 2] = 0;
+ logoPositions[offset] = x;
+ logoPositions[offset + 1] = y;
+ logoPositions[offset + 2] = 0;
+ spreadPositions[offset] = Math.cos(spreadAngle) * spreadRadius + (randomUnit(index + 27) - 0.5) * 1.1;
+ spreadPositions[offset + 1] = Math.sin(spreadAngle) * spreadRadius * 0.72 + (randomUnit(index + 39) - 0.5) * 1.1;
+ spreadPositions[offset + 2] = depth;
+ const introLane = Math.floor(randomUnit(index * 13.77 + 17) * 8);
+ const introAngle = introLane * (Math.PI / 4) + (randomUnit(index * 4.61 + 29) - 0.5) * 0.52;
+ const introRadius = 0.78 + randomUnit(index * 9.43 + 47) * 0.5;
+ introPositions[offset] = Math.cos(introAngle) * 10.8 * introRadius + (randomUnit(index * 2.19 + 61) - 0.5) * 1.2;
+ introPositions[offset + 1] = Math.sin(introAngle) * 6.7 * introRadius + (randomUnit(index * 8.31 + 73) - 0.5) * 0.9;
+ introPositions[offset + 2] = (randomUnit(index * 6.07 + 83) - 0.5) * 10.5;
+ introDelays[index] = randomUnit(index * 5.37 + 97) * 0.6;
+ const bendSeed = randomUnit(index * 7.13 + 109);
+ introBends[index] = (bendSeed < 0.5 ? -1 : 1) * (0.34 + Math.abs(bendSeed - 0.5) * 1.8);
+ seeds[index] = seed;
+ scales[index] = 0.68 + randomUnit(index * 2.73 + 5) * 1.08;
+ colors[index] = Math.min(1, Math.max(0, orbit * 0.74 + candidate[2] * 0.24));
+ }
+
+ const geometry = new THREE.BufferGeometry();
+ geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
+ geometry.setAttribute('aLogoPosition', new THREE.BufferAttribute(logoPositions, 3));
+ geometry.setAttribute('aSpreadPosition', new THREE.BufferAttribute(spreadPositions, 3));
+ geometry.setAttribute('aIntroPosition', new THREE.BufferAttribute(introPositions, 3));
+ geometry.setAttribute('aIntroDelay', new THREE.BufferAttribute(introDelays, 1));
+ geometry.setAttribute('aIntroBend', new THREE.BufferAttribute(introBends, 1));
+ geometry.setAttribute('aSeed', new THREE.BufferAttribute(seeds, 1));
+ geometry.setAttribute('aScale', new THREE.BufferAttribute(scales, 1));
+ geometry.setAttribute('aColorMix', new THREE.BufferAttribute(colors, 1));
+ geometry.computeBoundingSphere();
+ return geometry;
+}
+
+function LogoParticles({ progressRef, introRef, gatherRef, pointerRef, pointerActiveRef, onReady }) {
+ const materialRef = useRef(null);
+ const [geometry, setGeometry] = useState(null);
+ const particleTransform = useMemo(() => {
+ if (window.innerWidth > 900) return { position: [1.92, 0, 0], scale: 0.86 };
+ if (window.innerWidth > 640) return { position: [1.45, 0, 0], scale: 0.5 };
+ return { position: [0, 0, 0], scale: 1 };
+ }, []);
+ const particleCount = useMemo(() => {
+ if (typeof window === 'undefined') return 18000;
+ if (window.innerWidth < 480) return 7000;
+ if (window.innerWidth < 768) return 10000;
+ if (window.innerWidth < 1200) return 18000;
+ return 24000;
+ }, []);
+
+ const uniforms = useMemo(() => ({
+ uTime: { value: 0 },
+ uScroll: { value: 0 },
+ uIntro: { value: 0 },
+ uGather: { value: 0 },
+ uPixelRatio: { value: Math.min(window.devicePixelRatio || 1, 1.5) },
+ uPointerActive: { value: 0 },
+ uAspect: { value: 1 },
+ uPointer: { value: new THREE.Vector2(20, 20) }
+ }), []);
+
+ useEffect(() => {
+ let disposed = false;
+ const image = new Image();
+ image.decoding = 'async';
+ image.src = '/assets/brand-logo-final.png';
+ image.onload = () => {
+ if (disposed) return;
+ const nextGeometry = buildGeometry(image, particleCount);
+ setGeometry(nextGeometry);
+ onReady?.();
+ };
+ return () => {
+ disposed = true;
+ };
+ }, [onReady, particleCount]);
+
+ useEffect(() => () => geometry?.dispose(), [geometry]);
+
+ useFrame(({ clock, size }) => {
+ if (!materialRef.current) return;
+ materialRef.current.uniforms.uTime.value = clock.getElapsedTime();
+ materialRef.current.uniforms.uScroll.value = progressRef.current;
+ materialRef.current.uniforms.uIntro.value = introRef.current.value;
+ materialRef.current.uniforms.uGather.value = gatherRef.current.value;
+ materialRef.current.uniforms.uAspect.value = size.width / Math.max(size.height, 1);
+ materialRef.current.uniforms.uPointerActive.value += (
+ Number(pointerActiveRef.current) - materialRef.current.uniforms.uPointerActive.value
+ ) * 0.18;
+ materialRef.current.uniforms.uPointer.value.copy(pointerRef.current);
+ });
+
+ if (!geometry) return null;
+
+ return (
+
+
+
+ );
+}
+
+function useReducedMotion() {
+ const [reduced, setReduced] = useState(false);
+ useEffect(() => {
+ const media = window.matchMedia('(prefers-reduced-motion: reduce)');
+ const update = () => setReduced(media.matches);
+ update();
+ media.addEventListener?.('change', update);
+ return () => media.removeEventListener?.('change', update);
+ }, []);
+ return reduced;
+}
+
+export function ParticleStage({ progressRef }) {
+ const stageRef = useRef(null);
+ const pointerRef = useRef(new THREE.Vector2(20, 20));
+ const pointerActiveRef = useRef(false);
+ const introRef = useRef({ value: 0 });
+ const gatherRef = useRef({ value: 0 });
+ const [ready, setReady] = useState(false);
+ const [active, setActive] = useState(true);
+ const [failed, setFailed] = useState(false);
+ const reducedMotion = useReducedMotion();
+ const handleReady = useCallback(() => setReady(true), []);
+
+ useEffect(() => {
+ if (!ready) return undefined;
+ gsap.killTweensOf(introRef.current);
+ if (reducedMotion) {
+ introRef.current.value = 1;
+ return undefined;
+ }
+ introRef.current.value = 0;
+ const tween = gsap.to(introRef.current, {
+ value: 1,
+ duration: 3.15,
+ ease: 'none'
+ });
+ return () => tween.kill();
+ }, [ready, reducedMotion]);
+
+ useEffect(() => {
+ if (!stageRef.current) return undefined;
+ const observer = new IntersectionObserver(([entry]) => setActive(entry.isIntersecting), { rootMargin: '120px 0px' });
+ observer.observe(stageRef.current);
+ return () => observer.disconnect();
+ }, []);
+
+ const handlePointerMove = (event) => {
+ if (reducedMotion || !window.matchMedia('(pointer: fine)').matches) return;
+ const bounds = stageRef.current.getBoundingClientRect();
+ const normalizedX = ((event.clientX - bounds.left) / bounds.width) * 2 - 1;
+ const normalizedY = 1 - ((event.clientY - bounds.top) / bounds.height) * 2;
+ pointerRef.current.set(normalizedX, normalizedY);
+ pointerActiveRef.current = true;
+ };
+
+ const handleGather = (event) => {
+ if (reducedMotion || event.button !== 0) return;
+ gsap.killTweensOf(introRef.current);
+ gsap.killTweensOf(gatherRef.current);
+ gsap.to(introRef.current, { value: 1, duration: 0.48, ease: 'power2.out' });
+ gsap.timeline()
+ .to(gatherRef.current, { value: 1, duration: 0.62, ease: 'power3.out' })
+ .to(gatherRef.current, { value: 1, duration: 0.26 })
+ .to(gatherRef.current, { value: 0, duration: 0.9, ease: 'power2.inOut' });
+ };
+
+ return (
+
{ pointerActiveRef.current = false; }}
+ onPointerDown={handleGather}
+ aria-hidden="true"
+ >
+

+ {!reducedMotion && !failed && (
+
+ )}
+
+ );
+}
diff --git a/src/harness/harness.css b/src/harness/harness.css
new file mode 100644
index 0000000..69bc578
--- /dev/null
+++ b/src/harness/harness.css
@@ -0,0 +1,590 @@
+*, *::before, *::after { box-sizing: border-box; }
+
+:root {
+ color-scheme: dark;
+ --h-bg: #05070a;
+ --h-bg-soft: #07131c;
+ --h-surface: rgba(13, 29, 41, .72);
+ --h-surface-solid: #0b1822;
+ --h-text: #f4fbff;
+ --h-text-soft: rgba(229, 244, 247, .66);
+ --h-text-faint: rgba(229, 244, 247, .42);
+ --h-cyan: #47e5ef;
+ --h-cyan-bright: #8bfbff;
+ --h-blue: #42a5ff;
+ --h-violet: #8982ff;
+ --h-line: rgba(183, 229, 231, .13);
+ --h-line-strong: rgba(139, 251, 255, .34);
+ --h-ease: cubic-bezier(.2, .8, .2, 1);
+ --h-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
+ --h-shell: min(1200px, calc(100vw - 48px));
+}
+
+html { scroll-behavior: smooth; background: var(--h-bg); }
+
+body {
+ margin: 0;
+ min-width: 320px;
+ overflow-x: hidden;
+ background: var(--h-bg);
+ color: var(--h-text);
+ font-family: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+body, button, input { font: inherit; }
+a { color: inherit; text-decoration: none; }
+button { color: inherit; }
+img { display: block; max-width: 100%; }
+::selection { color: #031013; background: var(--h-cyan-bright); }
+
+:focus-visible {
+ outline: 2px solid var(--h-cyan-bright);
+ outline-offset: 4px;
+}
+
+.sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border: 0;
+}
+
+.skip-link {
+ position: fixed;
+ top: 12px;
+ left: 12px;
+ z-index: 2000;
+ padding: 10px 16px;
+ border-radius: 9px;
+ color: #031013;
+ background: var(--h-cyan-bright);
+ transform: translateY(-160%);
+ transition: transform .2s ease;
+}
+
+.skip-link:focus { transform: translateY(0); }
+.content-shell { width: var(--h-shell); margin-inline: auto; }
+
+.harness-page {
+ min-height: 100vh;
+ overflow: clip;
+ background: var(--h-bg);
+}
+
+.harness-nav {
+ position: fixed;
+ inset: 0 0 auto;
+ z-index: 1000;
+ height: 72px;
+ border-bottom: 1px solid rgba(183, 229, 231, .08);
+ background: rgba(5, 7, 10, .72);
+ backdrop-filter: blur(18px) saturate(135%);
+ -webkit-backdrop-filter: blur(18px) saturate(135%);
+}
+
+.harness-nav-inner {
+ width: var(--h-shell);
+ height: 100%;
+ margin-inline: auto;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 28px;
+}
+
+.harness-brand {
+ display: inline-flex;
+ align-items: center;
+ gap: 11px;
+ flex: 0 0 auto;
+}
+
+.harness-brand img {
+ width: 37px;
+ height: 37px;
+ object-fit: contain;
+ filter: brightness(1.95) saturate(1.15) drop-shadow(0 0 12px rgba(71, 229, 239, .2));
+}
+
+.harness-brand span { display: flex; flex-direction: column; line-height: 1.05; }
+.harness-brand strong { font-size: 14px; letter-spacing: .04em; }
+.harness-brand small { margin-top: 6px; color: var(--h-text-faint); font: 500 8px/1 var(--h-mono); letter-spacing: .14em; }
+
+.harness-nav-links { display: flex; align-items: center; gap: 26px; }
+
+.harness-nav-links > a:not(.nav-email-button) {
+ position: relative;
+ color: var(--h-text-soft);
+ font-size: 14px;
+ font-weight: 600;
+ transition: color .2s ease;
+}
+
+.harness-nav-links > a:not(.nav-email-button)::after {
+ content: "";
+ position: absolute;
+ inset: auto 50% -10px;
+ width: 4px;
+ height: 4px;
+ border-radius: 50%;
+ background: var(--h-cyan);
+ opacity: 0;
+ transform: translateX(-50%) scale(0);
+ transition: opacity .2s ease, transform .2s ease;
+}
+
+.harness-nav-links > a:not(.nav-email-button):hover { color: var(--h-text); }
+.harness-nav-links > a:not(.nav-email-button):hover::after { opacity: 1; transform: translateX(-50%) scale(1); }
+
+.language-button {
+ min-height: 34px;
+ padding: 0 10px;
+ border: 1px solid var(--h-line);
+ border-radius: 8px;
+ background: rgba(255, 255, 255, .025);
+ color: var(--h-text-soft);
+ font: 650 11px/1 var(--h-mono);
+ cursor: pointer;
+}
+
+.language-button:hover { color: var(--h-text); border-color: var(--h-line-strong); }
+
+.nav-email-button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ min-height: 38px;
+ padding: 0 15px;
+ border-radius: 9px;
+ color: #041014;
+ background: var(--h-cyan);
+ font-size: 13px;
+ font-weight: 750;
+ transition: transform .22s var(--h-ease), box-shadow .22s ease;
+}
+
+.nav-email-button:hover { transform: translateY(-2px); box-shadow: 0 12px 32px rgba(71, 229, 239, .18); }
+
+.harness-menu-button {
+ display: none;
+ width: 42px;
+ height: 42px;
+ place-items: center;
+ border: 1px solid var(--h-line);
+ border-radius: 10px;
+ background: rgba(255, 255, 255, .035);
+ cursor: pointer;
+}
+
+.harness-story { position: relative; height: 220svh; background: var(--h-bg); }
+
+.story-sticky {
+ position: sticky;
+ top: 0;
+ height: 100svh;
+ min-height: 680px;
+ overflow: hidden;
+ isolation: isolate;
+ background:
+ radial-gradient(circle at 76% 28%, rgba(66, 165, 255, .14), transparent 24%),
+ radial-gradient(circle at 58% 54%, rgba(71, 229, 239, .08), transparent 34%),
+ linear-gradient(180deg, #07111a 0%, #05070a 72%, #05070a 100%);
+}
+
+.story-sticky::after {
+ content: "";
+ position: absolute;
+ inset: auto 0 0;
+ z-index: 5;
+ height: 180px;
+ pointer-events: none;
+ background: linear-gradient(transparent, var(--h-bg));
+}
+
+.story-grid {
+ position: absolute;
+ inset: 0;
+ z-index: 0;
+ opacity: .52;
+ background-image:
+ linear-gradient(rgba(183, 229, 231, .045) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(183, 229, 231, .045) 1px, transparent 1px);
+ background-size: 72px 72px;
+ mask-image: radial-gradient(circle at 68% 44%, #000 0%, rgba(0, 0, 0, .82) 42%, transparent 88%);
+ -webkit-mask-image: radial-gradient(circle at 68% 44%, #000 0%, rgba(0, 0, 0, .82) 42%, transparent 88%);
+}
+
+.story-glow {
+ position: absolute;
+ z-index: 0;
+ top: 8%;
+ right: 3%;
+ width: min(55vw, 850px);
+ aspect-ratio: 1;
+ border-radius: 50%;
+ opacity: .46;
+ background: conic-gradient(from 210deg, transparent, rgba(66, 165, 255, .18), rgba(137, 130, 255, .16), transparent 42%);
+ filter: blur(42px);
+ pointer-events: none;
+}
+
+.particle-stage { position: absolute; inset: 0; z-index: 1; cursor: crosshair; }
+.particle-canvas { position: absolute !important; inset: 0; width: 100% !important; height: 100% !important; }
+
+.particle-static-logo {
+ position: absolute;
+ top: 50%;
+ right: 13%;
+ width: min(45vw, 610px);
+ transform: translateY(-50%);
+ opacity: 0;
+ object-fit: contain;
+ filter: brightness(1.6) saturate(1.55) hue-rotate(155deg) drop-shadow(0 0 38px rgba(66, 165, 255, .24));
+ transition: opacity .18s ease;
+}
+
+.particle-stage.is-ready .particle-static-logo { opacity: 0; }
+.particle-stage.has-failed .particle-static-logo,
+.particle-stage.is-reduced .particle-static-logo { opacity: .82; }
+
+.story-shell {
+ position: relative;
+ z-index: 3;
+ width: var(--h-shell);
+ height: 100%;
+ margin-inline: auto;
+ pointer-events: none;
+}
+
+.hero-copy {
+ position: absolute;
+ top: 50%;
+ left: 0;
+ width: min(47%, 560px);
+ transform: translateY(-48%);
+ pointer-events: none;
+}
+
+.hero-meta { display: flex; align-items: center; gap: 12px; margin-bottom: 26px; }
+.hero-meta > span, .section-kicker { color: var(--h-cyan); font: 700 11px/1.35 var(--h-mono); letter-spacing: .13em; }
+.hero-meta strong { padding: 7px 10px; border: 1px solid rgba(139, 251, 255, .22); border-radius: 999px; color: var(--h-text); background: rgba(71, 229, 239, .07); font: 650 10px/1 var(--h-mono); letter-spacing: .1em; }
+
+.hero-copy h1, .value-copy h2, .section-header h2, .product-cta h2 {
+ margin: 0;
+ color: var(--h-text);
+ font-weight: 680;
+ line-height: 1.06;
+ letter-spacing: -.05em;
+}
+
+.hero-copy h1 { font-size: clamp(48px, 5.35vw, 78px); }
+.hero-copy h1 span, .value-copy h2 span, .section-header h2 span, .product-cta h2 span { display: block; }
+
+.hero-copy > p {
+ max-width: 470px;
+ margin: 28px 0 0;
+ color: var(--h-text-soft);
+ font-size: 17px;
+ line-height: 1.82;
+}
+
+.hero-actions { display: flex; gap: 12px; margin-top: 34px; pointer-events: auto; }
+.button-primary, .button-secondary, .product-cta a {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ min-height: 50px;
+ padding: 0 20px;
+ border: 1px solid transparent;
+ border-radius: 10px;
+ font-size: 14px;
+ font-weight: 750;
+ transition: transform .24s var(--h-ease), border-color .24s ease, background .24s ease, box-shadow .24s ease;
+}
+
+.button-primary, .product-cta a { color: #031013; background: var(--h-cyan); }
+.button-primary:hover, .product-cta a:hover { transform: translateY(-3px); box-shadow: 0 16px 40px rgba(71, 229, 239, .2); }
+.button-secondary { border-color: var(--h-line); color: var(--h-text); background: rgba(255, 255, 255, .035); }
+.button-secondary:hover { transform: translateY(-3px); border-color: var(--h-line-strong); background: rgba(255, 255, 255, .07); }
+
+.hero-proof { display: flex; flex-wrap: wrap; gap: 8px 22px; margin-top: 30px; color: var(--h-text-faint); font-size: 12px; }
+.hero-proof span { position: relative; padding-left: 13px; }
+.hero-proof span::before { content: ""; position: absolute; left: 0; top: 50%; width: 5px; height: 5px; border-radius: 50%; background: var(--h-cyan); transform: translateY(-50%); box-shadow: 0 0 10px rgba(71, 229, 239, .65); }
+
+.value-copy {
+ position: absolute;
+ inset: 15% 0 auto;
+ opacity: 0;
+ text-align: center;
+ pointer-events: none;
+}
+
+.value-copy h2 { margin-top: 22px; font-size: clamp(40px, 4.5vw, 64px); }
+.value-copy > p { width: min(650px, 80%); margin: 22px auto 0; color: var(--h-text-soft); font-size: 17px; line-height: 1.8; }
+.value-copy > .section-kicker { display: inline-flex; align-items: center; gap: 9px; padding: 8px 12px; border: 1px solid rgba(139, 251, 255, .22); border-radius: 999px; background: rgba(71, 229, 239, .06); box-shadow: 0 0 28px rgba(71, 229, 239, .07); letter-spacing: .17em; }
+.value-copy > .section-kicker::before { content: ""; width: 5px; height: 5px; border-radius: 50%; background: var(--h-cyan); box-shadow: 0 0 10px rgba(71, 229, 239, .75); }
+
+.value-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; margin-top: 48px; text-align: left; }
+.value-card {
+ position: relative;
+ min-height: 208px;
+ padding: 24px;
+ border: 1px solid var(--h-line);
+ border-radius: 16px;
+ background: linear-gradient(150deg, rgba(14, 32, 44, .76), rgba(8, 18, 26, .48));
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, .025);
+ backdrop-filter: blur(16px);
+ -webkit-backdrop-filter: blur(16px);
+}
+
+.value-card > svg { color: var(--h-cyan); }
+.value-card > span { position: absolute; top: 24px; right: 24px; color: var(--h-text-faint); font: 600 10px/1 var(--h-mono); }
+.value-card h3 { margin: 30px 0 10px; font-size: 19px; letter-spacing: -.025em; }
+.value-card p { margin: 0; color: var(--h-text-soft); font-size: 13px; line-height: 1.7; }
+
+.story-scroll-cue {
+ position: absolute;
+ z-index: 6;
+ left: 50%;
+ bottom: 34px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ color: var(--h-text-faint);
+ font: 600 9px/1 var(--h-mono);
+ letter-spacing: .14em;
+ transform: translateX(-50%);
+}
+
+.story-scroll-cue span { width: 34px; height: 1px; background: linear-gradient(90deg, transparent, var(--h-cyan)); animation: cue-pulse 1.8s ease-in-out infinite; }
+@keyframes cue-pulse { 50% { width: 52px; opacity: .4; } }
+
+.content-section { position: relative; padding: 132px 0; overflow: hidden; }
+.content-section::before { content: ""; position: absolute; inset: 0; pointer-events: none; opacity: .36; background-image: linear-gradient(rgba(183, 229, 231, .035) 1px, transparent 1px), linear-gradient(90deg, rgba(183, 229, 231, .035) 1px, transparent 1px); background-size: 72px 72px; mask-image: linear-gradient(transparent, #000 18%, #000 82%, transparent); -webkit-mask-image: linear-gradient(transparent, #000 18%, #000 82%, transparent); }
+
+.engine-section { background: linear-gradient(180deg, var(--h-bg), #071019 48%, var(--h-bg)); }
+.editions-section { background: radial-gradient(circle at 78% 40%, rgba(137, 130, 255, .09), transparent 30%), var(--h-bg); }
+.workflow-section { background: linear-gradient(180deg, var(--h-bg), #07131c); }
+
+.section-header { position: relative; z-index: 1; display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(320px, .62fr); align-items: end; gap: 80px; margin-bottom: 62px; }
+.section-header h2 { margin-top: 22px; font-size: clamp(38px, 4.2vw, 60px); }
+.section-header > p { margin: 0; color: var(--h-text-soft); font-size: 17px; line-height: 1.82; }
+
+.opensource-note { position: relative; z-index: 1; display: flex; align-items: center; gap: 22px; margin: -28px 0 32px; padding: 14px 18px; border: 1px solid var(--h-line); border-radius: 11px; color: var(--h-text-faint); background: rgba(255, 255, 255, .018); }
+.opensource-note > span { flex: 0 0 auto; color: var(--h-cyan); font: 650 9px/1.4 var(--h-mono); letter-spacing: .12em; }
+.opensource-note > p { margin: 0; font-size: 11px; line-height: 1.6; }
+
+.engine-layout { position: relative; z-index: 1; display: grid; grid-template-columns: minmax(0, 1.48fr) minmax(300px, .62fr); gap: 22px; align-items: start; }
+
+.plugin-demo { overflow: hidden; border: 1px solid var(--h-line); border-radius: 20px; background: rgba(7, 18, 27, .87); box-shadow: 0 30px 90px rgba(0, 0, 0, .25), inset 0 1px 0 rgba(255, 255, 255, .03); }
+.demo-toolbar { display: flex; align-items: center; justify-content: space-between; min-height: 52px; padding: 0 18px; border-bottom: 1px solid var(--h-line); color: var(--h-text-faint); background: rgba(255, 255, 255, .02); font: 650 9px/1 var(--h-mono); letter-spacing: .12em; }
+.demo-toolbar > div { display: flex; align-items: center; gap: 8px; color: var(--h-text-soft); }
+.status-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--h-cyan); box-shadow: 0 0 14px rgba(71, 229, 239, .8); }
+.demo-search-row { padding: 18px 18px 12px; }
+.demo-search { display: flex; align-items: center; gap: 10px; min-height: 48px; padding: 0 14px; border: 1px solid var(--h-line); border-radius: 11px; background: rgba(255, 255, 255, .025); color: var(--h-text-faint); }
+.demo-search:focus-within { border-color: var(--h-line-strong); box-shadow: 0 0 0 3px rgba(71, 229, 239, .07); }
+.demo-search input { width: 100%; border: 0; outline: 0; color: var(--h-text); background: transparent; font-size: 14px; }
+.demo-search input::placeholder { color: var(--h-text-faint); }
+.category-tabs { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 12px; }
+.category-tabs button { min-height: 30px; padding: 0 11px; border: 1px solid transparent; border-radius: 8px; background: transparent; color: var(--h-text-faint); font-size: 11px; cursor: pointer; }
+.category-tabs button:hover, .category-tabs button.is-active { border-color: var(--h-line); color: var(--h-text); background: rgba(71, 229, 239, .065); }
+.demo-result-meta { padding: 0 18px 12px; color: var(--h-text-faint); font: 550 10px/1 var(--h-mono); }
+.demo-result-meta strong { color: var(--h-cyan); }
+.plugin-result-list { display: grid; gap: 8px; max-height: 416px; padding: 0 18px 18px; overflow: auto; scrollbar-width: thin; scrollbar-color: rgba(139, 251, 255, .2) transparent; }
+.plugin-result { display: grid; grid-template-columns: 42px 1fr auto; align-items: center; gap: 12px; min-height: 92px; padding: 13px; border: 1px solid var(--h-line); border-radius: 13px; background: rgba(255, 255, 255, .018); transition: border-color .2s ease, background .2s ease, transform .2s var(--h-ease); }
+.plugin-result:hover { transform: translateY(-2px); border-color: rgba(139, 251, 255, .23); background: rgba(255, 255, 255, .032); }
+.plugin-result.is-active { border-color: rgba(71, 229, 239, .42); background: linear-gradient(100deg, rgba(71, 229, 239, .08), rgba(66, 165, 255, .035)); }
+.plugin-icon { display: grid; place-items: center; width: 42px; height: 42px; border: 1px solid var(--h-line); border-radius: 11px; color: var(--h-cyan); background: rgba(71, 229, 239, .05); }
+.plugin-copy > span { color: var(--h-cyan); font: 650 8px/1 var(--h-mono); letter-spacing: .12em; }
+.plugin-copy h3 { margin: 7px 0 4px; font-size: 14px; }
+.plugin-copy p { margin: 0; color: var(--h-text-faint); font-size: 11px; line-height: 1.5; }
+.plugin-result button { min-height: 34px; padding: 0 12px; border: 1px solid var(--h-line); border-radius: 8px; color: var(--h-text-soft); background: rgba(255, 255, 255, .025); font-size: 11px; cursor: pointer; }
+.plugin-result button:hover, .plugin-result button[aria-pressed="true"] { border-color: var(--h-line-strong); color: #031013; background: var(--h-cyan); }
+.empty-result { padding: 36px 18px; color: var(--h-text-faint); text-align: center; font-size: 13px; }
+.runtime-strip { display: flex; align-items: center; justify-content: space-between; gap: 18px; min-height: 68px; padding: 12px 18px; border-top: 1px solid var(--h-line); background: rgba(71, 229, 239, .04); }
+.runtime-strip > div { display: flex; align-items: center; gap: 10px; color: var(--h-cyan); }
+.runtime-strip > div span { display: flex; flex-direction: column; gap: 5px; }
+.runtime-strip small { color: var(--h-text-faint); font: 600 8px/1 var(--h-mono); letter-spacing: .1em; text-transform: uppercase; }
+.runtime-strip strong { color: var(--h-text); font-size: 12px; }
+.runtime-strip p { margin: 0; color: var(--h-text-faint); font-size: 10px; }
+
+.engine-steps { display: grid; gap: 10px; }
+.engine-steps article { display: grid; grid-template-columns: 44px 1fr; gap: 16px; min-height: 135px; padding: 20px; border: 1px solid var(--h-line); border-radius: 15px; background: rgba(255, 255, 255, .018); }
+.engine-steps article > span { color: var(--h-cyan); font: 650 10px/1 var(--h-mono); letter-spacing: .1em; }
+.engine-steps h3 { margin: 0 0 9px; font-size: 18px; }
+.engine-steps p { margin: 0; color: var(--h-text-soft); font-size: 12px; line-height: 1.68; }
+
+.edition-grid { position: relative; z-index: 1; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; }
+.edition-card { position: relative; min-height: 570px; padding: 34px; border: 1px solid var(--h-line); border-radius: 22px; background: linear-gradient(155deg, rgba(15, 31, 43, .72), rgba(6, 14, 21, .78)); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .03); }
+.edition-card.is-featured { border-color: rgba(139, 251, 255, .28); background: radial-gradient(circle at 86% 6%, rgba(137, 130, 255, .15), transparent 28%), linear-gradient(155deg, rgba(15, 34, 47, .85), rgba(6, 14, 21, .82)); }
+.edition-head { display: flex; align-items: center; justify-content: space-between; color: var(--h-cyan); }
+.edition-head span { font: 700 10px/1 var(--h-mono); letter-spacing: .14em; }
+.edition-card h3 { margin: 46px 0 15px; font-size: 34px; letter-spacing: -.035em; }
+.edition-card > p { max-width: 510px; margin: 0; color: var(--h-text-soft); font-size: 15px; line-height: 1.72; }
+.edition-card ul { display: grid; gap: 14px; margin: 38px 0 0; padding: 28px 0 0; border-top: 1px solid var(--h-line); list-style: none; }
+.edition-card li { display: flex; align-items: flex-start; gap: 10px; color: var(--h-text-soft); font-size: 14px; }
+.edition-card li svg { flex: 0 0 auto; margin-top: 2px; color: var(--h-cyan); }
+.edition-card > a { position: absolute; inset: auto 34px 34px; display: inline-flex; align-items: center; justify-content: center; gap: 10px; min-height: 48px; border: 1px solid var(--h-line); border-radius: 10px; color: var(--h-text); background: rgba(255, 255, 255, .03); font-size: 13px; font-weight: 750; transition: border-color .2s ease, background .2s ease, transform .2s var(--h-ease); }
+.edition-card.is-featured > a { color: #031013; border-color: var(--h-cyan); background: var(--h-cyan); }
+.edition-card > a:hover { transform: translateY(-2px); border-color: var(--h-line-strong); background: rgba(71, 229, 239, .09); }
+.edition-card.is-featured > a:hover { background: var(--h-cyan-bright); }
+
+.workflow-grid { position: relative; z-index: 1; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); border: 1px solid var(--h-line); border-radius: 20px; overflow: hidden; background: rgba(5, 13, 19, .7); }
+.workflow-grid article { position: relative; min-height: 310px; padding: 28px; }
+.workflow-grid article + article { border-left: 1px solid var(--h-line); }
+.workflow-grid article > span { color: var(--h-text-faint); font: 650 10px/1 var(--h-mono); letter-spacing: .12em; }
+.workflow-node { display: grid; place-items: center; width: 48px; height: 48px; margin-top: 54px; border: 1px solid var(--h-line-strong); border-radius: 50%; color: var(--h-cyan); background: rgba(71, 229, 239, .055); box-shadow: 0 0 28px rgba(71, 229, 239, .08); }
+.workflow-grid article:not(:last-child) .workflow-node::after { content: ""; position: absolute; top: 111px; left: 76px; width: calc(100% - 48px); height: 1px; background: linear-gradient(90deg, rgba(71, 229, 239, .4), transparent); }
+.workflow-grid h3 { margin: 34px 0 10px; font-size: 19px; }
+.workflow-grid p { margin: 0; color: var(--h-text-soft); font-size: 13px; line-height: 1.68; }
+
+.product-cta { position: relative; min-height: 760px; display: grid; place-items: center; overflow: hidden; text-align: center; background: radial-gradient(circle at 50% 48%, rgba(71, 229, 239, .105), transparent 30%), linear-gradient(180deg, #07131c, var(--h-bg)); }
+.product-cta::before { content: ""; position: absolute; inset: 0; opacity: .45; background-image: linear-gradient(rgba(183, 229, 231, .04) 1px, transparent 1px), linear-gradient(90deg, rgba(183, 229, 231, .04) 1px, transparent 1px); background-size: 72px 72px; mask-image: radial-gradient(circle, #000 0%, transparent 70%); -webkit-mask-image: radial-gradient(circle, #000 0%, transparent 70%); }
+.product-cta .content-shell { position: relative; z-index: 2; display: flex; flex-direction: column; align-items: center; }
+.product-cta h2 { margin-top: 24px; font-size: clamp(43px, 5vw, 70px); }
+.product-cta p { width: min(640px, 88%); margin: 26px auto 0; color: var(--h-text-soft); font-size: 17px; line-height: 1.78; }
+.product-cta a { margin-top: 34px; }
+.product-cta strong { margin-top: 18px; color: var(--h-text-faint); font: 550 11px/1 var(--h-mono); letter-spacing: .08em; }
+.cta-orbit { position: absolute; z-index: 1; width: min(68vw, 780px); aspect-ratio: 1; opacity: .11; animation: orbit-rotate 38s linear infinite; }
+.cta-orbit img { width: 100%; height: 100%; object-fit: contain; filter: brightness(2.2) saturate(1.4) hue-rotate(155deg); }
+@keyframes orbit-rotate { to { transform: rotate(360deg); } }
+
+.harness-footer { padding: 28px 0 32px; border-top: 1px solid var(--h-line); background: var(--h-bg); }
+.harness-footer-row { display: flex; align-items: center; justify-content: space-between; gap: 24px; }
+.harness-footer-company { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 16px; }
+.harness-footer-company > span, .harness-footer-signal { color: var(--h-text-faint); font: 550 9px/1.5 var(--h-mono); }
+.harness-footer-record { color: var(--h-text-faint); font-size: 11px; line-height: 1.5; transition: color .2s ease; }
+.harness-footer-record:hover { color: var(--h-cyan-bright); }
+.harness-footer-police { display: inline-flex; align-items: center; gap: 5px; }
+.harness-footer-police img { width: 18px; height: 20px; object-fit: contain; }
+.harness-footer-signal { flex: 0 0 auto; letter-spacing: .08em; }
+
+@media (min-width: 1500px) {
+ :root { --h-shell: min(1380px, calc(100vw - 8vw)); }
+ .hero-copy { width: min(43%, 610px); }
+}
+
+@media (max-width: 1050px) {
+ .harness-nav-links { gap: 18px; }
+ .harness-nav-links > a:not(.nav-email-button) { font-size: 13px; }
+ .hero-copy { width: min(50%, 540px); }
+ .engine-layout { grid-template-columns: 1fr; }
+ .engine-steps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
+ .workflow-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
+ .workflow-grid article:nth-child(3) { border-left: 0; border-top: 1px solid var(--h-line); }
+ .workflow-grid article:nth-child(4) { border-top: 1px solid var(--h-line); }
+ .workflow-grid article:nth-child(2) .workflow-node::after { display: none; }
+}
+
+@media (max-width: 820px) {
+ :root { --h-shell: min(100% - 36px, 720px); }
+ .harness-nav { height: 66px; }
+ .harness-menu-button { display: grid; }
+ .harness-nav-links {
+ position: fixed;
+ inset: 66px 0 auto;
+ display: grid;
+ gap: 0;
+ padding: 16px 18px 20px;
+ border-bottom: 1px solid var(--h-line);
+ background: rgba(5, 7, 10, .98);
+ opacity: 0;
+ pointer-events: none;
+ transform: translateY(-12px);
+ transition: opacity .22s ease, transform .22s var(--h-ease);
+ }
+ .harness-nav-links.is-open { opacity: 1; pointer-events: auto; transform: translateY(0); }
+ .harness-nav-links > a:not(.nav-email-button) { padding: 13px 4px; border-bottom: 1px solid var(--h-line); }
+ .harness-nav-links > a:not(.nav-email-button)::after { display: none; }
+ .language-button { width: 100%; margin-top: 12px; }
+ .nav-email-button { margin-top: 10px; }
+ .story-sticky { min-height: 660px; }
+ .hero-copy { top: 48%; width: 58%; }
+ .hero-copy > p { max-width: 330px; }
+ .hero-copy h1 { font-size: clamp(44px, 8vw, 64px); }
+ .particle-static-logo { right: -1%; width: 54vw; }
+ .value-copy { inset: 12% 0 auto; }
+ .value-grid { gap: 10px; margin-top: 34px; }
+ .value-card { min-height: 190px; padding: 20px; }
+ .section-header { grid-template-columns: 1fr; gap: 24px; }
+ .edition-card { min-height: 590px; padding: 28px; }
+ .edition-card > a { inset: auto 28px 28px; }
+}
+
+@media (max-width: 640px) {
+ :root { --h-shell: min(100% - 28px, 520px); }
+ .harness-brand small { display: none; }
+ .harness-brand img { width: 34px; height: 34px; }
+ .harness-story { height: 230svh; }
+ .story-sticky { min-height: 620px; }
+ .story-grid, .content-section::before, .product-cta::before { background-size: 44px 44px; }
+ .particle-stage { opacity: .72; }
+ .particle-static-logo { top: 28%; right: 8%; width: 64vw; }
+ .hero-copy { top: 47%; width: 100%; transform: translateY(-42%); }
+ .hero-meta { margin-bottom: 20px; }
+ .hero-copy h1 { max-width: 500px; font-size: clamp(42px, 13.5vw, 59px); }
+ .hero-copy > p { margin-top: 22px; font-size: 15px; line-height: 1.72; }
+ .hero-actions { display: grid; gap: 9px; margin-top: 26px; }
+ .button-primary, .button-secondary { width: 100%; min-height: 47px; }
+ .hero-proof { display: grid; gap: 8px; margin-top: 20px; }
+ .value-copy { inset: 9% 0 auto; }
+ .value-copy h2 { margin-top: 17px; font-size: clamp(34px, 10vw, 46px); }
+ .value-copy > p { width: 100%; margin-top: 16px; font-size: 14px; line-height: 1.65; }
+ .value-grid { grid-template-columns: 1fr; gap: 8px; margin-top: 24px; }
+ .value-card { display: grid; grid-template-columns: 30px 1fr; align-items: center; gap: 3px 12px; min-height: 0; padding: 14px; }
+ .value-card > svg { grid-row: 1 / 3; }
+ .value-card > span { top: 14px; right: 14px; }
+ .value-card h3 { margin: 0; padding-right: 28px; font-size: 15px; }
+ .value-card p { margin: 0; font-size: 11px; line-height: 1.45; }
+ .story-scroll-cue { bottom: 18px; }
+ .content-section { padding: 92px 0; }
+ .section-header { margin-bottom: 40px; }
+ .section-header h2 { font-size: clamp(36px, 10vw, 48px); }
+ .section-header > p { font-size: 15px; }
+ .demo-toolbar > span { display: none; }
+ .demo-search-row { padding: 14px 14px 10px; }
+ .demo-result-meta { padding-inline: 14px; }
+ .plugin-result-list { padding: 0 14px 14px; }
+ .plugin-result { grid-template-columns: 38px 1fr; min-height: 0; }
+ .plugin-icon { width: 38px; height: 38px; }
+ .plugin-result button { grid-column: 1 / -1; width: 100%; }
+ .runtime-strip { align-items: flex-start; flex-direction: column; }
+ .engine-steps { grid-template-columns: 1fr; }
+ .engine-steps article { min-height: 0; }
+ .edition-grid { grid-template-columns: 1fr; }
+ .edition-card { min-height: 560px; padding: 24px; }
+ .edition-card > a { inset: auto 24px 24px; }
+ .workflow-grid { grid-template-columns: 1fr; }
+ .workflow-grid article { min-height: 250px; }
+ .workflow-grid article + article { border-left: 0; border-top: 1px solid var(--h-line); }
+ .workflow-grid article .workflow-node::after { display: none !important; }
+ .workflow-node { margin-top: 34px; }
+ .opensource-note { align-items: flex-start; flex-direction: column; gap: 7px; margin-top: -30px; }
+ .product-cta { min-height: 690px; }
+ .product-cta h2 { font-size: clamp(40px, 11vw, 54px); }
+ .product-cta p { width: 100%; font-size: 15px; }
+ .product-cta a { width: 100%; }
+ .cta-orbit { width: 100vw; }
+ .harness-footer-row { align-items: flex-start; flex-direction: column; gap: 14px; }
+ .harness-footer-company { align-items: flex-start; flex-direction: column; gap: 9px; }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ html { scroll-behavior: auto; }
+ *, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; }
+ .harness-story { height: auto; }
+ .story-sticky { position: relative; min-height: 1180px; height: auto; }
+ .hero-copy { top: 180px; transform: none; }
+ .value-copy { top: 650px; opacity: 1; }
+ .particle-static-logo { opacity: .32; }
+ .reveal-section { opacity: 1 !important; transform: none !important; }
+}
diff --git a/src/harness/main.jsx b/src/harness/main.jsx
new file mode 100644
index 0000000..328e1c6
--- /dev/null
+++ b/src/harness/main.jsx
@@ -0,0 +1,10 @@
+import { StrictMode } from 'react';
+import { createRoot } from 'react-dom/client';
+import { HarnessProductPage } from './HarnessProductPage';
+import './harness.css';
+
+createRoot(document.getElementById('root')).render(
+
+
+
+);
diff --git a/vite.config.js b/vite.config.js
new file mode 100644
index 0000000..f160df3
--- /dev/null
+++ b/vite.config.js
@@ -0,0 +1,24 @@
+import { resolve } from 'node:path';
+import { defineConfig } from 'vite';
+
+export default defineConfig({
+ appType: 'mpa',
+ base: '/',
+ server: {
+ host: '0.0.0.0',
+ allowedHosts: ['localhost', '127.0.0.1', 'terminal.local']
+ },
+ preview: {
+ host: '0.0.0.0'
+ },
+ build: {
+ outDir: 'dist',
+ emptyOutDir: true,
+ rollupOptions: {
+ input: {
+ home: resolve(process.cwd(), 'index.html'),
+ harness: resolve(process.cwd(), 'products/roncarve-harness/index.html')
+ }
+ }
+ }
+});
diff --git a/荣刻驭境-Roncarve-Harness-产品页设计文档.md b/荣刻驭境-Roncarve-Harness-产品页设计文档.md
new file mode 100644
index 0000000..fbf9e2c
--- /dev/null
+++ b/荣刻驭境-Roncarve-Harness-产品页设计文档.md
@@ -0,0 +1,717 @@
+# 荣刻驭境(Roncarve Harness)产品入口与产品介绍页设计文档
+
+> 文档状态:设计基线 v1.1(关键决策已确认)
+> 编写日期:2026-08-22
+> 适用范围:荣刻科技官网导航、产品列表入口、荣刻驭境产品介绍页
+> 参考页面:[DeepSeek Harness](https://www.deepseek.com/harness/)
+> 当前仓库基线:单页静态官网 `index.html` + 原生 CSS/JavaScript + Three.js Hero
+
+## 1. 设计结论
+
+本次新增内容由两个层级组成:
+
+1. 官网主导航新增“产品 / Products”入口,在下拉产品列表中展示“荣刻驭境 / Roncarve Harness”。
+2. 点击产品卡片进入独立产品介绍页,推荐正式路由为 `/products/roncarve-harness/`。
+
+产品页采用“深色空间舞台 + 品牌 Logo 粒子 + 滚动叙事”的视觉方向。首屏加载时,粒子从四面八方与不同景深层进入,并在约 3 秒内减速汇聚为完整荣刻 Logo;汇聚完成后维持轻微漂浮,只有在用户开始滚动后才逐渐进入 3D 旋转、景深缩放和空间展开。鼠标靠近粒子产生局部排斥,点击非交互背景时粒子短暂重新聚拢,再回到当前滚动位置对应的空间状态。
+
+页面表达的核心不是“又一个 Harness”,而是三项差异化价值:
+
+- 定制优化的插件引擎:面向海量插件进行精准检索、匹配与发现。
+- 更高效率的热插拔:插件可以快速加载、卸载、替换和组合。
+- 从本地到协同:个人版以本地客户端连接本地项目,并通过浏览器界面操作;平台版以团队共享工作空间承载所有成员协同。
+
+视觉上参考 DeepSeek Harness 的深色、网格、光晕、克制排版与滚动节奏,但不复制其品牌标识、文案、图形资产或页面结构细节。荣刻页面继续使用现有官网的蓝青紫品牌色、圆角、按钮和字体体系。
+
+### 1.1 已确认决策
+
+| 项目 | 决策 |
+| --- | --- |
+| 产品状态 | 统一展示为“即将开放 / Coming Soon” |
+| 转化渠道 | 当前只使用 `contact@roncarve.com` 邮箱 |
+| 版本边界 | 采用本文第 5.4 节定义的个人版/平台版首发能力 |
+| 演示参考 | 使用 DeepSeek Harness 官方 Web UI、插件状态页和运行轨迹界面作为研究参考 |
+| 上游许可 | 已确认允许二次开发;发布物继续保留上游许可证与必要署名 |
+| 前端架构 | 确认引入 React/Vite,产品页采用 React Three Fiber + GSAP |
+
+## 2. 背景与目标
+
+### 2.1 产品背景
+
+产品中文名为“荣刻驭境”,英文名为“Roncarve Harness”。产品基于 DeepSeek Harness 进行二次开发,面向大语言模型与 Agent 场景,重点强化插件检索、插件热插拔、浏览器操作本地项目的个人客户端,以及团队共享工作空间。
+
+“驭境”对应 Harness 对运行环境、工具与能力的组织和驾驭;英文名称保留 Harness,便于开发者快速理解产品类别。
+
+### 2.2 设计目标
+
+- 在官网中建立清晰、可扩展的产品入口,为后续新增产品预留结构。
+- 在首屏 10 秒内让访客理解产品类别、差异能力和适用对象。
+- 用品牌 Logo 粒子从“完整标识”到“空间插件生态”的变化,形成产品记忆点。
+- 第二屏立即解释 Harness 的价值,避免页面只有视觉效果而缺乏产品信息。
+- 同时服务技术决策者、开发者、企业团队负责人三类受众。
+- 在桌面端保持沉浸感,在移动端、低性能设备和减少动态效果模式下保持可读与可用。
+
+### 2.3 非目标
+
+- 本阶段不设计完整控制台、插件市场或工作空间内部界面。
+- 不在官网中展示未经确认的插件数量、性能倍数、客户数量或兼容模型数量。
+- 不复制 DeepSeek Harness 的 Logo、动态图形、代码卡片、具体文案与视觉资产。
+- 不让粒子交互覆盖导航、按钮、表单等核心操作。
+- 不要求首页现有 Three.js Hero 与产品页共用同一套 3D 场景。
+
+## 3. 产品定位与信息优先级
+
+### 3.1 一句话定位
+
+> 荣刻驭境是一套面向大语言模型与 Agent 的可组合 Harness,通过定制插件引擎实现海量插件精准检索和高效热插拔;个人版以本地客户端连接本地项目,平台版以共享工作空间连接团队协作。
+
+### 3.2 目标受众
+
+| 受众 | 主要任务 | 页面需要回答的问题 |
+| --- | --- | --- |
+| AI 开发者 | 快速组合模型、工具和插件 | 插件如何发现、加载、替换与调试? |
+| 技术负责人 | 评估 Agent 基础设施 | 是否可私有部署、可扩展、可治理? |
+| 企业团队负责人 | 组织多人协作 | 如何划定工作空间、管理成员与共享能力? |
+| 本地项目使用者 | 通过浏览器操作当前设备上的项目 | 客户端如何连接本地文件、运行环境与插件? |
+
+### 3.3 信息层级
+
+访客应按以下顺序理解产品:
+
+1. 这是什么:面向大语言模型与 Agent 的 Harness。
+2. 为什么不同:定制插件引擎、精准检索、高效热插拔。
+3. 如何使用:个人版通过本地客户端在浏览器中操作本地项目。
+4. 团队如何用:平台版让所有成员在共享工作空间中协同。
+5. 下一步是什么:通过邮箱咨询产品、选择版本或查看技术资料。
+
+## 4. 官网产品入口设计
+
+### 4.1 导航位置
+
+建议主导航顺序调整为:
+
+`产品` → `能力链路` → `解决方案` → `核心优势` → `交付网络` → `邮件咨询`
+
+“产品”放在能力与解决方案之前,先呈现可购买/可体验的具体对象,再承接企业能力说明。英文导航使用 `Products`。
+
+### 4.2 桌面端交互
+
+- “产品”是带下拉面板的导航项,点击和键盘操作都可打开;悬停只作为增强,不作为唯一触发方式。
+- 面板位于导航下方,宽度建议 `420–520px`,使用现有深色半透明表面、`16px` 圆角和低对比边框。
+- 当前只有一个产品时,仍使用产品卡片结构,为后续扩充产品保留空间。
+- 点击卡片任意非禁用区域进入 `/products/roncarve-harness/`。
+- 点击外部、按 `Esc`、选择产品或导航离开后关闭面板。
+- 打开时首个产品卡获得逻辑焦点;焦点不能被困在面板中。
+
+### 4.3 移动端交互
+
+- “产品”作为一级折叠项,点击后在移动导航中展开产品列表。
+- 产品卡改为全宽纵向条目,不使用桌面浮层定位。
+- 展开按钮使用 `aria-expanded`,列表与按钮通过 `aria-controls` 关联。
+- 产品卡最小触控高度 `56px`,与语言、主题和邮件咨询按钮保持足够间距。
+
+### 4.4 产品列表卡文案
+
+**中文**
+
+- 分类标签:`AGENT INFRA / HARNESS`
+- 产品名:`荣刻驭境`
+- 英文名:`Roncarve Harness`
+- 简介:`本地客户端连接本地项目,平台共享工作空间承载团队协同,并内置精准插件检索与高效热插拔。`
+- 状态标签:`即将开放`
+
+**英文**
+
+- Product: `Roncarve Harness`
+- Description: `A local client for browser-based local projects and a shared workspace for team collaboration, with precise plugin discovery and hot swapping.`
+- Status: `Coming Soon`
+
+### 4.5 路由与返回逻辑
+
+- 推荐正式路由:`/products/roncarve-harness/`。
+- 推荐面包屑:`首页 / 产品 / 荣刻驭境`,视觉上可弱化,但保留语义和返回能力。
+- 产品页品牌 Logo 点击返回官网首页;“产品”导航保持当前激活态。
+- 现有 Nginx `try_files` 规则支持目录式静态路由,但实现阶段仍需确认构建产物确实生成 `products/roncarve-harness/index.html`。
+- 上线时同步更新 `sitemap.xml`、canonical、Open Graph 和结构化数据。
+
+## 5. 产品介绍页信息架构
+
+页面采用 6 个内容阶段。粒子舞台在前两屏连续存在,第二屏结束后逐渐退为环境背景,正文接管注意力。
+
+| 阶段 | 建议高度 | 核心任务 | 主要内容 | 粒子状态 |
+| --- | ---: | --- | --- | --- |
+| 01 Hero | `100svh`,最小 `760px` | 说明产品是什么 | 品牌、主标题、简介、CTA、能力摘要 | 四向入场后汇聚为完整 Logo,不旋转、不展开 |
+| 02 Harness 价值 | `100–120svh` | 说明为什么需要它 | 三项核心能力与一句方法论 | Logo 展开为空间插件网络,进入景深与旋转 |
+| 03 插件引擎 | 内容自适应 | 解释差异化能力 | 精准检索、匹配、热插拔、运行可观测 | 粒子退到背景,形成节点流 |
+| 04 双版本 | 内容自适应 | 帮助选择版本 | 本地客户端、团队共享工作空间、协同 | 仅保留低密度环境粒子 |
+| 05 使用路径 | 内容自适应 | 建立落地感 | 发现插件 → 装配能力 → 运行 → 团队沉淀 | 局部连线与状态点 |
+| 06 CTA | `70–90svh` | 推动转化 | 邮件咨询、版本选择、技术资料 | 粒子再次轻度聚拢为品牌轮廓 |
+
+### 5.1 第一屏:完整 Logo 与产品价值
+
+首屏必须在 WebGL 初始化前先显示可读文案,粒子就绪后直接从画面边缘入场,不先闪现完整静态 Logo。静态 Logo 仅用于减少动态效果模式或 WebGL 失败回退。
+
+建议内容:
+
+- 状态标签:`即将开放 / COMING SOON`
+- Eyebrow:`RONCARVE HARNESS / 荣刻驭境`
+- H1:`让 Agent 驾驭每一种能力`
+- 副标题:`面向大语言模型的可组合 Harness,以定制插件引擎实现精准检索与高效热插拔;个人版在浏览器中操作本地项目,平台版以共享工作空间连接团队协作。`
+- 主按钮:`邮件咨询`
+- 次按钮:`查看版本能力`
+- 能力摘要:`精准插件检索`、`高效热插拔`、`个人 / 平台双版本`
+
+英文对应文案:状态为 `COMING SOON`,主按钮为 `Email Us`,次按钮为 `Compare Editions`。
+
+桌面端采用左侧文案、右侧/居中偏右 Logo 粒子构图。Logo 不应被正文遮挡,文字区占页面宽度约 `38–44%`,空间舞台占 `56–62%`。
+
+首屏背景由近黑渐变、低对比网格、青蓝环境光晕和边缘暗角组成。网格与光晕是环境层,不随鼠标进行大幅移动。
+
+### 5.2 第二屏:Harness 价值与空间展开
+
+第二屏是本设计的关键说明屏,不允许只展示粒子动画。
+
+标题建议:
+
+> `插件构成能力,Harness 组织运行`
+
+说明建议:
+
+> `荣刻驭境把插件发现、装配、替换和协作放进同一套运行环境,让 Agent 能力可以持续扩展,而不必反复重建。`
+
+三张能力卡:
+
+1. `海量插件精准检索`:从定制插件数据库中按任务、能力、兼容性和工作空间策略检索合适插件。
+2. `更高效率热插拔`:在明确生命周期与依赖关系的前提下完成加载、卸载、替换和组合。
+3. `本地项目到共享协作`:个人版通过本地客户端操作本地项目;平台版让所有成员进入同一个共享工作空间协同。
+
+滚动进入第二屏后,Logo 的轮廓开始出现深度:外环向不同 Z 层展开,右上角品牌切面形成高亮近景粒子,部分粒子转化为插件节点。卡片依次进入,但不与粒子同时进行大幅运动。
+
+### 5.3 第三屏:定制插件引擎
+
+标题建议:`为插件规模化而优化的引擎`
+
+内容使用四步能力链:
+
+- `发现 / Discover`:连接定制插件数据库,建立能力、版本、依赖与兼容性索引。
+- `匹配 / Match`:结合任务语义、运行环境、权限和工作空间策略完成候选筛选。
+- `装配 / Compose`:按需加载、卸载和替换插件,降低重新构建 Agent 的成本。
+- `观测 / Observe`:记录插件状态、依赖、调用和异常,为团队复用与治理提供依据。
+
+视觉建议使用真实 UI 风格的信息卡、状态标签和节点连线,不使用无含义的伪监控数字。
+
+### 5.4 第四屏:个人版与平台版
+
+采用两张并列版本卡。平台版可以略大,但不使用“推荐”标签和价格信息;产品当前统一显示“即将开放”。
+
+| 能力 | 个人版 Personal | 平台版 Platform |
+| --- | --- | --- |
+| 部署对象 | 当前设备上的本地项目 | 需要共同协作的团队 |
+| 部署形态 | 本地客户端 + 浏览器界面 | 组织级平台服务 |
+| 定制插件引擎 | 精准检索、安装、卸载、替换与热插拔 | 同等引擎能力,并增加组织级策略 |
+| 插件数据库 | 本地项目索引、收藏与本地配置 | 团队共享目录、可见范围与版本管理 |
+| 工作空间 | 本地项目上下文 | 所有成员共享的团队工作空间 |
+| 成员与角色 | 本地客户端,不包含多人协同 | 成员、角色与协作权限 |
+| 配置复用 | 本地项目预设、导入与导出 | 团队预设、共享插件组合与配置 |
+| 运行记录 | 本地项目运行记录 | 团队共享活动与运行历史 |
+| 凭据边界 | 本地连接配置保留在当前设备 | 团队工作空间凭据引用,页面不回显明文 |
+| CTA | `邮件咨询个人版` | `邮件咨询平台版` |
+
+#### 5.4.1 首发能力决策
+
+**个人版首发范围**
+
+- 采用本地客户端形态,通过浏览器界面操作当前设备上的本地项目。
+- 完整使用定制插件引擎与定制插件数据库。
+- 支持插件语义检索、筛选、安装、卸载、替换、版本固定和热插拔。
+- 本地文件、运行环境、项目预设和运行记录保留在当前设备。
+- 不提供团队成员、角色、组织策略和跨用户共享。
+
+**平台版首发范围**
+
+- 面向组织部署,所有成员围绕同一个团队共享工作空间协作。
+- 通过成员与角色权限管理项目、策略、插件组合、共享配置和记录。
+- 支持组织级插件目录、团队可见范围、版本管理和基础允许列表。
+- 支持共享项目、团队预设、插件组合、配置、活动记录和运行上下文。
+- 支持工作空间级凭据引用;前端仅展示凭据名称和状态,不回显明文。
+
+**不纳入首发公开承诺**
+
+- SSO、SCIM、跨组织协作和复杂的字段级权限。
+- 多级插件审批流、企业审计导出和合规报表。
+- 商业计费、资源配额、用量结算与成本中心。
+- 多地域高可用、自动容灾和托管云服务等级协议。
+
+这些能力可以进入平台版后续路线图,但“即将开放”页面不将其包装为已具备能力。
+
+### 5.5 第五屏:使用路径
+
+标题建议:`从一个任务,生长为团队能力`
+
+内容顺序:
+
+1. 描述目标或任务。
+2. 检索并选择匹配插件。
+3. 在运行中装配或替换能力。
+4. 个人版将有效配置保存到本地项目;平台版同步到团队共享工作空间。
+
+该段用于把抽象的 Harness 转化为可理解的工作流。若后续已有真实产品截图,应优先替换概念图,不长期使用占位界面。
+
+#### 5.5.1 官方演示环境参考
+
+截至 2026-08-22,DeepSeek Harness 官方提供的可验证演示形态是本地 Web UI,而非一个无需配置即可登录的公共 SaaS 演示账号:安装 Node.js 后可运行 `npx @deepseek-ai/dsh web`,默认启动于 `http://127.0.0.1:3080`。官方使用流程是配置模型、选择工作空间、创建会话并运行任务。
+
+内部设计研究可参考以下官方材料:
+
+- [DeepSeek Harness 官方介绍页](https://www.deepseek.com/harness/)
+- [官方 Web UI 使用指南](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/guide/index.md)
+- [官方插件安装状态界面](https://www.deepseek.com/harness/images/harness/feat-plugin.en.png)
+- [官方运行轨迹界面](https://www.deepseek.com/harness/images/harness/trajectory-real-view.en.png)
+- [官方页面演示视频](https://www.deepseek.com/harness/videos/demo.mp4)
+
+荣刻驭境可借鉴其“配置模型 → 选择工作空间 → 运行任务 → 查看插件与运行记录”的信息组织,但对外演示必须使用荣刻自己的界面、品牌和数据。建议后续完成一个可运行的最小演示环境并截取四个真实状态:
+
+1. 在定制插件数据库中搜索并筛选插件。
+2. 查看已安装插件、依赖、版本和热插拔状态。
+3. 在本地项目与团队共享工作空间两种形态中验证配置和成员协作。
+4. 查看一次运行的插件调用与活动记录。
+
+在荣刻真实演示尚未完成前,官网保留文字能力卡和粒子视觉,不制作容易被误认为真实产品的伪控制台截图,也不直接把 DeepSeek 官方截图作为荣刻产品截图发布。
+
+### 5.6 第六屏:转化 CTA
+
+标题建议:`让下一套 Agent 能力,从可组合开始`
+
+- 主 CTA:`邮件咨询荣刻驭境`
+- 次 CTA:`咨询个人版 / 平台版`
+- 可选链接:`查看技术资料`,仅在资料真实可访问时展示。
+
+所有对外 CTA 统一打开 `contact@roncarve.com`。基础链接为 `mailto:contact@roncarve.com?subject=%5B%E8%8D%A3%E5%88%BB%E9%A9%AD%E5%A2%83%5D%20%E4%BA%A7%E5%93%81%E5%92%A8%E8%AF%A2`;版本卡分别追加“个人版”或“平台版”主题。邮件正文可预填以下字段:关注版本、使用场景、部署环境、团队规模和联系人。页面不再展示尚不存在的预约表单、企业微信或独立申请入口。
+
+## 6. 视觉系统
+
+### 6.1 与现有官网的关系
+
+沿用现有官网:
+
+- 蓝青主色、蓝紫辅助色和近黑背景。
+- `Inter / PingFang SC / Microsoft YaHei` 正文字体体系。
+- 等宽字体用于标签、状态和技术元信息。
+- `10px / 16px / 24px` 圆角层级。
+- 低透明边框、克制光晕、明确 `:focus-visible`。
+- 最大内容宽度 `1200px`,导航高度桌面 `72px`。
+
+产品页第一版建议固定为深色艺术方向,不提供浅色粒子主题。官网首页的主题偏好不被清除;进入产品页时可隐藏外观切换,仅保留语言切换,离开产品页后恢复用户原有偏好。这样避免产品页在视觉上承诺一个尚未设计的浅色 3D 场景。
+
+### 6.2 建议颜色令牌
+
+| 用途 | 令牌 | 建议值 |
+| --- | --- | --- |
+| 页面底色 | `--harness-bg` | `#05070A` |
+| 次级底色 | `--harness-bg-soft` | `#07131C` |
+| 表面 | `--harness-surface` | `rgba(13, 29, 41, .72)` |
+| 主文字 | `--harness-text` | `#F4FBFF` |
+| 次文字 | `--harness-text-soft` | `rgba(229, 244, 247, .66)` |
+| 主强调 | `--harness-cyan` | `#47E5EF` |
+| 高亮强调 | `--harness-cyan-bright` | `#8BFBFF` |
+| 深度蓝 | `--harness-blue` | `#42A5FF` |
+| 空间紫 | `--harness-violet` | `#8982FF` |
+| 边框 | `--harness-line` | `rgba(183, 229, 231, .13)` |
+| 强边框 | `--harness-line-strong` | `rgba(139, 251, 255, .34)` |
+
+粒子颜色以青蓝为主,紫色只用于深度和状态过渡。近景粒子更亮但不使用纯白大面积闪烁。
+
+### 6.3 背景与网格
+
+- 基础使用纵向近黑渐变,避免纯黑导致层次丢失。
+- 网格建议 `64–80px` 单元,线宽 `1px`,透明度控制在 `0.025–0.055`。
+- Hero 光晕集中在 Logo 后方和页面右上区域,正文后方保持较干净的暗色。
+- 使用 CSS mask 让网格在页面边缘、正文区域和段落过渡处自然淡出。
+- 内容卡片只使用局部玻璃效果,不在全页叠加大面积 `backdrop-filter`。
+
+### 6.4 字体与排版
+
+- H1 桌面:`clamp(46px, 5.2vw, 76px)`,字重 `650–750`,行高 `1.04–1.1`。
+- H2 桌面:`clamp(36px, 4vw, 58px)`。
+- 正文:桌面 `17–19px`,移动端 `16px`,最大行宽 `36–42em`。
+- 技术标签:`10–12px` 等宽字体,字距 `0.10–0.16em`。
+- 中英文名称第一次出现时并列;正文不反复堆叠双语,语言切换后使用单一语言。
+
+### 6.5 按钮与卡片
+
+- 主按钮使用青色实底和深色文字,延续现有官网 CTA。
+- 次按钮使用透明底、低透明边框;悬停时提高边框和背景亮度。
+- 卡片采用 `16px` 圆角、`1px` 边框和轻微内高光。
+- Hover 位移不超过 `4px`,粒子活跃时卡片动效进一步收敛。
+- 所有按钮必须有与 Hover 不同的键盘焦点状态。
+
+## 7. Logo 空间粒子体验
+
+### 7.1 粒子来源
+
+使用现有 `assets/brand-logo-final.png` 的透明通道作为采样掩模。不得凭空绘制近似 Logo。
+
+建议流程:
+
+1. 将 Logo 绘制到离屏 Canvas。
+2. 从 alpha 高于阈值的像素中进行分层采样。
+3. 生成每个粒子的二维 Logo 目标坐标 `aLogoPosition`。
+4. 按八个方位生成椭圆形远场入场坐标 `aIntroPosition`,并分配稳定的延迟和弧线路径。
+5. 为每个粒子生成稳定随机种子、深度目标、旋转半径和颜色权重。
+6. 使用统一的 `uIntro` 进度把粒子从远场坐标平滑汇聚到 Logo 目标坐标;汇聚完成前不启用滚动展开旋转。
+
+入场位置必须是稳定、可复现的 BufferGeometry 属性,不在每帧重新随机。粒子沿八个方向和不同 Z 深度进入,通过错峰延迟与阻尼缓动汇聚;动画完成后才以 Logo 目标坐标为基础叠加轻微漂浮,避免形成无序爆炸或加载即被 3D 投影打散的观感。
+
+### 7.2 滚动状态
+
+| 页面滚动进度 | 粒子混合 | 旋转 | 景深与大小 | 内容状态 |
+| ---: | --- | --- | --- | --- |
+| `0–0.06` | 100% 完整 Logo | 无持续旋转 | Z 接近平面,大小统一 | Hero 完整可读 |
+| `0.06–0.22` | Logo 轮廓轻微松动 | 仅轻微 Y 轴偏转 | 少量 Z 深度 | Hero 文案开始上移 |
+| `0.22–0.55` | 向插件节点空间展开 | X/Y/Z 三轴缓慢旋转 | 近大远小、近亮远暗 | 第二屏标题与卡片进入 |
+| `0.55–0.82` | 形成稳定空间网络 | 持续慢旋转 | 景深最明显 | 三项能力完整展示 |
+| `0.82–1.00` | 粒子向背景节点过渡 | 旋转减弱 | 大粒子数量下降 | 进入正文内容 |
+
+滚动进度只驱动“展开强度、空间位置、整体旋转基准、粒子尺寸范围和透明度”。时间 `uTime` 只负责持续但缓慢的微旋转与漂移,避免滚动停止后场景完全冻结。
+
+### 7.3 鼠标排斥
+
+- 仅在 `pointer: fine` 且未启用减少动态效果时开启。
+- 将鼠标位置投射到粒子主平面,计算每个粒子到指针的距离。
+- 在排斥半径内使用平滑衰减,不使用硬边界。
+- 靠近中心的粒子位移更大,边缘粒子渐近归零。
+- 指针离开舞台后,粒子使用阻尼回到当前滚动状态对应的目标,而不是回到页面顶部的 Logo 状态。
+- 排斥不改变卡片、文字和可点击元素的位置。
+
+建议 Shader 参数:
+
+- 排斥半径:桌面视觉半径约 `90–140px`。
+- 最大位移:Logo 尺寸的 `3–6%`。
+- 回弹阻尼:约 `350–650ms` 的视觉恢复感。
+
+### 7.4 点击重新聚拢
+
+- 点击粒子画布或非交互背景时触发;点击导航、链接、按钮、卡片和表单不得触发。
+- `uGather` 在约 `500–800ms` 内升至 1,使粒子重新聚拢为完整 Logo。
+- 保持约 `250–400ms` 后,`uGather` 回落,粒子返回“当前滚动进度”对应的空间状态。
+- 点击不强制滚动回顶部,不打断用户阅读位置。
+- 连续点击时复用当前动画,不创建叠加 tween。
+- 触屏设备可通过点击舞台触发同样效果,但不实现持续触摸排斥。
+
+### 7.5 透视、大小与亮度
+
+顶点 Shader 根据视图空间 Z 值计算点大小:远处粒子更小,近处粒子更大。片元 Shader 同时根据深度、Logo 权重和指针作用计算透明度与亮度。
+
+推荐属性:
+
+- `aLogoPosition`:Logo 聚拢位置。
+- `aIntroPosition`:首屏远场入场位置。
+- `aIntroDelay`:每个粒子的错峰入场延迟。
+- `aIntroBend`:入场弧线的方向与幅度。
+- `aSpreadPosition`:空间展开位置。
+- `aSeed`:稳定随机种子。
+- `aScale`:个体大小扰动。
+- `aColorMix`:青、蓝、紫的颜色混合权重。
+
+推荐 uniforms:
+
+- `uTime`:持续旋转与漂移。
+- `uIntro`:首屏从四周入场并汇聚为 Logo 的统一进度。
+- `uScroll`:前两屏滚动进度。
+- `uGather`:点击重新聚拢强度。
+- `uPointer`:标准化指针坐标。
+- `uRepelRadius` / `uRepelStrength`:排斥参数。
+- `uPixelRatio`:DPR 控制。
+- `uReducedMotion`:减少动态效果状态。
+
+点精灵使用柔和圆点或轻微六边形切面,边缘通过 `smoothstep` 淡出;避免每个粒子都是高亮十字星。
+
+### 7.6 初始加载保护
+
+为确保首屏是完整 Logo:
+
+- HTML 先渲染静态 Logo 轮廓或品牌 PNG。
+- WebGL 初始化完成后,粒子以与静态 Logo 完全一致的位置和尺寸出现。
+- 两层短暂交叉淡化,静态 Logo 再退出。
+- `uScroll` 初值强制为 0,并在读取实际滚动位置后再同步;若用户通过历史记录恢复到页面中部,则直接进入对应滚动状态,不播放首屏入场。
+- 相机初始视角、Logo 平面尺寸和容器比例必须预先确定,不能依赖第一帧之后的随机校正。
+
+## 8. 技术方案
+
+### 8.1 架构决策
+
+已确认引入 React/Vite 构建链。当前仓库首页仍使用原生 HTML、JavaScript 和本地 Three.js 文件;产品页采用隔离的 React/Vite 入口,构建结果输出到现有静态站点目录,不改写首页运行方式。首页导航只负责链接到产品路由,不加载 React、R3F 或 GSAP 包。
+
+推荐结构:
+
+```text
+src/harness/
+ main.tsx
+ HarnessProductPage.tsx
+ components/
+ ProductNav.tsx
+ ParticleStage.tsx
+ LogoParticleSystem.tsx
+ FeatureSection.tsx
+ EditionComparison.tsx
+ ProductCTA.tsx
+ shaders/
+ logo-particles.vert.glsl
+ logo-particles.frag.glsl
+ styles/
+ harness.css
+public/assets/harness/
+dist/products/roncarve-harness/
+```
+
+根目录新增统一的 `package.json` 与 Vite 配置,开发入口指向 `src/harness/`,生产构建固定输出 `dist/products/roncarve-harness/index.html` 及带内容哈希的资源文件。部署流程需要把该目录与现有首页静态文件一起发布。产品页只维护 React Three Fiber 场景,不再增加一套原生 Three.js 粒子实现。
+
+DeepSeek Harness 官方当前仍将上游标记为开发者预览,并明确提示可能出现破坏性兼容变更。产品研发应固定已验证的上游提交或发行版本,把定制插件引擎、工作空间扩展和上游适配层分开维护,并为升级建立兼容性测试,不直接让生产版本持续追踪上游 `master`。
+
+### 8.2 推荐技术栈
+
+- React:产品页组件与内容组织。
+- Three.js / React Three Fiber:粒子场景、相机和渲染循环。
+- 自定义 Shader:位置混合、排斥、深度尺寸、亮度和点精灵外观。
+- GSAP + ScrollTrigger:前两屏滚动进度、内容进入和场景参数编排。
+- CSS mask:网格渐隐、画布边缘暗角、章节过渡与局部光晕裁切。
+- 原生 CSS variables:复用荣刻品牌颜色、圆角、排版和响应式断点。
+
+### 8.3 组件职责
+
+| 组件 | 职责 |
+| --- | --- |
+| `HarnessProductPage` | 页面语义结构、语言和 CTA 数据 |
+| `ProductNav` | 产品页导航、返回首页、语言切换 |
+| `ParticleStage` | Canvas、相机、DPR、可见性和降级策略 |
+| `LogoParticleSystem` | Logo 采样、BufferGeometry、自定义 Shader、交互状态 |
+| `ScrollController` | 将 ScrollTrigger 进度写入 R3F/Shader 状态 |
+| `FeatureSection` | 第二屏及插件引擎内容 |
+| `EditionComparison` | 个人版/平台版对比与 CTA |
+| `ProductCTA` | 页面底部转化与联系渠道 |
+
+### 8.4 状态优先级
+
+粒子最终位置按以下优先级组合:
+
+1. 基础状态:由 `uScroll` 在 Logo 位置与空间位置之间混合。
+2. 时间状态:根据 `uTime` 添加旋转和微漂移,强度受 `uScroll` 控制。
+3. 指针状态:在基础目标上叠加局部排斥。
+4. 点击状态:`uGather` 临时覆盖前述展开与排斥,使粒子重新聚拢。
+
+这样可以保证点击反馈确定、滚动状态可恢复、指针离开后粒子不会回到错误位置。
+
+### 8.5 ScrollTrigger 编排
+
+- 前两屏外层使用约 `200–220svh` 的滚动容器。
+- 粒子舞台使用 `position: sticky; top: 0; height: 100svh`,文案层在其上方滚动。
+- 使用一个主 ScrollTrigger 计算归一化进度,避免多个 trigger 同时写入相同粒子属性。
+- 内容卡片可使用独立 trigger,但只控制 `opacity` 和 `transform`。
+- `scrub` 建议使用轻微阻尼,不追求完全逐像素同步。
+- 组件卸载时销毁 ScrollTrigger,防止重复注册。
+
+### 8.6 CSS mask 用法
+
+- 网格层:中心清晰、边缘淡出。
+- 粒子画布:正文侧增加暗色遮罩,保证文字对比度。
+- 第二屏过渡:让粒子从完整舞台逐步退到背景,而不是突然隐藏 Canvas。
+- 卡片高光:仅在上边缘或角部使用渐变 mask,不做全边框跑马灯。
+
+## 9. 响应式设计
+
+| 视口 | 布局 | 粒子策略 | 交互策略 |
+| --- | --- | --- | --- |
+| `≥ 1200px` | Hero 左文右景,内容宽 `1200px` | `28k–42k` 粒子,完整 Z 深度 | 鼠标排斥 + 点击聚拢 + 滚动旋转 |
+| `768–1199px` | 文案与粒子更接近 1:1 | `18k–28k` 粒子,降低近景尺寸 | 保留排斥,减小半径与旋转幅度 |
+| `480–767px` | 单列,文案在上、Logo 居中 | `8k–14k` 粒子,压缩 Z 深度 | 无悬停排斥,点击聚拢,滚动效果减弱 |
+| `< 480px` | 单列、按钮全宽或双行 | `6k–10k` 或静态回退 | 只保留轻量滚动过渡与点击反馈 |
+
+移动端注意:
+
+- 使用 `svh` 处理浏览器地址栏高度变化。
+- Logo 不能被标题和按钮完全覆盖。
+- 第二屏卡片纵向排列,先保证文案,再呈现空间效果。
+- 不使用横向滚动作为核心阅读方式。
+- 触摸滚动时不得因为 Canvas 指针事件造成页面无法滚动。
+
+## 10. 性能与降级
+
+### 10.1 性能目标
+
+- Hero 文案不等待 Three.js 或 Shader 加载即可显示。
+- 桌面主流设备目标为稳定接近 `60fps`;移动端目标不低于可感知流畅的 `30fps`。
+- DPR 桌面建议上限 `1.5`,移动端建议上限 `1.25`。
+- 粒子舞台离开可视区域后停止持续渲染或降到按需渲染。
+- 页面隐藏时暂停渲染。
+- Resize 使用 `ResizeObserver`,避免每帧读取布局。
+- 不开启 `preserveDrawingBuffer`,不使用大面积实时阴影和后期模糊。
+
+### 10.2 自适应质量
+
+启动时根据视口、DPR、`prefers-reduced-motion` 和基础设备能力选择质量档位:
+
+- High:完整粒子数量、景深和颜色层次。
+- Medium:减少粒子与旋转幅度,关闭昂贵混合。
+- Low:静态 Logo + CSS 光晕 + 轻量内容入场。
+
+不通过读取敏感或不稳定的设备信息做复杂设备指纹判断。
+
+### 10.3 WebGL 失败回退
+
+- 保留静态品牌 Logo PNG。
+- 背景使用 CSS 网格、径向光晕和渐变。
+- 页面内容、导航、CTA 和版本对比不依赖 Canvas。
+- 发生 `webglcontextlost` 时切换回静态视觉,不反复重建渲染器。
+
+## 11. 无障碍与可用性
+
+- Canvas 使用 `aria-hidden="true"`;所有产品信息由 HTML 文本提供。
+- 提供“跳到主要内容”链接。
+- 导航产品菜单支持键盘打开、方向移动、`Esc` 关闭和清晰焦点。
+- 普通正文对比度目标至少达到 WCAG AA。
+- `prefers-reduced-motion: reduce` 下:关闭粒子旋转、鼠标排斥、滚动 scrub 和大幅位移,显示完整静态 Logo。
+- 不使用快速闪烁、高频缩放和突然的相机推进。
+- 点击聚拢只绑定非交互背景,不抢占按钮和链接事件。
+- 所有 CTA 文案应明确说明动作,不使用只有“了解更多”的模糊文本作为唯一按钮。
+- 语言切换后同步更新 `lang`、title、description、导航和按钮文案。
+
+## 12. SEO、分析与合规
+
+### 12.1 SEO
+
+建议中文 title:
+
+`荣刻驭境 Roncarve Harness|插件驱动的 Agent 运行环境 - 荣刻科技`
+
+建议中文 description:
+
+`荣刻驭境是一套面向大语言模型与 Agent 的可组合 Harness,通过定制插件引擎提供精准插件检索、高效热插拔,支持浏览器操作本地项目的个人客户端与团队共享工作空间。`
+
+同时补充:
+
+- canonical:`https://www.roncarve.com/products/roncarve-harness/`
+- Open Graph 图片:后续制作独立产品分享图,不直接截取动态粒子帧。
+- JSON-LD:根据实际发布形态选择 `SoftwareApplication` 或 `Product`,不填写未经确认的价格和评分。
+- sitemap 新增产品页 URL。
+
+### 12.2 建议分析事件
+
+- `product_menu_open`
+- `product_card_click`,参数 `product=roncarve_harness`
+- `harness_email_click`,参数 `placement=hero|personal|platform|footer`
+- `harness_edition_view`
+- `harness_personal_cta_click`
+- `harness_platform_cta_click`
+
+粒子悬停和每次点击聚拢不建议上报,避免制造高频、低价值事件。
+
+### 12.3 品牌与许可边界
+
+- 页面可以说明产品的技术来源,但不得使用 DeepSeek 品牌视觉或暗示官方联合、授权或背书。
+- 已确认上游允许二次开发;截至 2026-08-22,DeepSeek Harness 官方仓库采用 [MIT License](https://github.com/deepseek-ai/deepseek-harness/blob/master/LICENSE)。
+- 发布物必须保留上游版权声明和 MIT 许可文本,并建立第三方依赖许可证清单。
+- MIT 许可不等同于商标授权。产品页在“定制插件引擎”内容区展示“基于开源 DeepSeek Harness 二次开发的独立产品”,不在页脚重复来源说明;页脚仅保留公司、版权与备案信息。
+- “PLUGIN IS ALL YOU NEED”作为首屏滚动展开后第二幕的核心口号,与“插件构成能力,Harness 组织运行”的价值主张共同出现。
+- 页面状态固定为“即将开放”;不使用 DeepSeek 的“开发者预览”状态,也不在当前阶段宣称荣刻驭境已开源。
+- “兼容某模型”、性能提升比例和插件数量仍需测试数据支撑后才能进入对外文案。
+
+## 13. 验收标准
+
+### 13.1 导航与路由
+
+- [ ] 首页主导航存在“产品 / Products”。
+- [ ] 桌面端产品菜单可点击、可键盘操作、可按 `Esc` 关闭。
+- [ ] 移动端产品列表可折叠展开。
+- [ ] 点击“荣刻驭境”进入 `/products/roncarve-harness/`。
+- [ ] 产品页 Logo 可返回官网首页,“产品”保持激活态。
+
+### 13.2 首屏粒子
+
+- [ ] 初始加载呈现完整荣刻 Logo,不从随机散点开始。
+- [ ] 未滚动时不发生明显 3D 展开或大幅旋转。
+- [ ] 开始滚动后,粒子逐渐出现 Z 深度、透视缩放和空间旋转。
+- [ ] 远处粒子更小、更暗;近处粒子更大、更亮。
+- [ ] 粒子持续缓慢空间旋转,但内容阅读不受干扰。
+- [ ] 鼠标靠近产生平滑排斥,离开后回到当前滚动目标。
+- [ ] 点击非交互背景时重新聚拢,再恢复当前滚动状态。
+
+### 13.3 内容与第二屏
+
+- [ ] 第二屏明确呈现精准检索、热插拔、个人/平台双版本三项价值。
+- [ ] 第二屏进入与粒子展开同步,但文字不依赖 Canvas 才能显示。
+- [ ] 后续内容解释插件引擎、版本差异、工作空间和使用路径。
+- [ ] Hero、版本卡和页尾 CTA 均可打开 `contact@roncarve.com`,并带有正确的邮件主题。
+
+### 13.4 响应式与无障碍
+
+- [ ] 在 `375px`、`768px`、`1280px`、`1440px` 视口检查布局。
+- [ ] 移动端滚动不被 Canvas 阻塞。
+- [ ] 减少动态效果模式显示完整静态 Logo,无持续旋转与排斥。
+- [ ] WebGL 不可用时页面仍完整可读、可导航、可转化。
+- [ ] 所有交互元素具有清晰 Hover、Active 和 Focus 状态。
+
+### 13.5 性能与稳定性
+
+- [ ] Canvas 离开可视区域后暂停或显著降频。
+- [ ] 页面隐藏时暂停渲染。
+- [ ] 重复进入/离开页面不会叠加 ScrollTrigger 或事件监听器。
+- [ ] Resize、语言切换和返回历史位置后粒子状态正确。
+- [ ] WebGL context 丢失后能安全降级。
+
+## 14. 实施顺序
+
+### 阶段 A:入口与静态内容
+
+- 新增“产品”导航与产品下拉列表。
+- 建立 `/products/roncarve-harness/` 路由。
+- 完成产品页语义结构、双语文案、CTA、版本对比与静态视觉。
+- 更新 SEO、sitemap 和分享信息。
+
+### 阶段 B:粒子原型
+
+- 以真实品牌 Logo alpha mask 生成粒子。
+- 先完成“首屏完整聚拢 → 滚动展开 → 第二屏稳定空间状态”。
+- 再增加景深尺寸、亮度、持续旋转和画布遮罩。
+
+### 阶段 C:交互与降级
+
+- 增加鼠标排斥与点击重新聚拢。
+- 增加触屏策略、减少动态效果和 WebGL 回退。
+- 完成性能档位与可见性暂停。
+
+### 阶段 D:视觉与体验验收
+
+- 同视口比较设计目标与实现截图。
+- 检查卡片间距、文字行宽、光晕强度、Logo 比例和滚动节奏。
+- 完成键盘、移动端、低性能和 context lost 测试。
+
+## 15. 设计决策记录
+
+| 编号 | 原待确认项 | 最终决策 | 对实现的影响 |
+| ---: | --- | --- | --- |
+| 1 | CTA 目标 | 仅使用 `contact@roncarve.com` | 所有主要 CTA 使用带预填主题的 `mailto:`,不建设表单 |
+| 2 | 产品状态 | 即将开放 | 中英文统一为“即将开放 / Coming Soon”,不提供下载或立即体验按钮 |
+| 3 | 版本能力边界 | 采用第 5.4 节首发方案 | 个人版聚焦本地客户端和本地项目;平台版聚焦团队共享工作空间、成员协同、共享配置和活动记录 |
+| 4 | 演示环境 | 参考 DeepSeek Harness 官方 Web UI | 研究插件状态、工作空间和运行轨迹;对外只使用荣刻自有界面 |
+| 5 | 上游许可 | 已确认允许二次开发 | 保留 MIT 许可与版权声明,并避免商标或官方背书暗示 |
+| 6 | 前端构建链 | 引入 React/Vite | 产品页使用 R3F、Shader 和 GSAP;首页继续保持原生静态实现 |
+
+当前设计已无阻塞性待确认项。开发阶段若真实产品能力与第 5.4 节不一致,应先调整公开文案和版本表,再进入上线验收。
+
+## 16. 参考页观察摘要
+
+本设计对 DeepSeek Harness 页面的借鉴限于设计方法:
+
+- 首屏在深色空间背景中直接提出一句核心产品主张。
+- 首屏提供明确行动按钮,而不是把视觉效果当作唯一内容。
+- 第二屏马上解释 `MODEL + HARNESS` 的关系,并用三张卡片拆解能力。
+- 页面使用近黑背景、低透明表面、细边框、胶囊按钮、网格和少量蓝色强调。
+- 长页通过连续章节解释设计理念、运行模式和开始使用方式。
+
+荣刻驭境的差异化处理:
+
+- 用荣刻真实 Logo 作为粒子聚拢目标。
+- 视觉重点从“插件构成一切”转向“本地项目控制、精准插件检索、高效热插拔和团队共享协同”。
+- 沿用荣刻官网蓝青紫色与现有组件语言。
+- 第二屏直接连接个人版的本地项目形态与平台版的共享协同价值,强化商业产品定位。
+- 采用独立文案、独立结构和独立粒子运动逻辑,不复刻参考页资产。