Compare commits

...
8 Commits
31 changed files with 4196 additions and 22 deletions
+10 -1
View File
@@ -1,8 +1,17 @@
.git .git
.gitignore
.idea .idea
qa-evidence qa-evidence
design-references design-references
.docker-config
.npm-cache
.npm-cache-*
node_modules
dist
design-qa-artifacts
deploy
Dockerfile Dockerfile
docker-compose.yml
.dockerignore .dockerignore
*.md *.md
docker-compose*.yml
Caddyfile.server
+7
View File
@@ -43,3 +43,10 @@ buildNumber.properties
# JDT-specific (Eclipse Java Development Tools) # JDT-specific (Eclipse Java Development Tools)
.classpath .classpath
node_modules/
dist/
.npm-cache/
.npm-cache-*/
.docker-config/
design-qa-artifacts/
design-qa.md
+11 -2
View File
@@ -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 nginx.conf /etc/nginx/conf.d/default.conf
COPY . /usr/share/nginx/html COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80 EXPOSE 80
+11
View File
@@ -2,12 +2,23 @@
海南荣刻科技有限公司官方网站。 海南荣刻科技有限公司官方网站。
## 本地预览
```bash
docker compose up -d --build
```
- 官网首页:`http://127.0.0.1:8081/`
- 荣刻驭境:`http://127.0.0.1:8081/products/roncarve-harness/`
- 箴言助手:`http://127.0.0.1:8081/products/chat-assistant/`
## 百度收录上线清单 ## 百度收录上线清单
站点已经在代码中提供以下收录基础文件: 站点已经在代码中提供以下收录基础文件:
- `https://www.roncarve.com/robots.txt`:允许 `Baiduspider` 和其他搜索引擎抓取。 - `https://www.roncarve.com/robots.txt`:允许 `Baiduspider` 和其他搜索引擎抓取。
- `https://www.roncarve.com/sitemap.xml`:提交首页规范 URL。 - `https://www.roncarve.com/sitemap.xml`:提交首页规范 URL。
- `https://roncarve.com/WW_verify_kMPlH8gCU5u7a4LQ.txt`:企业微信域名验证文件随站点镜像发布。
- 首页包含中文标题、描述、规范 URL、结构化数据和可见的“海南荣刻科技有限公司”全称。 - 首页包含中文标题、描述、规范 URL、结构化数据和可见的“海南荣刻科技有限公司”全称。
- `Caddyfile.server` 为主站配置 HTTPS 安全响应头;Caddy 会为 `www.roncarve.com` 自动申请和续期证书。 - `Caddyfile.server` 为主站配置 HTTPS 安全响应头;Caddy 会为 `www.roncarve.com` 自动申请和续期证书。
+1
View File
@@ -0,0 +1 @@
kMPlH8gCU5u7a4LQ
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

+1
View File
@@ -0,0 +1 @@
94f23ded66ca5b5c5003812faeacf00d
+3
View File
@@ -1,2 +1,5 @@
MARIADB_ROOT_PASSWORD=replace-with-a-long-random-password MARIADB_ROOT_PASSWORD=replace-with-a-long-random-password
MARIADB_PASSWORD=replace-with-another-long-random-password MARIADB_PASSWORD=replace-with-another-long-random-password
GITEA_MAILER_FROM=noreply@git.roncarve.com
GITEA_MAILER_USER=noreply@git.roncarve.com
GITEA_MAILER_PASSWORD=replace-with-tencent-cloud-smtp-password
+7
View File
@@ -21,7 +21,14 @@ services:
GITEA__database__NAME: gitea GITEA__database__NAME: gitea
GITEA__database__USER: gitea GITEA__database__USER: gitea
GITEA__database__PASSWD: ${MARIADB_PASSWORD} GITEA__database__PASSWD: ${MARIADB_PASSWORD}
GITEA__mailer__ENABLED: "true"
GITEA__mailer__PROTOCOL: smtps GITEA__mailer__PROTOCOL: smtps
GITEA__mailer__SMTP_ADDR: smtp.qcloudmail.com
GITEA__mailer__SMTP_PORT: "465"
GITEA__mailer__FROM: '"代码协作平台-海南荣刻科技有限公司" <${GITEA_MAILER_FROM}>'
GITEA__mailer__ENVELOPE_FROM: ${GITEA_MAILER_FROM}
GITEA__mailer__USER: ${GITEA_MAILER_USER}
GITEA__mailer__PASSWD: ${GITEA_MAILER_PASSWORD}
ports: ports:
- "2222:22" - "2222:22"
volumes: volumes:
@@ -0,0 +1,49 @@
海南荣刻科技有限公司|账号信息
---
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="format-detection" content="telephone=no,date=no,address=no,email=no,url=no">
<title>账号信息</title>
</head>
<body style="margin:0;padding:24px;background:#f5f7fa;color:#1f2937;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Microsoft YaHei',Arial,sans-serif;line-height:1.7;">
{{$set_pwd_url := printf "%[1]suser/forgot_password" AppUrl}}
<div style="max-width:620px;margin:0 auto;background:#ffffff;border:1px solid #e5e7eb;border-radius:10px;padding:32px;">
<h2 style="margin:0 0 24px;color:#111827;font-size:22px;">账号信息</h2>
<p>{{.DisplayName}},您好:</p>
<p>海南荣刻科技有限公司已为您开通代码协作平台账号。</p>
<p>
用户名:<strong>{{.Username}}</strong>
</p>
<p>请点击下面的链接设置登录密码:</p>
<p>
<a href="{{$set_pwd_url}}" style="display:inline-block;padding:10px 18px;border-radius:6px;background:#2563eb;color:#ffffff;text-decoration:none;">完成账号设置</a>
</p>
<p style="word-break:break-all;color:#4b5563;font-size:13px;">{{$set_pwd_url}}</p>
<p>完成账号设置后,可通过以下地址访问平台:</p>
<p>
<a href="{{AppUrl}}user/login">{{AppUrl}}user/login</a>
</p>
<p style="margin-top:28px;color:#6b7280;font-size:13px;">
如果您并未预期收到此邮件,请联系管理员确认。<br>
本邮件由系统自动发送,请勿直接回复。
</p>
<p style="margin:24px 0 0;color:#374151;">
海南荣刻科技有限公司<br>
代码协作平台<br>
{{AppUrl}}
</p>
</div>
</body>
</html>
@@ -0,0 +1,38 @@
海南荣刻科技有限公司|账号访问链接
---
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="format-detection" content="telephone=no,date=no,address=no,email=no,url=no">
<title>账号访问链接</title>
</head>
<body style="margin:0;padding:24px;background:#f5f7fa;color:#1f2937;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Microsoft YaHei',Arial,sans-serif;line-height:1.7;">
{{$recover_url := printf "%suser/recover_account?code=%s" AppUrl (QueryEscape .Code)}}
<div style="max-width:620px;margin:0 auto;background:#ffffff;border:1px solid #e5e7eb;border-radius:10px;padding:32px;">
<h2 style="margin:0 0 24px;color:#111827;font-size:22px;">账号访问链接</h2>
<p>{{.DisplayName}},您好:</p>
<p>我们收到了一次账号访问设置请求。</p>
<p>请在 {{.ResetPwdCodeLives}} 内点击下面的链接完成设置:</p>
<p>
<a href="{{$recover_url}}" style="display:inline-block;padding:10px 18px;border-radius:6px;background:#2563eb;color:#ffffff;text-decoration:none;">继续操作</a>
</p>
<p style="word-break:break-all;color:#4b5563;font-size:13px;">{{$recover_url}}</p>
<p style="margin-top:24px;color:#6b7280;font-size:13px;">
如果您没有发起本次请求,可以忽略此邮件。为保障账号安全,请勿将此链接转发给他人。
</p>
<p style="margin:24px 0 0;color:#374151;">
海南荣刻科技有限公司<br>
代码协作平台<br>
{{AppUrl}}
</p>
</div>
</body>
</html>
+6
View File
@@ -5,3 +5,9 @@ services:
restart: unless-stopped restart: unless-stopped
ports: ports:
- "127.0.0.1:8081:80" - "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
+120 -18
View File
@@ -233,14 +233,62 @@
.nav-link:hover, .nav-link.active { color: var(--white); } .nav-link:hover, .nav-link.active { color: var(--white); }
.nav-link.active::after { opacity: 1; transform: translateX(-50%) scale(1); } .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 + .product-card { margin-top: 8px; }
.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; } .nav-actions { display: flex; align-items: center; gap: 10px; }
.preference-menu { position: relative; } .preference-menu { position: relative; }
.preference-trigger { .preference-trigger {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 8px; justify-content: center;
width: 38px;
min-height: 38px; min-height: 38px;
padding: 0 11px; padding: 0;
border: 1px solid var(--line); border: 1px solid var(--line);
border-radius: 9px; border-radius: 9px;
background: rgba(255, 255, 255, .025); background: rgba(255, 255, 255, .025);
@@ -264,8 +312,6 @@
font: 500 17px/1 "Segoe UI Symbol", "Noto Sans Symbols", sans-serif; font: 500 17px/1 "Segoe UI Symbol", "Noto Sans Symbols", sans-serif;
} }
.preference-icon--globe { font-size: 16px; } .preference-icon--globe { font-size: 16px; }
.preference-chevron { color: var(--text-faint); font: 700 13px/1 var(--mono); transition: transform .2s ease; }
.preference-menu.open .preference-chevron { transform: rotate(180deg); }
.preference-popover { .preference-popover {
position: absolute; position: absolute;
top: calc(100% + 10px); top: calc(100% + 10px);
@@ -661,7 +707,13 @@
.solution-group-code { display: block; margin-bottom: 28px; color: var(--solution-accent, var(--cyan)); font: 650 10px/1 var(--mono); letter-spacing: .12em; } .solution-group-code { display: block; margin-bottom: 28px; color: var(--solution-accent, var(--cyan)); font: 650 10px/1 var(--mono); letter-spacing: .12em; }
.solution-group h3 { margin-bottom: 12px; color: var(--white); font-size: 34px; line-height: 1.2; letter-spacing: -.04em; } .solution-group h3 { margin-bottom: 12px; color: var(--white); font-size: 34px; line-height: 1.2; letter-spacing: -.04em; }
.solution-group-desc { margin-bottom: 0; color: var(--text-soft); font-size: 14px; line-height: 1.7; } .solution-group-desc { margin-bottom: 0; color: var(--text-soft); font-size: 14px; line-height: 1.7; }
.solution-items { display: grid; gap: 10px; margin-top: auto; } .solution-items {
display: grid;
flex: 1;
grid-template-rows: repeat(3, minmax(min-content, 1fr));
gap: 10px;
margin-top: auto;
}
.solution-item { padding: 17px 18px 18px; border: 1px solid var(--line); border-radius: var(--radius-md); background: rgba(0, 0, 0, .13); transition: transform .25s var(--ease), border-color .25s ease, background .25s ease; } .solution-item { padding: 17px 18px 18px; border: 1px solid var(--line); border-radius: var(--radius-md); background: rgba(0, 0, 0, .13); transition: transform .25s var(--ease), border-color .25s ease, background .25s ease; }
.solution-item:hover { transform: translateX(4px); border-color: color-mix(in srgb, var(--solution-accent, var(--cyan)) 42%, transparent); background: rgba(255, 255, 255, .045); } .solution-item:hover { transform: translateX(4px); border-color: color-mix(in srgb, var(--solution-accent, var(--cyan)) 42%, transparent); background: rgba(255, 255, 255, .045); }
.solution-item-code { display: block; margin-bottom: 15px; color: var(--solution-accent, var(--cyan)); font: 700 10px/1 var(--mono); letter-spacing: .12em; } .solution-item-code { display: block; margin-bottom: 15px; color: var(--solution-accent, var(--cyan)); font: 700 10px/1 var(--mono); letter-spacing: .12em; }
@@ -713,6 +765,8 @@
.footer-inner > .footer-record-company { display: none; } .footer-inner > .footer-record-company { display: none; }
.footer-signal { color: var(--text-faint); font: 550 9px/1 var(--mono); letter-spacing: .1em; } .footer-signal { color: var(--text-faint); font: 550 9px/1 var(--mono); letter-spacing: .1em; }
.footer-record { color: var(--text-faint); font-size: 12px; transition: color .2s ease; } .footer-record { color: var(--text-faint); font-size: 12px; transition: color .2s ease; }
.footer-record-police { display: inline-flex; align-items: center; gap: 4px; }
.footer-record-police img { width: 18px; height: 20px; object-fit: contain; }
.footer-record-company { margin-left: 8px; } .footer-record-company { margin-left: 8px; }
.footer-record:hover { color: var(--cyan); } .footer-record:hover { color: var(--cyan); }
@@ -782,13 +836,6 @@
.hero-spatial { width: min(100%, 760px); min-height: 420px; margin-left: auto; } .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) { @media (max-width: 860px) {
:root[data-theme="light"] .nav-links { background: rgba(250, 252, 255, .98); } :root[data-theme="light"] .nav-links { background: rgba(250, 252, 255, .98); }
} }
@@ -857,6 +904,24 @@
.nav-links.open { opacity: 1; pointer-events: auto; transform: translateY(0); } .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 { padding: 14px 6px; border-bottom: 1px solid var(--line); font-size: 15px; }
.nav-link::after { display: none; } .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: 560px; 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-actions { padding-top: 14px; justify-content: space-between; }
.nav-cta { min-height: 42px; } .nav-cta { min-height: 42px; }
.hero { padding-top: 126px; } .hero { padding-top: 126px; }
@@ -916,6 +981,7 @@
.services-layout { grid-template-columns: 1fr; } .services-layout { grid-template-columns: 1fr; }
.solution-group { min-height: 0; padding: 24px; } .solution-group { min-height: 0; padding: 24px; }
.solution-group-head { min-height: 0; margin-bottom: 28px; } .solution-group-head { min-height: 0; margin-bottom: 28px; }
.solution-items { flex: none; grid-template-rows: none; }
.solution-group-code { margin-bottom: 20px; } .solution-group-code { margin-bottom: 20px; }
.solution-group h3 { font-size: 29px; } .solution-group h3 { font-size: 29px; }
.proof-grid { grid-template-columns: 1fr; } .proof-grid { grid-template-columns: 1fr; }
@@ -952,6 +1018,23 @@
</a> </a>
<div class="nav-links" id="navLinks"> <div class="nav-links" id="navLinks">
<div class="product-nav" id="productNav">
<button class="nav-link product-trigger" id="productTrigger" type="button" aria-haspopup="menu" aria-expanded="false" aria-controls="productPopover" data-i18n="nav_products">产品</button>
<div class="product-popover" id="productPopover" role="menu" aria-label="产品列表" data-i18n-aria="product_list_label">
<a class="product-card" href="/products/roncarve-harness/" role="menuitem">
<span class="product-card-meta"><span>AGENT INFRA / HARNESS</span><strong data-i18n="product_status">即将开放</strong></span>
<h3 data-i18n="product_harness_name">荣刻驭境</h3>
<small>RONCARVE HARNESS</small>
<p data-i18n="product_harness_desc">本地客户端连接本地项目,平台共享工作空间承载团队协同,并内置精准插件检索与高效热插拔。</p>
</a>
<a class="product-card" href="/products/chat-assistant/" role="menuitem">
<span class="product-card-meta"><span>DOMAIN AI / ADVISORY</span><strong data-i18n="product_assistant_status">产品演示</strong></span>
<h3 data-i18n="product_assistant_name">箴言助手</h3>
<small>CHAT ASSISTANT</small>
<p data-i18n="product_assistant_desc">连接企业数据、领域知识与业务工具,为垂直行业提供有依据、可落地、可追溯的智能建议。</p>
</a>
</div>
</div>
<a class="nav-link" href="#about" data-i18n="nav_about">能力链路</a> <a class="nav-link" href="#about" data-i18n="nav_about">能力链路</a>
<a class="nav-link" href="#services" data-i18n="nav_services">解决方案</a> <a class="nav-link" href="#services" data-i18n="nav_services">解决方案</a>
<a class="nav-link" href="#advantages" data-i18n="nav_advantages">核心优势</a> <a class="nav-link" href="#advantages" data-i18n="nav_advantages">核心优势</a>
@@ -960,8 +1043,6 @@
<div class="preference-menu" id="themeSwitch"> <div class="preference-menu" id="themeSwitch">
<button class="preference-trigger" type="button" aria-haspopup="menu" aria-expanded="false" aria-label="外观模式" data-i18n-aria="theme_label"> <button class="preference-trigger" type="button" aria-haspopup="menu" aria-expanded="false" aria-label="外观模式" data-i18n-aria="theme_label">
<span class="preference-icon" data-theme-current-icon aria-hidden="true"></span> <span class="preference-icon" data-theme-current-icon aria-hidden="true"></span>
<span class="preference-trigger-label" data-theme-current>Auto</span>
<span class="preference-chevron" aria-hidden="true"></span>
</button> </button>
<div class="preference-popover" role="menu"> <div class="preference-popover" role="menu">
<button class="preference-item" type="button" role="menuitemradio" aria-checked="true" data-theme-option="system" aria-label="跟随系统" data-i18n-aria="theme_system"> <button class="preference-item" type="button" role="menuitemradio" aria-checked="true" data-theme-option="system" aria-label="跟随系统" data-i18n-aria="theme_system">
@@ -978,8 +1059,6 @@
<div class="preference-menu" id="langSwitch"> <div class="preference-menu" id="langSwitch">
<button class="preference-trigger" type="button" aria-haspopup="menu" aria-expanded="false" aria-label="语言切换" data-i18n-aria="lang_label"> <button class="preference-trigger" type="button" aria-haspopup="menu" aria-expanded="false" aria-label="语言切换" data-i18n-aria="lang_label">
<span class="preference-icon preference-icon--globe" aria-hidden="true"></span> <span class="preference-icon preference-icon--globe" aria-hidden="true"></span>
<span class="preference-trigger-label" data-language-current>简体中文</span>
<span class="preference-chevron" aria-hidden="true"></span>
</button> </button>
<div class="preference-popover" role="menu"> <div class="preference-popover" role="menu">
<button class="preference-item" type="button" role="menuitemradio" aria-checked="true" data-lang="zh"> <button class="preference-item" type="button" role="menuitemradio" aria-checked="true" data-lang="zh">
@@ -1166,6 +1245,7 @@
<div class="footer-company"> <div class="footer-company">
<span class="footer-copyright">© <span id="year-copyright">2026</span> <span data-i18n="footer_company">Roncarve Technology</span>. All Rights Reserved.</span> <span class="footer-copyright">© <span id="year-copyright">2026</span> <span data-i18n="footer_company">Roncarve Technology</span>. All Rights Reserved.</span>
<a class="footer-record footer-record-company" href="https://beian.miit.gov.cn/" target="_blank" rel="noopener noreferrer">琼ICP备2026011549号</a> <a class="footer-record footer-record-company" href="https://beian.miit.gov.cn/" target="_blank" rel="noopener noreferrer">琼ICP备2026011549号</a>
<a class="footer-record footer-record-company footer-record-police" href="https://www.beian.gov.cn/portal/registerSystemInfo?recordcode=46010002001924" target="_blank" rel="noopener noreferrer"><img src="assets/beian-police.png" alt="公安备案图标">琼公网安备46010002001924号</a>
</div> </div>
<a class="footer-record footer-record-company" href="https://beian.miit.gov.cn/" target="_blank" rel="noopener noreferrer">琼ICP备2026011549号</a> <a class="footer-record footer-record-company" href="https://beian.miit.gov.cn/" target="_blank" rel="noopener noreferrer">琼ICP备2026011549号</a>
<span>© <span id="year">2026</span> <span data-i18n="footer_company">海南荣刻科技有限公司</span></span> <span>© <span id="year">2026</span> <span data-i18n="footer_company">海南荣刻科技有限公司</span></span>
@@ -1184,7 +1264,7 @@
zh: { zh: {
title: '海南荣刻科技有限公司 - 数据筑基 · AI 落地', skip: '跳到主要内容', brand: '荣刻科技', nav_label: '主导航', home_label: '荣刻科技首页', lang_label: '语言切换', menu_open: '打开导航菜单', menu_close: '关闭导航菜单', 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', 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: '本地客户端连接本地项目,平台共享工作空间承载团队协同,并内置精准插件检索与高效热插拔。', product_assistant_status: '产品演示', product_assistant_name: '箴言助手', product_assistant_desc: '连接企业数据、领域知识与业务工具,为垂直行业提供有依据、可落地、可追溯的智能建议。', nav_about: '能力链路', nav_services: '解决方案', nav_advantages: '核心优势', nav_offices: '交付网络', nav_contact: '预约咨询',
hero_eyebrow: '海南荣刻科技有限公司 · AI 与数据基础设施', hero_line1: '让企业数据', hero_line2: '成为 AI 生产力', hero_eyebrow: '海南荣刻科技有限公司 · AI 与数据基础设施', hero_line1: '让企业数据', hero_line2: '成为 AI 生产力',
hero_desc: '从数据治理、模型优化到私有化部署,荣刻科技为企业构建可落地、可持续、可掌控的 AI 能力体系。', hero_desc: '从数据治理、模型优化到私有化部署,荣刻科技为企业构建可落地、可持续、可掌控的 AI 能力体系。',
hero_primary: '预约 AI 场景诊断', hero_secondary: '查看解决方案', proof_data: '7 年数据技术积累', proof_private: '支持私有化部署', proof_delivery: '成都 / 海口双城交付', hero_primary: '预约 AI 场景诊断', hero_secondary: '查看解决方案', proof_data: '7 年数据技术积累', proof_private: '支持私有化部署', proof_delivery: '成都 / 海口双城交付',
@@ -1218,7 +1298,7 @@
en: { 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', 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', 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.', product_assistant_status: 'Product Demo', product_assistant_name: 'Chat Assistant', product_assistant_desc: 'Connect enterprise data, domain knowledge and business tools for grounded, actionable and traceable vertical-industry advice.', 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_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_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', 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',
@@ -1254,6 +1334,8 @@
const navbar = document.getElementById('navbar'); const navbar = document.getElementById('navbar');
const menuBtn = document.getElementById('menuBtn'); const menuBtn = document.getElementById('menuBtn');
const navLinks = document.getElementById('navLinks'); const navLinks = document.getElementById('navLinks');
const productNav = document.getElementById('productNav');
const productTrigger = document.getElementById('productTrigger');
const langSwitch = document.getElementById('langSwitch'); const langSwitch = document.getElementById('langSwitch');
const themeSwitch = document.getElementById('themeSwitch'); const themeSwitch = document.getElementById('themeSwitch');
const themeQuery = window.matchMedia('(prefers-color-scheme: dark)'); const themeQuery = window.matchMedia('(prefers-color-scheme: dark)');
@@ -1333,6 +1415,12 @@
menuBtn.setAttribute('aria-expanded', 'false'); menuBtn.setAttribute('aria-expanded', 'false');
menuBtn.setAttribute('aria-label', i18n[currentLanguage].menu_open); menuBtn.setAttribute('aria-label', i18n[currentLanguage].menu_open);
document.body.classList.remove('menu-open'); document.body.classList.remove('menu-open');
closeProductMenu();
}
function closeProductMenu() {
productNav.classList.remove('open');
productTrigger.setAttribute('aria-expanded', 'false');
} }
function closePreferenceMenus(except) { function closePreferenceMenus(except) {
@@ -1348,12 +1436,24 @@
const trigger = menu.querySelector('.preference-trigger'); const trigger = menu.querySelector('.preference-trigger');
trigger.addEventListener('click', function () { trigger.addEventListener('click', function () {
const open = !menu.classList.contains('open'); const open = !menu.classList.contains('open');
closeProductMenu();
closePreferenceMenus(menu); closePreferenceMenus(menu);
menu.classList.toggle('open', open); menu.classList.toggle('open', open);
trigger.setAttribute('aria-expanded', String(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) { langSwitch.addEventListener('click', function (event) {
const button = event.target.closest('[data-lang]'); const button = event.target.closest('[data-lang]');
if (!button) return; if (!button) return;
@@ -1390,10 +1490,12 @@
if (event.key === 'Escape') { if (event.key === 'Escape') {
closeMenu(); closeMenu();
closePreferenceMenus(); closePreferenceMenus();
closeProductMenu();
} }
}); });
document.addEventListener('click', function (event) { document.addEventListener('click', function (event) {
if (!event.target.closest('.preference-menu')) closePreferenceMenus(); 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 }); window.addEventListener('scroll', function () { navbar.classList.toggle('scrolled', window.scrollY > 24); }, { passive: true });
+14
View File
@@ -5,10 +5,24 @@ server {
root /usr/share/nginx/html; root /usr/share/nginx/html;
index index.html; index index.html;
location = /products/roncarve-harness {
return 301 /products/roncarve-harness/;
}
location = /products/chat-assistant {
absolute_redirect off;
return 301 /products/chat-assistant/;
}
location / { location / {
try_files $uri $uri/ /index.html; 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?)$ { location ~* \.(?:css|js|png|jpg|jpeg|gif|svg|webp|ico|woff2?)$ {
expires 7d; expires 7d;
add_header Cache-Control "public, max-age=604800, immutable"; add_header Cache-Control "public, max-age=604800, immutable";
+1115
View File
File diff suppressed because it is too large Load Diff
+22
View File
@@ -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"
}
}
+40
View File
@@ -0,0 +1,40 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="theme-color" content="#f7f9fc">
<meta name="description" content="箴言助手(Chat Assistant)连接企业数据、领域知识与业务工具,为保险、制造、零售及专业服务等垂直领域提供有依据、可落地、可追溯的智能建议。">
<meta name="keywords" content="箴言助手,Chat Assistant,行业智能助手,企业AI助手,智能建议,领域知识,数据接入,私有化部署">
<link rel="canonical" href="https://www.roncarve.com/products/chat-assistant/">
<link rel="icon" type="image/png" href="/assets/brand-logo-final.png">
<meta property="og:type" content="website">
<meta property="og:locale" content="zh_CN">
<meta property="og:title" content="箴言助手 Chat Assistant|让行业经验成为每一次决策的底气">
<meta property="og:description" content="连接企业数据与领域知识,提供有依据、可落地、可追溯的智能建议。">
<meta property="og:url" content="https://www.roncarve.com/products/chat-assistant/">
<meta property="og:image" content="https://www.roncarve.com/assets/chat-assistant-hero-v1.jpg">
<title>箴言助手 Chat Assistant|垂直行业智能建议助手 - 荣刻科技</title>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "箴言助手",
"alternateName": "Chat Assistant",
"applicationCategory": "BusinessApplication",
"operatingSystem": "Web, Private Deployment",
"description": "连接企业数据、领域知识与业务工具,为垂直行业提供有依据、可落地、可追溯的智能建议。",
"url": "https://www.roncarve.com/products/chat-assistant/",
"publisher": {
"@type": "Organization",
"name": "海南荣刻科技有限公司",
"url": "https://www.roncarve.com/"
}
}
</script>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/chat-assistant/main.jsx"></script>
</body>
</html>
+40
View File
@@ -0,0 +1,40 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="theme-color" content="#05070a">
<meta name="description" content="荣刻驭境是一套面向大语言模型与 Agent 的可组合 Harness,个人版通过本地客户端在浏览器中操作本地项目,平台版提供团队共享工作空间。">
<meta name="keywords" content="荣刻驭境,Roncarve Harness,Agent Harness,插件引擎,插件检索,热插拔,工作空间">
<link rel="canonical" href="https://www.roncarve.com/products/roncarve-harness/">
<link rel="icon" type="image/png" href="/assets/brand-logo-final.png">
<meta property="og:type" content="website">
<meta property="og:locale" content="zh_CN">
<meta property="og:title" content="荣刻驭境 Roncarve Harness|插件驱动的 Agent 运行环境">
<meta property="og:description" content="精准检索、高效热插拔,从浏览器操作本地项目到团队共享工作空间。">
<meta property="og:url" content="https://www.roncarve.com/products/roncarve-harness/">
<meta property="og:image" content="https://www.roncarve.com/assets/brand-logo-final.png">
<title>荣刻驭境 Roncarve Harness|插件驱动的 Agent 运行环境 - 荣刻科技</title>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "荣刻驭境",
"alternateName": "Roncarve Harness",
"applicationCategory": "DeveloperApplication",
"operatingSystem": "Web, Local Client, Private Deployment",
"description": "面向大语言模型与 Agent 的可组合 Harness,个人版通过本地客户端在浏览器中操作本地项目,平台版提供团队共享工作空间。",
"url": "https://www.roncarve.com/products/roncarve-harness/",
"publisher": {
"@type": "Organization",
"name": "海南荣刻科技有限公司",
"url": "https://www.roncarve.com/"
}
}
</script>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/harness/main.jsx"></script>
</body>
</html>
+29
View File
@@ -0,0 +1,29 @@
import { cp, copyFile, mkdir, readFile, writeFile } from 'node:fs/promises';
import { resolve } from 'node:path';
const root = process.cwd();
const output = resolve(root, 'dist');
const wecomVerification = (await readFile(
resolve(root, 'WW_verify_kMPlH8gCU5u7a4LQ.txt'),
'utf8'
)).trim();
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')),
writeFile(
resolve(output, 'WW_verify_kMPlH8gCU5u7a4LQ.txt'),
wecomVerification,
'utf8'
),
copyFile(
resolve(root, 'baidu_verify_codeva-Jnq0QS7SFC.html'),
resolve(output, 'baidu_verify_codeva-Jnq0QS7SFC.html')
)
]);
+13 -1
View File
@@ -2,8 +2,20 @@
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url> <url>
<loc>https://www.roncarve.com/</loc> <loc>https://www.roncarve.com/</loc>
<lastmod>2026-08-16</lastmod> <lastmod>2026-08-22</lastmod>
<changefreq>monthly</changefreq> <changefreq>monthly</changefreq>
<priority>1.0</priority> <priority>1.0</priority>
</url> </url>
<url>
<loc>https://www.roncarve.com/products/roncarve-harness/</loc>
<lastmod>2026-08-22</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://www.roncarve.com/products/chat-assistant/</loc>
<lastmod>2026-09-11</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
</urlset> </urlset>
@@ -0,0 +1,135 @@
import {
ArrowRight, Brain, ChartLineUp, CheckCircle, CirclesFour,
Database, Factory, List, LockKey, MagnifyingGlass, ShieldCheck,
ShoppingCart, Stack, UserFocus, X
} from '@phosphor-icons/react';
import { useEffect, useMemo, useState } from 'react';
const zhIndustries = [
{ id: 'insurance', label: '保险', icon: ShieldCheck, title: '让保障建议更精准、更可信', description: '联动客户需求、保单信息、产品条款与服务记录,在数据权限和合规策略约束下辅助顾问组织保障建议。', evidence: ['客户需求与家庭画像', '保单、产品与条款事实', '合规表达与适当性策略'], action: '识别保障缺口,生成匹配客户目标的建议,并明确依据、适用条件与下一步沟通重点。' },
{ id: 'manufacturing', label: '制造', icon: Factory, title: '把经验沉淀进生产与供应链判断', description: '接入生产、质量、库存与供应商数据,融合工艺知识和处置规则,辅助团队识别风险与改进路径。', evidence: ['生产与质量数据', '工艺知识与异常规则', '供应链历史案例'], action: '定位异常的可能原因,给出处置顺序、验证条件与协同建议。' },
{ id: 'retail', label: '零售', icon: ShoppingCart, title: '把门店与商品信号转成运营动作', description: '连接商品、库存、会员和营销数据,结合区域经验与运营策略,辅助门店形成可执行方案。', evidence: ['商品与库存状态', '会员与渠道行为', '区域运营策略'], action: '形成选品、补货与活动建议,并说明适用门店和预期目标。' },
{ id: 'services', label: '专业服务', icon: UserFocus, title: '让专业判断有章可循、有据可查', description: '将项目材料、专业知识、服务流程与质量标准组织起来,辅助团队提高交付一致性。', evidence: ['项目与客户材料', '专业方法与知识库', '交付规范和检查项'], action: '生成结构化判断、工作建议与待核验事项,帮助团队稳步推进。' }
];
const enIndustries = [
{ id: 'insurance', label: 'Insurance', icon: ShieldCheck, title: 'Make protection advice precise and trustworthy', description: 'Connect customer needs, policies, product terms and service history under data-access and compliance controls.', evidence: ['Customer needs and household context', 'Policy, product and coverage facts', 'Compliance and suitability policies'], action: 'Identify protection gaps and propose suitable next steps with clear evidence and conditions.' },
{ id: 'manufacturing', label: 'Manufacturing', icon: Factory, title: 'Bring experience into supply-chain decisions', description: 'Connect production, quality, inventory and supplier data with process knowledge.', evidence: ['Production and quality data', 'Process knowledge and rules', 'Supply-chain cases'], action: 'Prioritize likely causes, verification steps and cross-team actions.' },
{ id: 'retail', label: 'Retail', icon: ShoppingCart, title: 'Turn store and product signals into action', description: 'Combine merchandise, inventory, membership and campaign data with regional playbooks.', evidence: ['Merchandise and inventory', 'Member and channel behavior', 'Regional playbooks'], action: 'Propose assortment, replenishment and campaign actions for the right stores.' },
{ id: 'services', label: 'Professional Services', icon: UserFocus, title: 'Make expert judgement consistent and traceable', description: 'Organize project materials, specialist knowledge, service workflows and quality standards.', evidence: ['Project and client materials', 'Methods and knowledge base', 'Delivery standards'], action: 'Produce structured findings, recommendations and open verification items.' }
];
const copy = {
zh: {
brand: '荣刻科技', product: '箴言助手', navCapabilities: '核心能力', navIndustries: '行业扩展', navTrust: '安全可信', navCta: '预约演示', language: 'EN', menu: '打开导航', close: '关闭导航',
eyebrow: 'CHAT ASSISTANT / 箴言助手', titleLine1: '让行业经验,', titleLine2: '成为', titleAccent: '每一次决策的底气',
summary: '连接企业数据与领域知识,基于可信的信息来源和真实业务经验,为你提供有依据、可落地的智能建议,让复杂问题更快找到确定的答案。', primary: '预约产品演示', secondary: '了解核心能力',
proof: ['连接企业数据', '融合领域知识', '给出有依据的建议'], visualAlt: '箴言助手基于企业数据与领域知识生成建议的产品界面',
capabilitiesKicker: '核心能力', capabilitiesTitle: '六大能力,构建企业的专属智能伙伴', capabilitiesSummary: '从数据连接到行业落地,箴言助手把建议生成所需的上下文、工具与治理能力组织成一套可持续运营的系统。',
capabilities: [
['企业数据连接', '连接业务系统与数据库,以受控范围、安全字段和运行时查询构建事实底座。'],
['领域知识融合', '将行业经验沉淀为技能、意图、工具、知识与表达策略,按场景组合调用。'],
['智能分析建议', '结合会话上下文与工具事实,形成面向具体对象、具体目标的可执行建议。'],
['可信来源追溯', '记录意图、技能、工具调用、模型响应与关键依据,支持分析、审计和回放。'],
['行业场景扩展', '替换数据、知识、工具与策略资源,即可从保险扩展到更多专业领域。'],
['安全合规保障', '通过统一身份、角色权限、数据范围、字段脱敏与追加式审计守住边界。']
],
systemKicker: 'DOMAIN INTELLIGENCE SYSTEM', systemTitle: ['行业能力,', '可以持续生长'], systemSummary: '以保险顾问服务为示例,将客户需求、产品条款、权益与服务记录组织为建议依据;通用资源体系让同一套助手能够适配不同领域的业务对象、知识结构与工作方法。', industryLabel: '选择行业示例', industryEvidence: '建议依据', industryAction: '示例建议', industries: zhIndustries,
processKicker: 'FROM CONTEXT TO ACTION', processTitle: '从业务信息,到可执行建议', processSummary: '每次回答都经历清晰的连接、理解、建议与追溯路径,让行业经验真正进入业务流程。',
process: [['01', '连接', '接入业务系统、知识资源与模型服务,划定数据范围和使用边界。'], ['02', '理解', '识别意图、上下文与业务对象,选择匹配的技能、工具和表达策略。'], ['03', '建议', '基于工具事实和行业经验生成建议,明确依据、条件与下一步行动。'], ['04', '追溯', '保留会话、资源版本、工具调用、耗时与结果,支持复盘和持续优化。']],
trustKicker: 'SECURE BY DESIGN', trustTitle: ['建议可以智能,', '边界必须清晰'], trustSummary: '箴言助手以企业身份、数据权限和可审计运行作为产品底座。模型负责理解与表达,关键事实来自受控数据与业务工具。',
trustItems: [['统一身份与明确授权', '接入 Roncarve IAM OIDC,以稳定外部身份绑定账号,通过显式应用准入和角色授权控制访问。'], ['数据范围与字段保护', '数据源、对象范围和字段元数据独立管理,运行时按权限查询并对敏感字段实施脱敏。'], ['全链路指标与审计', '会话指标、Agent 选择、工具调用和管理操作均可查询,支持完整性校验与问题回放。']],
ctaKicker: 'CHAT ASSISTANT / PRODUCT DEMO', ctaTitle: ['从一个真实场景开始,', '让行业经验进入每一次决策'], ctaSummary: '告诉我们你的业务对象、数据基础、专业流程与安全要求,我们将一起梳理最适合的箴言助手落地路径。', ctaButton: '预约产品演示', copyright: '海南荣刻科技有限公司', footerSignal: '数据驱动 · AI 赋能 · 安全交付', record: '琼ICP备2025052166号-1', policeAlt: '公安备案图标'
},
en: {
brand: 'Roncarve Tech', product: 'Chat Assistant', navCapabilities: 'Capabilities', navIndustries: 'Industries', navTrust: 'Trust', navCta: 'Book a Demo', language: '中文', menu: 'Open navigation', close: 'Close navigation',
eyebrow: 'CHAT ASSISTANT', titleLine1: 'Turn domain experience', titleLine2: 'into confidence for', titleAccent: 'every decision', summary: 'Connect enterprise data with domain knowledge to deliver grounded, actionable advice from trusted information and real operating experience.', primary: 'Book a Product Demo', secondary: 'Explore Capabilities', proof: ['Connect enterprise data', 'Apply domain knowledge', 'Ground every recommendation'], visualAlt: 'Chat Assistant product interface producing advice from enterprise data and domain knowledge',
capabilitiesKicker: 'CORE CAPABILITIES', capabilitiesTitle: 'Six capabilities for a dedicated enterprise intelligence partner', capabilitiesSummary: 'From governed data access to vertical delivery, Chat Assistant organizes the context, tools and controls required for dependable advice.',
capabilities: [['Enterprise Data', 'Connect systems and databases through governed scopes, protected fields and runtime queries.'], ['Domain Knowledge', 'Organize expertise as skills, intents, tools, knowledge and expression policies.'], ['Actionable Advice', 'Combine conversation context with tool-grounded facts for specific business objectives.'], ['Traceable Sources', 'Record intent, skills, tool calls, model responses and evidence for analysis and replay.'], ['Vertical Expansion', 'Adapt to a new industry by replacing data, knowledge, tools and policy resources.'], ['Security & Compliance', 'Protect enterprise boundaries with SSO, roles, scopes, masking and append-only audit.']],
systemKicker: 'DOMAIN INTELLIGENCE SYSTEM', systemTitle: ['Industry capability', 'grows with your business.'], systemSummary: 'Using insurance advisory as an example, the experience organizes customer needs, product terms, benefits and service context into grounded advice. The same resource model adapts to new objects, knowledge and operating methods.', industryLabel: 'Choose an example industry', industryEvidence: 'Evidence', industryAction: 'Example outcome', industries: enIndustries,
processKicker: 'FROM CONTEXT TO ACTION', processTitle: 'From business context to actionable advice', processSummary: 'Every response follows a clear connect, understand, advise and trace path.', process: [['01', 'Connect', 'Connect systems, knowledge and model services with explicit data boundaries.'], ['02', 'Understand', 'Resolve intent, context and entities, then select relevant skills, tools and policies.'], ['03', 'Advise', 'Use tool-grounded facts and domain experience to explain evidence and next actions.'], ['04', 'Trace', 'Preserve conversations, resource versions, tool calls, latency and outcomes.']],
trustKicker: 'SECURE BY DESIGN', trustTitle: ['Advice can be intelligent.', 'Boundaries must stay explicit.'], trustSummary: 'Enterprise identity, data authorization and observable operation are part of the product foundation. Models interpret and communicate; governed data and tools provide the facts.', trustItems: [['Identity and explicit access', 'Integrate Roncarve IAM OIDC with stable external identity binding, explicit application access and role grants.'], ['Data scope and field protection', 'Manage sources, object scopes and field metadata separately, enforcing access and masking at runtime.'], ['Metrics and audit end to end', 'Query conversation metrics, Agent selection, tool use and admin operations with integrity checks and replay.']],
ctaKicker: 'CHAT ASSISTANT / PRODUCT DEMO', ctaTitle: ['Start with one real scenario.', 'Put domain experience into every decision.'], ctaSummary: 'Tell us about your business entities, data foundation, specialist workflow and security needs. We will shape the right delivery path together.', ctaButton: 'Book a Product Demo', copyright: 'Hainan Roncarve Technology Co., Ltd', footerSignal: 'DATA-DRIVEN · AI-POWERED · SECURE DELIVERY', record: 'Qiong ICP No. 2025052166-1', policeAlt: 'Public security filing icon'
}
};
const capabilityIcons = [Database, Brain, ChartLineUp, MagnifyingGlass, CirclesFour, ShieldCheck];
const processIcons = [Database, Brain, ChartLineUp, Stack];
const trustIcons = [LockKey, ShieldCheck, MagnifyingGlass];
function mailto(language) {
const subject = language === 'zh' ? '[箴言助手] 产品演示预约' : '[Chat Assistant] Product demo request';
const body = language === 'zh' ? '您好,我想预约箴言助手产品演示。\n\n所属行业:\n业务场景:\n现有数据/系统:\n部署要求:\n联系人:' : 'Hello, I would like to book a Chat Assistant demo.\n\nIndustry:\nUse case:\nCurrent data/systems:\nDeployment needs:\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 <nav className="assistant-nav" aria-label={language === 'zh' ? '箴言助手产品导航' : 'Chat Assistant product navigation'}>
<div className="assistant-shell assistant-nav-inner">
<a className="assistant-brand" href="/" aria-label={language === 'zh' ? '返回荣刻科技首页' : 'Back to Roncarve home'}><img src="/assets/brand-logo-final.png" alt="" /><span><strong>{t.brand}</strong><small>RONCARVE TECHNOLOGY</small></span></a>
<button className="assistant-menu" type="button" onClick={() => setOpen(!open)} aria-expanded={open} aria-controls="assistantNavLinks" aria-label={open ? t.close : t.menu}>{open ? <X size={22} /> : <List size={22} />}</button>
<div className={`assistant-nav-links ${open ? 'is-open' : ''}`} id="assistantNavLinks">
<a href="#top" onClick={close}>{t.product}</a><a href="#capabilities" onClick={close}>{t.navCapabilities}</a><a href="#industries" onClick={close}>{t.navIndustries}</a><a href="#trust" onClick={close}>{t.navTrust}</a>
<button className="assistant-language" type="button" onClick={() => setLanguage(language === 'zh' ? 'en' : 'zh')}>{t.language}</button>
<a className="assistant-nav-cta" href={mailto(language)} onClick={close}>{t.navCta}<ArrowRight size={16} weight="bold" /></a>
</div>
</div>
</nav>;
}
function IndustryExplorer({ t }) {
const [activeId, setActiveId] = useState(t.industries[0].id);
useEffect(() => setActiveId(t.industries[0].id), [t]);
const active = t.industries.find((item) => item.id === activeId) ?? t.industries[0];
const ActiveIcon = active.icon;
return <div className="industry-explorer">
<div className="industry-tab-label">{t.industryLabel}</div>
<div className="industry-tabs" role="tablist" aria-label={t.industryLabel}>{t.industries.map((industry) => { const Icon = industry.icon; const selected = industry.id === active.id; return <button key={industry.id} type="button" role="tab" aria-selected={selected} className={selected ? 'is-active' : ''} onClick={() => setActiveId(industry.id)}><Icon size={19} weight="duotone" />{industry.label}</button>; })}</div>
<div className="industry-detail" role="tabpanel" aria-live="polite">
<div className="industry-detail-heading"><span><ActiveIcon size={26} weight="duotone" /></span><div><small>{active.label}</small><h3>{active.title}</h3></div></div>
<p>{active.description}</p>
<div className="industry-detail-grid"><div><strong>{t.industryEvidence}</strong><ul>{active.evidence.map((item) => <li key={item}><CheckCircle size={16} weight="fill" />{item}</li>)}</ul></div><div><strong>{t.industryAction}</strong><p>{active.action}</p></div></div>
</div>
</div>;
}
export function ChatAssistantProductPage() {
const [language, setLanguageState] = useState(() => { try { return localStorage.getItem('site_lang') === 'en' ? 'en' : 'zh'; } catch { return 'zh'; } });
const t = copy[language];
const meta = useMemo(() => language === 'zh' ? { title: '箴言助手 Chat Assistant|垂直行业智能建议助手 - 荣刻科技', description: '箴言助手连接企业数据、领域知识与业务工具,为垂直行业提供有依据、可落地、可追溯的智能建议。' } : { title: 'Chat Assistant | Vertical Industry Advice from Roncarve', description: 'Chat Assistant connects enterprise data, domain knowledge and business tools to deliver grounded, actionable and traceable advice.' }, [language]);
const setLanguage = (next) => { setLanguageState(next); try { localStorage.setItem('site_lang', next); } catch { /* non-critical */ } };
useEffect(() => { document.documentElement.lang = language === 'zh' ? 'zh-CN' : 'en'; document.title = meta.title; document.querySelector('meta[name="description"]')?.setAttribute('content', meta.description); }, [language, meta]);
useEffect(() => {
const items = Array.from(document.querySelectorAll('[data-reveal]'));
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { items.forEach((item) => item.classList.add('is-visible')); return undefined; }
const observer = new IntersectionObserver((entries) => entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.classList.add('is-visible'); observer.unobserve(entry.target); } }), { threshold: .13 });
items.forEach((item) => observer.observe(item));
return () => observer.disconnect();
}, [language]);
return <div className="assistant-page">
<a className="assistant-skip" href="#main">{language === 'zh' ? '跳到主要内容' : 'Skip to main content'}</a>
<ProductNav language={language} setLanguage={setLanguage} />
<main id="main">
<section className="assistant-hero" id="top"><div className="assistant-shell assistant-hero-grid">
<div className="assistant-hero-copy" data-reveal><p className="assistant-eyebrow">{t.eyebrow}</p><h1><span>{t.titleLine1}</span><span>{t.titleLine2}{language === 'en' ? ' ' : ''}<em>{t.titleAccent}</em></span></h1><p className="assistant-hero-summary">{t.summary}</p><div className="assistant-hero-actions"><a className="assistant-button-primary" href={mailto(language)}>{t.primary}<ArrowRight size={18} weight="bold" /></a><a className="assistant-button-secondary" href="#capabilities">{t.secondary}</a></div><ul className="assistant-proof">{t.proof.map((item) => <li key={item}><span />{item}</li>)}</ul></div>
<figure className="assistant-hero-visual" data-reveal><img src="/assets/chat-assistant-hero-v1.jpg" alt={t.visualAlt} /><figcaption><span>KNOWLEDGE</span><span>CONNECTS</span><span>PEOPLE</span><span>AND</span><span>POSSIBILITIES</span></figcaption></figure>
</div></section>
<section className="assistant-capabilities" id="capabilities"><div className="assistant-shell"><header className="assistant-section-center" data-reveal><span className="assistant-kicker">{t.capabilitiesKicker}</span><h2>{t.capabilitiesTitle}</h2><p>{t.capabilitiesSummary}</p></header><div className="capability-grid" data-reveal>{t.capabilities.map(([title, description], index) => { const Icon = capabilityIcons[index]; return <article key={title}><span className="capability-icon"><Icon size={25} weight="duotone" /></span><h3>{title}</h3><p>{description}</p></article>; })}</div></div></section>
<section className="assistant-system" id="industries"><div className="assistant-shell assistant-system-grid"><header className="assistant-section-left" data-reveal><span className="assistant-kicker">{t.systemKicker}</span><h2>{t.systemTitle.map((line) => <span key={line}>{line}</span>)}</h2><p>{t.systemSummary}</p></header><div data-reveal><IndustryExplorer t={t} /></div></div></section>
<section className="assistant-process" id="process"><div className="assistant-shell"><header className="assistant-section-split" data-reveal><div><span className="assistant-kicker">{t.processKicker}</span><h2>{t.processTitle}</h2></div><p>{t.processSummary}</p></header><div className="process-grid" data-reveal>{t.process.map(([number, title, description], index) => { const Icon = processIcons[index]; return <article key={number}><span className="process-number">{number}</span><span className="process-icon"><Icon size={24} weight="duotone" /></span><h3>{title}</h3><p>{description}</p></article>; })}</div></div></section>
<section className="assistant-trust" id="trust"><div className="assistant-shell assistant-trust-grid"><header className="assistant-section-left" data-reveal><span className="assistant-kicker">{t.trustKicker}</span><h2>{t.trustTitle.map((line) => <span key={line}>{line}</span>)}</h2><p>{t.trustSummary}</p></header><div className="trust-list" data-reveal>{t.trustItems.map(([title, description], index) => { const Icon = trustIcons[index]; return <article key={title}><span><Icon size={23} weight="duotone" /></span><div><h3>{title}</h3><p>{description}</p></div></article>; })}</div></div></section>
<section className="assistant-cta"><div className="assistant-shell assistant-cta-inner" data-reveal><span className="assistant-kicker">{t.ctaKicker}</span><h2>{t.ctaTitle.map((line) => <span key={line}>{line}</span>)}</h2><p>{t.ctaSummary}</p><a className="assistant-button-primary" href={mailto(language)}>{t.ctaButton}<ArrowRight size={18} weight="bold" /></a></div></section>
</main>
<footer className="assistant-footer"><div className="assistant-shell assistant-footer-row"><div className="assistant-footer-company"><span>© 20252026 {t.copyright}</span><a href="https://beian.miit.gov.cn/" target="_blank" rel="noreferrer">{t.record}</a><a className="assistant-footer-police" href="https://beian.mps.gov.cn/#/query/webSearch" target="_blank" rel="noreferrer"><img src="/assets/beian-police.png" alt={t.policeAlt} />琼公网安备 46010602000704</a></div><span className="assistant-footer-signal">{t.footerSignal}</span></div></footer>
</div>;
}
+219
View File
@@ -0,0 +1,219 @@
:root {
--ca-bg: #fff;
--ca-surface: #f6f8fc;
--ca-surface-blue: #edf4ff;
--ca-ink: #07152f;
--ca-text: #3f4f6a;
--ca-muted: #71809a;
--ca-line: #dce3ee;
--ca-blue: #0b63f6;
--ca-blue-strong: #004ed5;
--ca-mint: #13a98c;
--ca-shell: min(1320px, calc(100vw - 72px));
--ca-display: Inter, "PingFang SC", "Microsoft YaHei", ui-sans-serif, system-ui, sans-serif;
--ca-mono: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
--ca-ease: cubic-bezier(.2, .75, .2, 1);
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; scroll-padding-top: 84px; }
body { margin: 0; min-width: 320px; overflow-x: hidden; background: var(--ca-bg); color: var(--ca-ink); font-family: var(--ca-display); -webkit-font-smoothing: antialiased; }
body, button, input { font-family: var(--ca-display); }
a { color: inherit; text-decoration: none; }
button { color: inherit; }
img { display: block; max-width: 100%; }
::selection { color: #fff; background: var(--ca-blue); }
:focus-visible { outline: 3px solid rgba(11, 99, 246, .35); outline-offset: 3px; }
.assistant-page { min-height: 100vh; background: var(--ca-bg); }
.assistant-shell { width: var(--ca-shell); margin-inline: auto; }
.assistant-skip { position: fixed; z-index: 3000; top: 12px; left: 12px; padding: 10px 15px; border-radius: 8px; color: #fff; background: var(--ca-blue); transform: translateY(-150%); transition: transform .2s; }
.assistant-skip:focus { transform: translateY(0); }
.assistant-nav { position: fixed; z-index: 1000; inset: 0 0 auto; height: 76px; border-bottom: 1px solid rgba(220, 227, 238, .88); background: rgba(255, 255, 255, .94); box-shadow: 0 8px 30px rgba(42, 64, 103, .035); backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px); }
.assistant-nav-inner { height: 100%; display: flex; align-items: center; justify-content: space-between; gap: 28px; }
.assistant-brand { display: inline-flex; align-items: center; gap: 11px; flex: 0 0 auto; }
.assistant-brand img { width: 39px; height: 39px; object-fit: contain; }
.assistant-brand > span { display: flex; flex-direction: column; gap: 4px; }
.assistant-brand strong { font-size: 16px; font-weight: 780; letter-spacing: .035em; }
.assistant-brand small { color: var(--ca-muted); font: 600 8px/1 var(--ca-mono); letter-spacing: .15em; }
.assistant-nav-links { display: flex; align-items: center; gap: 28px; }
.assistant-nav-links > a:not(.assistant-nav-cta) { position: relative; padding-block: 10px; font-size: 14px; font-weight: 650; }
.assistant-nav-links > a:not(.assistant-nav-cta)::after { content: ""; position: absolute; inset: auto 0 2px; height: 2px; background: var(--ca-blue); transform: scaleX(0); transform-origin: left; transition: transform .2s ease; }
.assistant-nav-links > a:not(.assistant-nav-cta):hover::after { transform: scaleX(1); }
.assistant-language { min-height: 38px; padding: 0 12px; border: 1px solid var(--ca-line); border-radius: 8px; background: #fff; font-size: 12px; font-weight: 700; cursor: pointer; }
.assistant-language:hover { border-color: #aab9d0; background: var(--ca-surface); }
.assistant-nav-cta, .assistant-button-primary { display: inline-flex; align-items: center; justify-content: center; gap: 10px; min-height: 46px; padding: 0 20px; border: 1px solid var(--ca-blue); border-radius: 9px; color: #fff; background: var(--ca-blue); font-size: 14px; font-weight: 760; box-shadow: 0 10px 28px rgba(11, 99, 246, .17); transition: transform .2s var(--ca-ease), background .2s ease, box-shadow .2s ease; }
.assistant-nav-cta:hover, .assistant-button-primary:hover { background: var(--ca-blue-strong); transform: translateY(-2px); box-shadow: 0 15px 36px rgba(11, 99, 246, .23); }
.assistant-menu { display: none; place-items: center; width: 42px; height: 42px; border: 1px solid var(--ca-line); border-radius: 9px; background: #fff; cursor: pointer; }
.assistant-hero { padding: 110px 0 20px; overflow: hidden; background: #fff; }
.assistant-hero-grid { display: grid; grid-template-columns: minmax(510px, .94fr) minmax(0, 1.06fr); align-items: start; gap: 32px; }
.assistant-hero-copy { padding-top: 35px; }
.assistant-eyebrow, .assistant-kicker { display: inline-flex; align-items: center; gap: 12px; margin: 0; color: var(--ca-blue); font: 750 11px/1.4 var(--ca-mono); letter-spacing: .15em; text-transform: uppercase; }
.assistant-eyebrow::after { content: ""; width: 34px; height: 1px; background: var(--ca-blue); }
.assistant-hero h1 { margin: 28px 0 0; font-size: clamp(48px, 4.1vw, 62px); line-height: 1.05; letter-spacing: -.07em; }
.assistant-hero h1 > span { display: block; }
.assistant-hero h1 em { color: var(--ca-blue); font-style: normal; }
.assistant-hero-summary { max-width: 500px; margin: 30px 0 0; color: var(--ca-text); font-size: 17px; line-height: 1.85; }
.assistant-hero-actions { display: flex; gap: 12px; margin-top: 34px; }
.assistant-button-secondary { display: inline-flex; align-items: center; justify-content: center; min-height: 46px; padding: 0 20px; border: 1px solid #c8d3e2; border-radius: 9px; color: var(--ca-ink); background: #fff; font-size: 14px; font-weight: 750; transition: border-color .2s ease, background .2s ease, transform .2s var(--ca-ease); }
.assistant-button-secondary:hover { border-color: #9badc5; background: var(--ca-surface); transform: translateY(-2px); }
.assistant-proof { display: flex; flex-wrap: wrap; gap: 12px 24px; margin: 28px 0 0; padding: 0; color: var(--ca-text); list-style: none; }
.assistant-proof li { display: inline-flex; align-items: center; gap: 9px; font-size: 12px; font-weight: 620; }
.assistant-proof li span { width: 6px; height: 6px; border-radius: 50%; background: var(--ca-blue); }
.assistant-hero-visual { position: relative; margin: 0; overflow: hidden; border: 1px solid #dbe3ee; border-radius: 25px; background: var(--ca-surface); box-shadow: 0 35px 90px rgba(48, 70, 109, .13); }
.assistant-hero-visual::after { content: ""; position: absolute; inset: 0; border-radius: inherit; box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .7); pointer-events: none; }
.assistant-hero-visual img { width: 100%; aspect-ratio: 1.31; object-fit: cover; object-position: center; }
.assistant-hero-visual figcaption { position: absolute; right: 15px; top: 50%; display: flex; flex-direction: column; gap: 4px; color: #697994; font: 600 7px/1.2 var(--ca-mono); letter-spacing: .08em; transform: translateY(-50%); }
.assistant-capabilities { padding: 38px 0 124px; border-top: 1px solid #eef1f6; background: var(--ca-bg); }
.assistant-section-center { max-width: 780px; margin: 0 auto 58px; text-align: center; }
.assistant-section-center h2, .assistant-section-left h2, .assistant-section-split h2, .assistant-cta h2 { margin: 20px 0 0; font-size: clamp(38px, 4vw, 58px); line-height: 1.12; letter-spacing: -.055em; }
.assistant-section-center > p, .assistant-section-left > p, .assistant-section-split > p, .assistant-cta p { color: var(--ca-text); font-size: 16px; line-height: 1.8; }
.assistant-section-center > p { margin: 20px auto 0; }
.capability-grid { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); border-top: 1px solid var(--ca-line); border-bottom: 1px solid var(--ca-line); }
.capability-grid article { min-height: 250px; padding: 30px 22px; }
.capability-grid article + article { border-left: 1px solid var(--ca-line); }
.capability-icon { display: grid; place-items: center; width: 50px; height: 50px; border-radius: 50%; color: var(--ca-blue); background: var(--ca-surface-blue); }
.capability-grid article:nth-child(even) .capability-icon { color: var(--ca-mint); background: #eaf8f4; }
.capability-grid h3 { margin: 28px 0 12px; font-size: 17px; letter-spacing: -.025em; }
.capability-grid p { margin: 0; color: var(--ca-muted); font-size: 12px; line-height: 1.72; }
.assistant-system { padding: 126px 0; border-top: 1px solid #eef1f6; background: var(--ca-surface); }
.assistant-system-grid, .assistant-trust-grid { display: grid; grid-template-columns: minmax(320px, .72fr) minmax(620px, 1.3fr); gap: 86px; align-items: start; }
.assistant-section-left h2 span, .assistant-cta h2 span { display: block; }
.assistant-section-left > p { margin: 26px 0 0; }
.industry-explorer { overflow: hidden; border: 1px solid var(--ca-line); border-radius: 18px; background: #fff; box-shadow: 0 20px 65px rgba(58, 79, 116, .08); }
.industry-tab-label { padding: 17px 22px 11px; color: var(--ca-muted); font: 700 9px/1 var(--ca-mono); letter-spacing: .12em; text-transform: uppercase; }
.industry-tabs { display: grid; grid-template-columns: repeat(4, 1fr); padding: 0 12px 12px; border-bottom: 1px solid var(--ca-line); }
.industry-tabs button { display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: 46px; border: 0; border-radius: 8px; color: var(--ca-text); background: transparent; font-size: 13px; font-weight: 700; cursor: pointer; }
.industry-tabs button:hover { background: var(--ca-surface); }
.industry-tabs button.is-active { color: var(--ca-blue); background: var(--ca-surface-blue); }
.industry-detail { padding: 30px; }
.industry-detail-heading { display: flex; align-items: flex-start; gap: 16px; }
.industry-detail-heading > span { display: grid; place-items: center; width: 48px; height: 48px; flex: 0 0 48px; border-radius: 12px; color: var(--ca-blue); background: var(--ca-surface-blue); }
.industry-detail-heading small { color: var(--ca-blue); font: 700 9px/1 var(--ca-mono); letter-spacing: .12em; text-transform: uppercase; }
.industry-detail h3 { margin: 7px 0 0; font-size: 23px; letter-spacing: -.035em; }
.industry-detail > p { margin: 20px 0 0; color: var(--ca-text); font-size: 14px; line-height: 1.75; }
.industry-detail-grid { display: grid; grid-template-columns: .95fr 1.05fr; gap: 0; margin-top: 28px; border-top: 1px solid var(--ca-line); }
.industry-detail-grid > div { padding-top: 24px; }
.industry-detail-grid > div + div { margin-left: 26px; padding-left: 26px; border-left: 1px solid var(--ca-line); }
.industry-detail-grid strong { font-size: 12px; }
.industry-detail-grid ul { display: grid; gap: 11px; margin: 15px 0 0; padding: 0; list-style: none; }
.industry-detail-grid li { display: flex; align-items: center; gap: 8px; color: var(--ca-text); font-size: 12px; }
.industry-detail-grid li svg { color: var(--ca-mint); }
.industry-detail-grid p { margin: 15px 0 0; color: var(--ca-text); font-size: 12px; line-height: 1.72; }
.assistant-process { padding: 124px 0; background: #fff; }
.assistant-section-split { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(340px, .65fr); align-items: end; gap: 80px; margin-bottom: 60px; }
.assistant-section-split > p { margin: 0; }
.process-grid { display: grid; grid-template-columns: repeat(4, 1fr); border-top: 1px solid var(--ca-line); border-bottom: 1px solid var(--ca-line); }
.process-grid article { position: relative; min-height: 300px; padding: 28px; }
.process-grid article + article { border-left: 1px solid var(--ca-line); }
.process-number { color: var(--ca-blue); font: 750 10px/1 var(--ca-mono); letter-spacing: .12em; }
.process-icon { display: grid; place-items: center; width: 48px; height: 48px; margin-top: 46px; border-radius: 50%; color: var(--ca-blue); background: var(--ca-surface-blue); }
.process-grid h3 { margin: 28px 0 11px; font-size: 20px; }
.process-grid p { margin: 0; color: var(--ca-muted); font-size: 13px; line-height: 1.72; }
.assistant-trust { padding: 126px 0; background: var(--ca-surface-blue); }
.assistant-trust .assistant-kicker { color: var(--ca-blue-strong); }
.trust-list { border-top: 1px solid #cbd9ee; }
.trust-list article { display: grid; grid-template-columns: 52px 1fr; gap: 20px; padding: 27px 0; border-bottom: 1px solid #cbd9ee; }
.trust-list article > span { display: grid; place-items: center; width: 48px; height: 48px; border-radius: 12px; color: var(--ca-blue); background: #fff; }
.trust-list h3 { margin: 0; font-size: 19px; }
.trust-list p { margin: 10px 0 0; color: var(--ca-text); font-size: 13px; line-height: 1.75; }
.assistant-cta { padding: 138px 0; text-align: center; background: #fff; }
.assistant-cta-inner { max-width: 850px; display: flex; flex-direction: column; align-items: center; }
.assistant-cta p { max-width: 700px; margin: 25px auto 0; }
.assistant-cta .assistant-button-primary { margin-top: 34px; }
.assistant-footer { padding: 28px 0 32px; border-top: 1px solid var(--ca-line); background: var(--ca-surface); }
.assistant-footer-row { display: flex; align-items: center; justify-content: space-between; gap: 24px; }
.assistant-footer-company { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 16px; }
.assistant-footer-company > span, .assistant-footer-signal { color: var(--ca-muted); font: 600 9px/1.5 var(--ca-mono); }
.assistant-footer-company a { color: var(--ca-muted); font-size: 11px; }
.assistant-footer-company a:hover { color: var(--ca-blue); }
.assistant-footer-police { display: inline-flex; align-items: center; gap: 5px; }
.assistant-footer-police img { width: 18px; height: 20px; object-fit: contain; }
.assistant-footer-signal { flex: 0 0 auto; letter-spacing: .08em; }
[data-reveal] { opacity: 1; transform: none; transition: opacity .7s ease, transform .7s var(--ca-ease); }
[data-reveal].is-visible { opacity: 1; transform: none; }
@media (max-width: 1160px) {
:root { --ca-shell: min(100% - 48px, 1060px); }
.assistant-nav-links { gap: 18px; }
.assistant-hero-grid { grid-template-columns: minmax(390px, .8fr) minmax(500px, 1.1fr); gap: 26px; }
.assistant-hero h1 { font-size: clamp(46px, 4.5vw, 54px); }
.assistant-hero h1 span:last-child em { display: block; }
.capability-grid { grid-template-columns: repeat(3, 1fr); }
.capability-grid article:nth-child(4) { border-left: 0; }
.capability-grid article:nth-child(n+4) { border-top: 1px solid var(--ca-line); }
.assistant-system-grid, .assistant-trust-grid { gap: 50px; }
}
@media (max-width: 900px) {
.assistant-menu { display: grid; }
.assistant-nav-links { position: fixed; inset: 76px 0 auto; display: grid; gap: 0; padding: 14px 24px 22px; border-bottom: 1px solid var(--ca-line); background: rgba(255, 255, 255, .98); opacity: 0; pointer-events: none; transform: translateY(-10px); transition: opacity .2s ease, transform .2s var(--ca-ease); }
.assistant-nav-links.is-open { opacity: 1; pointer-events: auto; transform: none; }
.assistant-nav-links > a:not(.assistant-nav-cta) { padding: 14px 4px; border-bottom: 1px solid var(--ca-line); }
.assistant-nav-links > a:not(.assistant-nav-cta)::after { display: none; }
.assistant-language { width: 100%; margin-top: 12px; }
.assistant-nav-cta { margin-top: 10px; }
.assistant-hero { min-height: 0; padding-top: 134px; }
.assistant-hero-grid { grid-template-columns: 1fr; }
.assistant-hero-copy { max-width: 720px; padding-top: 0; }
.assistant-hero-visual { margin-top: 20px; }
.assistant-system-grid, .assistant-trust-grid { grid-template-columns: 1fr; }
.assistant-section-left { max-width: 720px; }
.assistant-section-split { grid-template-columns: 1fr; gap: 22px; }
.process-grid { grid-template-columns: repeat(2, 1fr); }
.process-grid article:nth-child(3) { border-left: 0; }
.process-grid article:nth-child(n+3) { border-top: 1px solid var(--ca-line); }
}
@media (max-width: 640px) {
:root { --ca-shell: min(100% - 28px, 560px); }
.assistant-nav { height: 68px; }
.assistant-brand small { display: none; }
.assistant-nav-links { inset: 68px 0 auto; }
.assistant-hero { padding: 116px 0 70px; }
.assistant-hero h1 { margin-top: 24px; font-size: clamp(43px, 13vw, 58px); }
.assistant-hero h1 span:last-child em { display: block; }
.assistant-hero-summary { margin-top: 24px; font-size: 15px; line-height: 1.75; }
.assistant-hero-actions { display: grid; }
.assistant-button-primary, .assistant-button-secondary { width: 100%; }
.assistant-proof { display: grid; gap: 10px; }
.assistant-hero-visual { border-radius: 15px; }
.assistant-hero-visual img { aspect-ratio: 1.12; object-position: 44% center; }
.assistant-hero-visual figcaption { display: none; }
.assistant-capabilities, .assistant-system, .assistant-process, .assistant-trust { padding: 88px 0; }
.assistant-section-center { margin-bottom: 42px; text-align: left; }
.assistant-section-center h2, .assistant-section-left h2, .assistant-section-split h2, .assistant-cta h2 { font-size: clamp(35px, 10vw, 47px); }
.capability-grid { grid-template-columns: 1fr; }
.capability-grid article { display: grid; grid-template-columns: 48px 1fr; gap: 4px 16px; min-height: 0; padding: 22px 8px; }
.capability-grid article + article { border-left: 0; border-top: 1px solid var(--ca-line); }
.capability-icon { grid-row: 1 / 3; width: 48px; height: 48px; }
.capability-grid h3 { margin: 2px 0 4px; }
.industry-tabs { grid-template-columns: repeat(2, 1fr); }
.industry-detail { padding: 22px; }
.industry-detail h3 { font-size: 20px; }
.industry-detail-grid { grid-template-columns: 1fr; }
.industry-detail-grid > div + div { margin: 20px 0 0; padding: 22px 0 0; border-top: 1px solid var(--ca-line); border-left: 0; }
.process-grid { grid-template-columns: 1fr; }
.process-grid article { min-height: 0; padding: 25px 8px 28px; }
.process-grid article + article { border-left: 0; border-top: 1px solid var(--ca-line); }
.process-icon { margin-top: 28px; }
.trust-list article { grid-template-columns: 46px 1fr; gap: 14px; }
.trust-list article > span { width: 44px; height: 44px; }
.assistant-cta { padding: 100px 0; }
.assistant-footer-row, .assistant-footer-company { align-items: flex-start; flex-direction: column; }
}
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
*, *::before, *::after { scroll-behavior: auto !important; animation-duration: .01ms !important; transition-duration: .01ms !important; }
[data-reveal] { opacity: 1; transform: none; }
}
+10
View File
@@ -0,0 +1,10 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { ChatAssistantProductPage } from './ChatAssistantProductPage';
import './chat-assistant.css';
createRoot(document.getElementById('root')).render(
<StrictMode>
<ChatAssistantProductPage />
</StrictMode>
);
+471
View File
@@ -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 (
<nav className="harness-nav" aria-label={language === 'zh' ? '产品导航' : 'Product navigation'}>
<div className="harness-nav-inner">
<a className="harness-brand" href="/" aria-label={language === 'zh' ? '返回荣刻科技首页' : 'Back to Roncarve home'}>
<img src="/assets/brand-logo-final.png" alt="" />
<span><strong>{t.brand}</strong><small>RONCARVE TECHNOLOGY</small></span>
</a>
<button className="harness-menu-button" type="button" onClick={() => setOpen(!open)} aria-expanded={open} aria-controls="harnessNavLinks" aria-label={open ? t.close : t.menu}>
{open ? <X size={22} /> : <List size={22} />}
</button>
<div className={`harness-nav-links ${open ? 'is-open' : ''}`} id="harnessNavLinks">
<a href="#top" onClick={close}>{t.navProduct}</a>
<a href="#engine" onClick={close}>{t.navEngine}</a>
<a href="#editions" onClick={close}>{t.navEditions}</a>
<a href="#workflow" onClick={close}>{t.navWorkflow}</a>
<button className="language-button" type="button" onClick={() => setLanguage(language === 'zh' ? 'en' : 'zh')}>{t.language}</button>
<a className="nav-email-button" href={emailHref(language)} onClick={close}>{t.email}<ArrowRight size={16} weight="bold" /></a>
</div>
</div>
</nav>
);
}
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 (
<div className="plugin-demo">
<div className="demo-toolbar">
<div><span className="status-dot" />{t.demoStatus}</div>
<span>RONCARVE PLUGIN INDEX</span>
</div>
<div className="demo-search-row">
<label className="demo-search">
<MagnifyingGlass size={19} aria-hidden="true" />
<span className="sr-only">{t.searchPlaceholder}</span>
<input value={query} onChange={(event) => setQuery(event.target.value)} placeholder={t.searchPlaceholder} />
</label>
<div className="category-tabs" aria-label={language === 'zh' ? '插件分类' : 'Plugin categories'}>
{t.categories.map((label, index) => (
<button key={label} type="button" className={category === index ? 'is-active' : ''} onClick={() => setCategory(index)}>{label}</button>
))}
</div>
</div>
<div className="demo-result-meta"><strong>{filtered.length}</strong> {t.candidates}</div>
<div className="plugin-result-list">
{filtered.map((plugin) => {
const [name, description, tag] = plugin[language];
const Icon = plugin.icon;
const isActive = activePlugin === plugin.id;
return (
<article className={`plugin-result ${isActive ? 'is-active' : ''}`} key={plugin.id}>
<div className="plugin-icon"><Icon size={23} weight="duotone" /></div>
<div className="plugin-copy"><span>{tag}</span><h3>{name}</h3><p>{description}</p></div>
<button type="button" onClick={() => setActivePlugin(plugin.id)} aria-pressed={isActive}>{isActive ? t.composed : t.compose}</button>
</article>
);
})}
{filtered.length === 0 && <div className="empty-result">{language === 'zh' ? '没有匹配结果,请尝试其他关键词。' : 'No matches. Try another keyword.'}</div>}
</div>
<div className="runtime-strip" aria-live="polite">
<div><Lightning size={18} weight="fill" /><span><small>{t.runtime}</small><strong>{active?.[language][0]}</strong></span></div>
<p>{t.hotSwap}</p>
</div>
</div>
);
}
function EditionCard({ title, code, description, features, href, featured, cta }) {
return (
<article className={`edition-card ${featured ? 'is-featured' : ''}`}>
<div className="edition-head"><span>{code}</span>{featured && <UsersThree size={26} weight="duotone" />}</div>
<h3>{title}</h3>
<p>{description}</p>
<ul>{features.map((feature) => <li key={feature}><Check size={17} weight="bold" />{feature}</li>)}</ul>
<a href={href}>{cta}<ArrowRight size={17} weight="bold" /></a>
</article>
);
}
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 (
<div className="harness-page" ref={pageRef}>
<a className="skip-link" href="#main">{language === 'zh' ? '跳到主要内容' : 'Skip to main content'}</a>
<ProductNav language={language} setLanguage={setLanguage} />
<main id="main">
<section className="harness-story" id="top" ref={storyRef}>
<div className="story-sticky">
<div className="story-grid" />
<div className="story-glow" />
<ParticleStage progressRef={progressRef} />
<div className="story-shell">
<div className="hero-copy">
<div className="hero-meta"><span>{t.eyebrow}</span><strong>{t.status}</strong></div>
<h1>{t.title.split('\n').map((line) => <span key={line}>{line}</span>)}</h1>
<p>{t.summary}</p>
<div className="hero-actions">
<a className="button-primary" href={emailHref(language)}>{t.primary}<ArrowRight size={18} weight="bold" /></a>
<a className="button-secondary" href="#editions">{t.secondary}</a>
</div>
<div className="hero-proof">{t.proof.map((item) => <span key={item}>{item}</span>)}</div>
</div>
<div className="value-copy">
<span className="section-kicker">{t.formula}</span>
<h2>{t.valueTitle.split('\n').map((line) => <span key={line}>{line}</span>)}</h2>
<p>{t.valueSummary}</p>
<div className="value-grid">
{t.valueCards.map(([title, description], index) => {
const Icon = [MagnifyingGlass, Plug, UsersThree][index];
return <article className="value-card" key={title}><Icon size={27} weight="duotone" /><span>0{index + 1}</span><h3>{title}</h3><p>{description}</p></article>;
})}
</div>
</div>
</div>
<div className="story-scroll-cue"><span />{t.scroll}</div>
</div>
</section>
<section className="content-section engine-section" id="engine">
<div className="content-shell">
<header className="section-header reveal-section">
<div><span className="section-kicker">{t.engineKicker}</span><h2>{t.engineTitle}</h2></div>
<p>{t.engineSummary}</p>
</header>
<aside className="opensource-note reveal-section">
<span>{language === 'zh' ? '开源来源' : 'OPEN-SOURCE FOUNDATION'}</span>
<p>{t.originLine}</p>
</aside>
<div className="engine-layout">
<div className="reveal-section"><PluginDemo language={language} /></div>
<div className="engine-steps reveal-section">
{t.engineSteps.map(([number, title, description]) => (
<article key={number}><span>{number}</span><div><h3>{title}</h3><p>{description}</p></div></article>
))}
</div>
</div>
</div>
</section>
<section className="content-section editions-section" id="editions">
<div className="content-shell">
<header className="section-header reveal-section">
<div><span className="section-kicker">{t.editionsKicker}</span><h2>{t.editionsTitle.split('\n').map((line) => <span key={line}>{line}</span>)}</h2></div>
<p>{t.editionsSummary}</p>
</header>
<div className="edition-grid reveal-section">
<EditionCard title={t.personal} code={t.personalEn} description={t.personalDesc} features={t.personalFeatures} href={emailHref(language, t.personal)} cta={t.askPersonal} />
<EditionCard title={t.platform} code={t.platformEn} description={t.platformDesc} features={t.platformFeatures} href={emailHref(language, t.platform)} cta={t.askPlatform} featured />
</div>
</div>
</section>
<section className="content-section workflow-section" id="workflow">
<div className="content-shell">
<header className="section-header reveal-section">
<div><span className="section-kicker">{t.workflowKicker}</span><h2>{t.workflowTitle.split('\n').map((line) => <span key={line}>{line}</span>)}</h2></div>
<p>{t.workflowSummary}</p>
</header>
<div className="workflow-grid reveal-section">
{t.workflowSteps.map(([number, title, description], index) => (
<article key={number}><span>{number}</span><div className="workflow-node"><Plug size={20} weight={index === 2 ? 'fill' : 'duotone'} /></div><h3>{title}</h3><p>{description}</p></article>
))}
</div>
</div>
</section>
<section className="product-cta">
<div className="cta-orbit" aria-hidden="true"><img src="/assets/brand-logo-final.png" alt="" /></div>
<div className="content-shell reveal-section">
<span className="section-kicker">{t.ctaKicker}</span>
<h2>{t.ctaTitle.split('\n').map((line) => <span key={line}>{line}</span>)}</h2>
<p>{t.ctaSummary}</p>
<a href={emailHref(language)}>{t.ctaButton}<ArrowRight size={19} weight="bold" /></a>
<strong>contact@roncarve.com</strong>
</div>
</section>
</main>
<footer className="harness-footer">
<div className="content-shell">
<div className="harness-footer-row">
<div className="harness-footer-company">
<span>© {new Date().getFullYear()} {t.copyright}. All Rights Reserved.</span>
<a className="harness-footer-record" href="https://beian.miit.gov.cn/" target="_blank" rel="noopener noreferrer">琼ICP备2026011549号</a>
<a className="harness-footer-record harness-footer-police" href="https://www.beian.gov.cn/portal/registerSystemInfo?recordcode=46010002001924" target="_blank" rel="noopener noreferrer">
<img src="/assets/beian-police.png" alt={t.policeAlt} />
琼公网安备46010002001924号
</a>
</div>
<span className="harness-footer-signal">{t.footerSignal}</span>
</div>
</div>
</footer>
</div>
);
}
+402
View File
@@ -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 (
<points
geometry={geometry}
frustumCulled={false}
position={particleTransform.position}
scale={particleTransform.scale}
>
<shaderMaterial
ref={materialRef}
uniforms={uniforms}
vertexShader={vertexShader}
fragmentShader={fragmentShader}
transparent
depthWrite={false}
blending={THREE.AdditiveBlending}
/>
</points>
);
}
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 (
<div
className={`particle-stage ${ready ? 'is-ready' : ''} ${failed ? 'has-failed' : ''} ${reducedMotion ? 'is-reduced' : ''}`}
ref={stageRef}
onPointerMove={handlePointerMove}
onPointerLeave={() => { pointerActiveRef.current = false; }}
onPointerDown={handleGather}
aria-hidden="true"
>
<img className="particle-static-logo" src="/assets/brand-logo-final.png" alt="" />
{!reducedMotion && !failed && (
<Canvas
className="particle-canvas"
dpr={[1, 1.5]}
camera={{ position: [0, 0, 10], fov: 42, near: 0.1, far: 50 }}
gl={{ alpha: true, antialias: false, powerPreference: 'high-performance' }}
frameloop={active ? 'always' : 'demand'}
onCreated={({ gl }) => {
gl.setClearColor(0x000000, 0);
gl.domElement.addEventListener('webglcontextlost', () => setFailed(true), { once: true });
}}
>
<LogoParticles
progressRef={progressRef}
introRef={introRef}
gatherRef={gatherRef}
pointerRef={pointerRef}
pointerActiveRef={pointerActiveRef}
onReady={handleReady}
/>
</Canvas>
)}
</div>
);
}
+590
View File
@@ -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; }
}
+10
View File
@@ -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(
<StrictMode>
<HarnessProductPage />
</StrictMode>
);
+25
View File
@@ -0,0 +1,25 @@
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'),
chatAssistant: resolve(process.cwd(), 'products/chat-assistant/index.html')
}
}
}
});
@@ -0,0 +1,80 @@
# 箴言助手 Chat Assistant 产品页设计文档
## 1. 产品命名与定位
- 中文名:箴言助手
- 英文名:Chat Assistant
- 产品定位:连接企业数据、领域知识、业务工具与表达策略,为垂直行业提供有依据、可落地、可追溯的智能建议。
- 演示场景:保险顾问服务与保障建议。
- 扩展方式:用新的业务数据、知识资源、技能、工具、意图、表达模板和治理策略替换保险资源包,复用对话、编排、权限、观测与审计底座。
## 2. 当前项目能力总结
以下总结以 `D:\workspace\bankchat` 当前代码、README 和设计文档为依据。产品页只表达代码中已经存在的能力边界,不把路线图能力包装成现状。
### 2.1 对话与上下文
- 新建、保存和查询会话,按会话加载最近历史消息,并支持较早上下文摘要。
- 通过 SSE 流式输出回答、工具调用与模型原生 thinking 状态;模型不支持 thinking 时明确降级,不影响正式回答。
- 支持在输入中显式选择已发布技能及产品、活动、权益对象,并将控制信息与用户正文分开存储。
- 支持用户档案和表达风格偏好,风格只调整语言表达,不覆盖事实、合规规则和工具结果。
### 2.2 业务事实与建议
- 保险演示场景复用客户与客户画像、产品、活动、权益查询能力,并以保障建议方式呈现。
- Agent 可把会话问题、历史上下文、业务查询结果与合规表达资源组合为建议。
- 快捷动作采用白名单和确定性工具预执行,数据库管理工具元数据,运行体仍由代码控制。
### 2.3 可配置的行业资源体系
- 意图、技能、工具、方案、表达模板、内容、字典与策略均具有独立资源模型。
- 资源支持启用、发布、版本历史和回退,并可在解决方案总览中查看关系。
- 行业扩展不依赖改造聊天外壳,核心工作是建立新的数据对象、行业技能、业务工具与表达策略。
### 2.4 数据接入与治理
- 数据源支持配置、连通性测试、启停与历史记录。
- 数据范围支持同步、预览、发布与停用,运行时通过受控目录查询。
- 字段元数据、敏感字段保护和查询审计形成数据访问边界。
### 2.5 模型与运行配置
- 使用 OpenAI-compatible API,可配置不同厂商的 Base URL、模型和调用参数。
- 模型配置支持连通性测试、激活、历史版本与回退,敏感凭据由后端加密存储和脱敏展示。
### 2.6 分析、追踪与审计
- 会话效果分析包括概要、时序、意图、工具和会话记录。
- Trace 记录资源选择、工具调用、模型节点、状态与耗时,支持问题回放。
- 管理操作进入追加式审计事件,并提供查询、详情、选项与完整性校验能力。
### 2.7 身份与权限
- 浏览器统一通过 Java 网关访问,Python Agent API 仅承载编排与流式生成。
- 支持 Roncarve IAM OIDC Authorization Code + PKCE 登录与显式应用准入。
- 外部身份按 `(issuer, subject)` 绑定,不按邮箱自动合并;系统管理员、管理员和普通用户职责分离。
## 3. 产品页信息架构
1. Hero:用“让行业经验,成为每一次决策的底气”建立核心价值,并展示真实产品对话界面。
2. 六大能力:企业数据、领域知识、智能建议、来源追溯、行业扩展、安全合规。
3. 行业扩展:以保险为演示示例,交互切换制造、零售和专业服务场景。
4. 落地路径:连接、理解、建议、追溯。
5. 安全可信:统一身份、数据范围与审计观测。
6. 转化区:邮件预约产品演示。
## 4. 视觉方向
- 参考“荣刻驭境”的品牌层级、导航比例、大标题力度和企业级质感。
- 与荣刻驭境的深色粒子主题区分,采用白色与浅灰蓝基底、荣刻蓝主色和克制薄荷绿辅助色。
- 首屏使用选定方案 1 的真实产品工作台视觉;正文使用清晰分隔、充足留白和少量表面层级,不堆叠卡片。
- 桌面端以左右分栏为主;移动端改为单列,能力与流程按阅读顺序展开。
- 中英文共享同一信息架构,并持久化语言偏好。
## 5. 实现范围
- 新增 `/products/chat-assistant/` 多页应用入口。
- 新增响应式 React 产品页、行业场景交互、移动导航和中英文切换。
- 首页产品菜单增加“箴言助手”入口。
- 更新 Vite 多页构建、Nginx 规范路径和站点地图。
- 通过现有 Docker Compose 在 `127.0.0.1:8081` 提供本地预览。
@@ -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 桌面端交互
- “产品”是带下拉面板的导航项,点击和键盘操作都可打开;悬停只作为增强,不作为唯一触发方式。
- 面板位于导航下方,宽度建议 `420520px`,使用现有深色半透明表面、`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 价值 | `100120svh` | 说明为什么需要它 | 三项核心能力与一句方法论 | Logo 展开为空间插件网络,进入景深与旋转 |
| 03 插件引擎 | 内容自适应 | 解释差异化能力 | 精准检索、匹配、热插拔、运行可观测 | 粒子退到背景,形成节点流 |
| 04 双版本 | 内容自适应 | 帮助选择版本 | 本地客户端、团队共享工作空间、协同 | 仅保留低密度环境粒子 |
| 05 使用路径 | 内容自适应 | 建立落地感 | 发现插件 → 装配能力 → 运行 → 团队沉淀 | 局部连线与状态点 |
| 06 CTA | `7090svh` | 推动转化 | 邮件咨询、版本选择、技术资料 | 粒子再次轻度聚拢为品牌轮廓 |
### 5.1 第一屏:完整 Logo 与产品价值
首屏必须在 WebGL 初始化前先显示可读文案,粒子就绪后直接从画面边缘入场,不先闪现完整静态 Logo。静态 Logo 仅用于减少动态效果模式或 WebGL 失败回退。
建议内容:
- 状态标签:`即将开放 / COMING SOON`
- Eyebrow`RONCARVE HARNESS / 荣刻驭境`
- H1`让 Agent 驾驭每一种能力`
- 副标题:`面向大语言模型的可组合 Harness,以定制插件引擎实现精准检索与高效热插拔;个人版在浏览器中操作本地项目,平台版以共享工作空间连接团队协作。`
- 主按钮:`邮件咨询`
- 次按钮:`查看版本能力`
- 能力摘要:`精准插件检索``高效热插拔``个人 / 平台双版本`
英文对应文案:状态为 `COMING SOON`,主按钮为 `Email Us`,次按钮为 `Compare Editions`
桌面端采用左侧文案、右侧/居中偏右 Logo 粒子构图。Logo 不应被正文遮挡,文字区占页面宽度约 `3844%`,空间舞台占 `5662%`
首屏背景由近黑渐变、低对比网格、青蓝环境光晕和边缘暗角组成。网格与光晕是环境层,不随鼠标进行大幅移动。
### 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-22DeepSeek 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 背景与网格
- 基础使用纵向近黑渐变,避免纯黑导致层次丢失。
- 网格建议 `6480px` 单元,线宽 `1px`,透明度控制在 `0.0250.055`
- Hero 光晕集中在 Logo 后方和页面右上区域,正文后方保持较干净的暗色。
- 使用 CSS mask 让网格在页面边缘、正文区域和段落过渡处自然淡出。
- 内容卡片只使用局部玻璃效果,不在全页叠加大面积 `backdrop-filter`
### 6.4 字体与排版
- H1 桌面:`clamp(46px, 5.2vw, 76px)`,字重 `650750`,行高 `1.041.1`
- H2 桌面:`clamp(36px, 4vw, 58px)`
- 正文:桌面 `1719px`,移动端 `16px`,最大行宽 `3642em`
- 技术标签:`1012px` 等宽字体,字距 `0.100.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 滚动状态
| 页面滚动进度 | 粒子混合 | 旋转 | 景深与大小 | 内容状态 |
| ---: | --- | --- | --- | --- |
| `00.06` | 100% 完整 Logo | 无持续旋转 | Z 接近平面,大小统一 | Hero 完整可读 |
| `0.060.22` | Logo 轮廓轻微松动 | 仅轻微 Y 轴偏转 | 少量 Z 深度 | Hero 文案开始上移 |
| `0.220.55` | 向插件节点空间展开 | X/Y/Z 三轴缓慢旋转 | 近大远小、近亮远暗 | 第二屏标题与卡片进入 |
| `0.550.82` | 形成稳定空间网络 | 持续慢旋转 | 景深最明显 | 三项能力完整展示 |
| `0.821.00` | 粒子向背景节点过渡 | 旋转减弱 | 大粒子数量下降 | 进入正文内容 |
滚动进度只驱动“展开强度、空间位置、整体旋转基准、粒子尺寸范围和透明度”。时间 `uTime` 只负责持续但缓慢的微旋转与漂移,避免滚动停止后场景完全冻结。
### 7.3 鼠标排斥
- 仅在 `pointer: fine` 且未启用减少动态效果时开启。
- 将鼠标位置投射到粒子主平面,计算每个粒子到指针的距离。
- 在排斥半径内使用平滑衰减,不使用硬边界。
- 靠近中心的粒子位移更大,边缘粒子渐近归零。
- 指针离开舞台后,粒子使用阻尼回到当前滚动状态对应的目标,而不是回到页面顶部的 Logo 状态。
- 排斥不改变卡片、文字和可点击元素的位置。
建议 Shader 参数:
- 排斥半径:桌面视觉半径约 `90140px`
- 最大位移:Logo 尺寸的 `36%`
- 回弹阻尼:约 `350650ms` 的视觉恢复感。
### 7.4 点击重新聚拢
- 点击粒子画布或非交互背景时触发;点击导航、链接、按钮、卡片和表单不得触发。
- `uGather` 在约 `500800ms` 内升至 1,使粒子重新聚拢为完整 Logo。
- 保持约 `250400ms` 后,`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 编排
- 前两屏外层使用约 `200220svh` 的滚动容器。
- 粒子舞台使用 `position: sticky; top: 0; height: 100svh`,文案层在其上方滚动。
- 使用一个主 ScrollTrigger 计算归一化进度,避免多个 trigger 同时写入相同粒子属性。
- 内容卡片可使用独立 trigger,但只控制 `opacity``transform`
- `scrub` 建议使用轻微阻尼,不追求完全逐像素同步。
- 组件卸载时销毁 ScrollTrigger,防止重复注册。
### 8.6 CSS mask 用法
- 网格层:中心清晰、边缘淡出。
- 粒子画布:正文侧增加暗色遮罩,保证文字对比度。
- 第二屏过渡:让粒子从完整舞台逐步退到背景,而不是突然隐藏 Canvas。
- 卡片高光:仅在上边缘或角部使用渐变 mask,不做全边框跑马灯。
## 9. 响应式设计
| 视口 | 布局 | 粒子策略 | 交互策略 |
| --- | --- | --- | --- |
| `≥ 1200px` | Hero 左文右景,内容宽 `1200px` | `28k42k` 粒子,完整 Z 深度 | 鼠标排斥 + 点击聚拢 + 滚动旋转 |
| `7681199px` | 文案与粒子更接近 1:1 | `18k28k` 粒子,降低近景尺寸 | 保留排斥,减小半径与旋转幅度 |
| `480767px` | 单列,文案在上、Logo 居中 | `8k14k` 粒子,压缩 Z 深度 | 无悬停排斥,点击聚拢,滚动效果减弱 |
| `< 480px` | 单列、按钮全宽或双行 | `6k10k` 或静态回退 | 只保留轻量滚动过渡与点击反馈 |
移动端注意:
- 使用 `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-22DeepSeek 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 作为粒子聚拢目标。
- 视觉重点从“插件构成一切”转向“本地项目控制、精准插件检索、高效热插拔和团队共享协同”。
- 沿用荣刻官网蓝青紫色与现有组件语言。
- 第二屏直接连接个人版的本地项目形态与平台版的共享协同价值,强化商业产品定位。
- 采用独立文案、独立结构和独立粒子运动逻辑,不复刻参考页资产。