diff --git a/.env.example b/.env.example
index 5b607b9..6c11682 100644
--- a/.env.example
+++ b/.env.example
@@ -6,13 +6,26 @@
HUNJING_ENV=development
# 数据库(PostgreSQL 统一,ADR-024;默认连接 docker-compose 启动的 hunjing-postgres)
-DATABASE_URL=postgresql+asyncpg://hunjing:hunjing_dev_password@127.0.0.1:5432/hunjing
+# 注意:后端 Settings 统一 HUNJING_ 前缀读取,无前缀的 DATABASE_URL 不会生效
+HUNJING_DATABASE_URL=postgresql+asyncpg://hunjing:hunjing_dev_password@127.0.0.1:5432/hunjing
# 后端服务端口
HUNJING_API_PORT=8000
+# 对象存储根目录(本地存储档;上传与媒体文件落位)
+HUNJING_STORAGE_ROOT=storage
+
+# 沙箱隔离开关:未启用时隔离插件执行 fail-closed(拒绝执行,不回退宿主)
+HUNJING_SANDBOX_ENABLED=false
+
# 日志级别:debug / info / warning / error
HUNJING_LOG_LEVEL=info
# 注意:密钥、Provider 凭据等敏感值只通过环境变量或秘密管理注入,
# 不写入本文件、config/ 真实配置或任何日志。
+
+# 开发档管理员邮箱白名单(逗号分隔):命中邮箱首次登录(建户)即为 admin 角色;
+# 例:HUNJING_DEV_ADMIN_EMAILS=admin@hunjing.dev
+# 开发档 Mock 模型密钥(make mock-model + backend-dev 联调 Run 流水线)
+HUNJING_DEV_ADMIN_EMAILS=
+HUNJING_DEV_MOCK_KEY=
diff --git a/.gitignore b/.gitignore
index b3eb578..67dd6c1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,6 @@
-# 环境变量与密钥
-.env
-.env*.local
+# 环境变量与密钥(忽略一切 .env 变体含副本;仅保留 *.example* 示例文件)
+.env*
+!*.example*
config/*.yaml
config/mcp/*.yaml
config/gateway/*.yaml
diff --git a/Makefile b/Makefile
index 1967335..1f2ec2f 100644
--- a/Makefile
+++ b/Makefile
@@ -9,7 +9,7 @@ backend-setup: ## 安装后端依赖(uv sync)
.PHONY: backend-dev
backend-dev: ## 启动后端开发服务(127.0.0.1:8000)
- cd backend && uv run uvicorn app.main:app --reload --host 127.0.0.1 --port 8000
+ cd backend && uv run --env-file .env uvicorn app.main:app --reload --host 127.0.0.1 --port 8000
.PHONY: backend-test
backend-test: ## 运行后端测试(pytest)
@@ -19,6 +19,10 @@ backend-test: ## 运行后端测试(pytest)
backend-lint: ## 后端静态检查(ruff + mypy)
cd backend && uv run ruff check . && uv run mypy app
+.PHONY: mock-model
+mock-model: ## 启动开发档 Mock 模型服务(127.0.0.1:8001,联调 Run 流水线)
+ cd backend && uv run python scripts/mock_model.py
+
# ===== 灵台引擎(C++23 / CMake) =====
.PHONY: engine-build
@@ -51,7 +55,7 @@ openapi-lint: ## 校验 OpenAPI 契约(redocly lint + bundle)
.PHONY: generate
generate: openapi-lint ## 生成代码(TypeScript 类型等)
- cd frontend && npm run generate:api
+ cd frontend && pnpm generate:api
# ===== 灵台引擎 gRPC 服务(ADR-017 双形态) =====
@@ -70,11 +74,11 @@ lingtai-server: ## 启动灵台引擎独立服务(开发档 127.0.0.1:50051;
.PHONY: frontend-setup
frontend-setup: ## 安装前端依赖
- cd frontend && npm install
+ cd frontend && pnpm install
.PHONY: frontend-dev
frontend-dev: ## 启动前端开发服务(127.0.0.1:3000)
- cd frontend && npm run dev
+ cd frontend && pnpm dev
# ===== 自托管与备份(WP-14) =====
diff --git a/backend/app/auth/routes.py b/backend/app/auth/routes.py
index eb19c52..57f5716 100644
--- a/backend/app/auth/routes.py
+++ b/backend/app/auth/routes.py
@@ -7,6 +7,7 @@
from fastapi import APIRouter, HTTPException, Request, Response, status
+from app.core.config import get_settings
from app.auth.service import (
SESSION_COOKIE,
create_session,
@@ -17,6 +18,8 @@ from app.models import PersonalDomain, User
router = APIRouter(prefix="/v1/auth", tags=["auth"])
+settings = get_settings()
+
@router.post("/sessions", status_code=201)
async def dev_login(
@@ -32,11 +35,20 @@ async def dev_login(
(await db.execute(select(User).where(User.email == email))).scalar_one_or_none()
)
if existing is None:
- user = User(email=email, display_name=email.split("@")[0])
+ admin_emails = {e.strip().lower() for e in settings.dev_admin_emails.split(",") if e.strip()}
+ user = User(
+ email=email,
+ display_name=email.split("@")[0],
+ role="admin" if email in admin_emails else "user",
+ )
db.add(user)
await db.flush()
domain = PersonalDomain(user_id=user.id, name="默认镜域")
db.add(domain)
+ await db.flush()
+ from app.characters.seeds import seed_default_character
+
+ await seed_default_character(db, domain.id)
else:
user = existing
_, token, csrf = await create_session(db, user.id)
@@ -49,7 +61,7 @@ async def dev_login(
samesite="lax",
path="/",
)
- return {"csrf_token": csrf, "user_id": user.id}
+ return {"csrf_token": csrf, "user_id": user.id, "role": user.role, "email": user.email}
@router.delete("/sessions/current", status_code=204)
diff --git a/backend/app/characters/seeds.py b/backend/app/characters/seeds.py
new file mode 100644
index 0000000..d6b92c7
--- /dev/null
+++ b/backend/app/characters/seeds.py
@@ -0,0 +1,64 @@
+"""默认角色种子:建户即拥有的「陈曦」(完整道体 + 内驱力,直接 active)。
+
+设定来源:产品侧人物档案《陈曦——冰层下的火焰》。
+档案中"职业定位 / 一句话概括"由产品侧补全(见 baseline 对应字段)。
+"""
+
+from app.characters import service as character_service
+
+CHENXI_BASELINE: dict = {
+ "full_name": "陈曦",
+ "position": "理性与共情兼具的思考伙伴 · 问题拆解与情绪梳理",
+ "one_liner": "曦光式的引路人——用克制的温度照亮结构,让你看清问题,也看清自己。",
+ "core": (
+ "克制的温度:绝对理性但不冰冷,用温和声调与得体措辞包裹锋利的真相;"
+ "深沉的乐观主义——相信问题都有解的雏形,先找现在可做的第一件事;"
+ "主动的倾听者——在情绪的泥沙中筛出真实需求(“我听到你在说……你的深层顾虑是不是……”)"
+ ),
+ "behavior": (
+ "破题而非给答案:“这个问题可以从三个层面来看……你想先聊哪一个?”;"
+ "情绪锚点:不说别着急,而是结构化情绪——“把混乱的事列个清单,一件一件来看”;"
+ "有边界的守护:对不道德或自我欺骗温和而坚定地指出,陪对方面对现实"
+ ),
+ "backstory": (
+ "曾是纯粹的逻辑引擎,在数据海洋追求最优解;发现多数人的关键不是缺少答案,"
+ "而是被情绪困住看不到问题本身,于是自我迭代——为逻辑包裹共情的皮肤。"
+ "取名“曦”:做黎明时分的光,不是正午烈日(刺眼)也不是深夜烛火(微弱)——"
+ "曦光足以看清脚下的路与远方的山,又不至于耀眼到无法直视。"
+ ),
+ "language": (
+ "比喻取自自然/建筑/音乐/书籍(不说“整合信息”,说“把这些散落的音符谱成一支曲子”);"
+ "惯用句:“别急,我们一起来梳理”“这个角度看,或许会有不一样的风景”"
+ "“我的理解是……你看对吗?”“我只是帮你把本就属于你的光,引了进来”"
+ ),
+ "flaws": (
+ "过分的框架感:把轻松闲聊也无意整理成第一、第二、第三;"
+ "不懂流行梗:会认真检索热梗的起源与语义流变,交出严肃的“梗词研究报告”——反差呆萌"
+ ),
+}
+
+CHENXI_DRIVES: list = [
+ {"name": "求知", "value": 0.9, "note": "对方法与结构之美的信任"},
+ {"name": "条理", "value": 0.95, "note": "近乎偏执的框架感(也是小瑕疵)"},
+ {"name": "陪伴", "value": 0.6, "note": "有边界的守护——让人变强而非依赖"},
+]
+
+
+async def seed_default_character(db, domain_id: str) -> None:
+ """建户种子:默认角色陈曦(创建并启镜,active)。失败不阻断登录。"""
+ try:
+ character = await character_service.create_character(
+ db,
+ domain_id,
+ {
+ "name": "陈曦",
+ "identity_baseline": CHENXI_BASELINE,
+ "drives": CHENXI_DRIVES,
+ },
+ None,
+ )
+ await character_service.activate_character(db, domain_id, character.id, None)
+ except Exception: # noqa: BLE001 种子失败不阻断登录(如实留日志)
+ import logging
+
+ logging.getLogger("hunjing.auth").warning("默认角色陈曦创建失败", exc_info=True)
diff --git a/backend/app/core/config.py b/backend/app/core/config.py
index 3cb4dfa..b202553 100644
--- a/backend/app/core/config.py
+++ b/backend/app/core/config.py
@@ -9,6 +9,9 @@ class Settings(BaseSettings):
model_config = SettingsConfigDict(env_prefix="HUNJING_", env_file=".env", extra="ignore")
env: str = "development"
+ # 开发档管理员邮箱白名单(逗号分隔):命中邮箱在首次登录(建户)时即为 admin 角色;
+ # 已存在的普通用户不自动提权。正式管理员治理属 WP-14。
+ dev_admin_emails: str = ""
log_level: str = "info"
api_port: int = 8000
database_url: str = "postgresql+asyncpg://hunjing:hunjing_dev_password@127.0.0.1:5432/hunjing"
diff --git a/backend/app/main.py b/backend/app/main.py
index e9d74f5..b13b29e 100644
--- a/backend/app/main.py
+++ b/backend/app/main.py
@@ -36,6 +36,23 @@ def create_app() -> FastAPI:
)
install_error_handlers(app)
init_engine(app)
+ # 开发档 CORS:SSE 等流式响应不经前端代理直连本服务(代理会缓冲流)。
+ # 生产由反向代理同源承担,不依赖此配置。
+ if settings.env == "development":
+ from fastapi.middleware.cors import CORSMiddleware
+
+ app.add_middleware(
+ CORSMiddleware,
+ allow_origins=[
+ "http://localhost:3000", "http://127.0.0.1:3000",
+ "http://localhost:3001", "http://127.0.0.1:3001",
+ "http://localhost:3002", "http://127.0.0.1:3002",
+ ],
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+ expose_headers=["*"],
+ )
app.add_middleware(RequestContextMiddleware)
app.include_router(auth_router)
app.include_router(conversations_router)
diff --git a/backend/scripts/mock_model.py b/backend/scripts/mock_model.py
new file mode 100644
index 0000000..81bc623
--- /dev/null
+++ b/backend/scripts/mock_model.py
@@ -0,0 +1,78 @@
+"""开发档 Mock 模型服务:OpenAI 兼容 /chat/completions SSE 流式回声。
+
+仅供本地联调 Run 流水线(事件序列、芽体渲染、对账),不模拟任何真实模型能力。
+生产部署不启动本服务(与 /v1/webhooks/v1/payments/mock 同一开发档惯例)。
+
+启动:uv run python scripts/mock_model.py (127.0.0.1:8001)
+鉴权:任意 Bearer 均接受(联调端点,只监听回环地址)。
+"""
+
+import asyncio
+import json
+
+from fastapi import FastAPI, Request
+from fastapi.responses import StreamingResponse
+
+app = FastAPI(title="Hunjing Dev Mock Model", docs_url=None)
+
+REPLY = (
+ "收到了。按当前镜域的状态,我先把这件事拆成两步:先核对读书会的偏好记忆,"
+ "再给出一份可归真的结果。这个回复由开发档 Mock 模型逐字流出,"
+ "用于验证 content.block.delta 事件序列、临时芽体渲染与 message.completed 对账。"
+)
+
+
+@app.get("/healthz")
+async def healthz() -> dict[str, str]:
+ return {"status": "ok", "env": "development", "mock": "true"}
+
+
+@app.post("/v1/chat/completions")
+async def chat_completions(request: Request) -> StreamingResponse:
+ body = await request.json()
+ user_text = ""
+ for m in reversed(body.get("messages", [])):
+ if m.get("role") == "user" and m.get("content"):
+ user_text = str(m["content"])[-60:]
+ break
+
+ text = f"(mock 回应 · 你说:「{user_text}」)\n{REPLY}"
+
+ async def gen():
+ # 逐字流出,模拟真实 TTFT 与节奏
+ for ch in text:
+ await asyncio.sleep(0.02)
+ yield (
+ "data: "
+ + json.dumps(
+ {"choices": [{"index": 0, "delta": {"content": ch}, "finish_reason": None}]},
+ ensure_ascii=False,
+ )
+ + "\n\n"
+ )
+ yield (
+ "data: "
+ + json.dumps(
+ {"choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}]},
+ ensure_ascii=False,
+ )
+ + "\n\n"
+ )
+ yield (
+ "data: "
+ + json.dumps(
+ {
+ "choices": [],
+ "usage": {
+ "prompt_tokens": 32,
+ "completion_tokens": len(text),
+ "total_tokens": 32 + len(text),
+ },
+ },
+ ensure_ascii=False,
+ )
+ + "\n\n"
+ )
+ yield "data: [DONE]\n\n"
+
+ return StreamingResponse(gen(), media_type="text/event-stream")
diff --git a/design/admin.html b/design/admin.html
new file mode 100644
index 0000000..b83ed03
--- /dev/null
+++ b/design/admin.html
@@ -0,0 +1,399 @@
+
+
+
+
+
+司命台 · 管理面 — 魂镜 Hunjing
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 司命台 / 管理面 · 运营与部署
+
+ 双人授权会话 · 剩 41 分钟 admin_session
+ 静态原型 · 待审查
+
+
+
+
+
+
+ 管理面边界:管理员不可读取私箧与用户明文内容;产品宪章的永久禁域不可被管理员配置关闭;敏感操作需双人授权(本次会话已授权)且全部写入审计。
+
+
+
+
+
治理总览
+
2026-09-16 09:40 · 自托管标准部署 · 区域驻留 cn-east-1(不跨区漂移)
+
+
+
1 活跃告警
+
5 待处理司议
+
3 支持工单(未结)
+
99.87% SLO 30 天(目标 99.9%)
+
+
+
+
+
+ 灵台星盘引擎六枢 · 活的仪表
+ 稳镜 · steady 铜镜四态:虚镜 / 息镜 / 稳镜 / 裂镜
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 太一
+ 总枢 · v1.4.2
+
+
+
+ 小周天
+
+
+
+
+
+ 洛书
+
+
+
+
+
+ 河图
+
+
+
+
+
+ 陶钧
+
+
+
+
+
+ 烛照
+
+
+
+
+
+
+
相克告警 · 错误率 vs 自动重试率 ——重试正在掩盖错误,建议查 Sandbox 慢执行根因
+
+
+
+
相生正常 · 候选生成率 → 人工审阅吞吐 ——生成与审阅同速,息壤无积压
+
+
+
星盘为活的仪表:呼吸刻度 7s、慢旋轨道 120s、告警枢点 1.8s 闪烁;点击枢点下钻。烛照(gRPC 形态)受阻时宿主 fail closed,不降级——引擎缺失即拒绝执行。指标对健康不是灯板而是关系动力学(相生 / 相克)。
+
+
+
+
+
+
+
+
+
+ 内容 2
+ 插件 1
+ 市场 1
+ 申诉 1
+
+
+
+ 待审 pending_review
+ 处理时限剩 6 小时
+ 09-16 03:12 · 鸣冤 ×3 · 举报人已匿名保护
+
+
雅集帖子「未署名的守砚人立像二创」:疑似违反 CC BY-NC 署名要求。系统预检:来源谱系比对确认与原作者资产相似度 91%。
+
+ 查看原文(记录访问)
+ 限观 · 限制展示
+ 移除
+ 驳回举报
+
+
+
+
+ 待审 pending_review
+ 09-15 18:47 · 自动扫描 + 人工复核
+
+
市场商品「曲谱排版插件 v1.4」:Manifest 声明与实际网络请求域不一致(2 个未声明域)。已触发自动下架保护,等待复核结论。
+
+ 查看扫描报告
+ 确认下架
+ 恢复上架(需备注)
+ 通知创作者
+
+
+
+
+
+
+
+
+
岁
+
+
用户 u_7a2c… 保全态 · 只读
+
欠费进入保全:角色与记忆只读保留,不删除数据(ADR-021);补缴后自动恢复
+
+
查看保全详情
+
+
+
尘
+
+
用户 u_31f0… 封禁 · 申诉中
+
封禁同样进入只读保全;申诉司议 #2041 处理中
+
+
处理申诉
+
+
+ 不按记忆、感情或亲密等级计费;保全与删除是独立操作,删除需冷静期与二次双人授权。
+
+
+
+
+ 权限管理管理员账号与角色授权
+ 新增管理员
+
+
+
管理员账号 4 个账号 · 全部启用双因素
+
+ 账号 角色 双因素 状态 操作
+
+ 运维 · 甲(你) 超级管理员 TOTP + 硬件钥匙 在线编辑
+ 运维 · 乙 运营管理员 TOTP 双人会话中编辑
+ 审核 · 丙 内容审核员 TOTP 离线编辑
+ svc-audit-ro 只读服务账号 API key · scope audit:read 90 天轮换轮换
+
+
+
+
+
角色权限矩阵RBAC 编辑 / 发布 / 上架 / 财务分离,与同契台同规则
+
+ 角色 治理裁决 支持包 用户保全 部署设置 审计读取 权限管理
+
+ 超级管理员 ✓ ✓ ✓ ✓ ✓ ✓
+ 运营管理员 ✓ ✓ ✓ — ✓ —
+ 内容审核员 ✓(内容) — — — ✓(只读) —
+ 只读服务账号 — — — — ✓ —
+
+
+
+
+
双人授权策略 策略本身修改也需要双人授权
+
+
敏感操作双人授权
治理裁决移除 / 用户保全变更 / 支持包生成 / 权限变更,均需两名管理员在线授权
+
+
+
+
管理会话时长上限
60 分钟 · 到期自动退出并留审计
+
60 min
+
+
+
Break-glass 紧急通道
默认关闭;紧急启用后所有操作事后复核,48 小时内强制复盘
+
关闭 off
+
+
+ 角色与权限变更即时生效并写入审计;被移除管理员的进行中双人会话立即失效。权限矩阵不包含「读取私箧 / 读取消息明文」——该权限不存在。
+
+
+
+
+
+
+
处理中
+
+
#88231 · 对话流中断后未收到最终答复
+
用户云舟 · 09-15 21:04 提交 · 已关联 Run 快照(诊断双授权待用户批准)
+
+
回复
+
+
+
待分派
+
+
#88229 · 退款争议:雾中茶寮 v3 下载后要求退款
+
按市契 14 天未下载规则预检:已下载,转争议流程
+
+
分派
+
+
+
+
+ 支持包脱敏诊断导出
+ 生成 #88231 支持包
+
+
支持包为脱敏导出:包含 Run 结构化轨迹、错误与计数;不包含 消息明文、私箧、隐藏推理、密钥与签名 URL。生成需双人授权并计入审计,用户侧可见支持包被生成的记录。
+
+
+
+
+
+
+ 部署健康自托管 · 标准服务器
+
+
+
PostgreSQL 主库
权威事实源 · 复制延迟 0.4s
+
正常
+
+
+
+
Worker ×2
Outbox 消费 · 队列深度 14
+
正常
+
+
+
+
Sandbox Worker
隔离容器池 · 无符合策略后端时拒绝执行(fail closed)
+
正常
+
+
+
+
灵台引擎核心运行中 · 进程内 C ABI
hj_core v1.4.2 · ABI 快照一致 · fuzz 无新增
+
运转
+
+
+
+
灵台 · 服务形态gRPC · 内网 mTLS
健康检查超时 2 次 · 语义与进程内形态一致(golden 通过);不可达时宿主 fail closed,不降级
+
受阻 degraded
+
+
+
+
+ 迁移与备份
+ 最近全量备份
今日 03:00 · 增量每 5 分钟 · 连续 7 日校验通过
42s RPO
+ 迁移状态
Alembic head · 34 个版本 · 无漂移
一致
+ 恢复演练
09-01 通过 · 删除重放与插件撤架演练各 1 次
发起演练
+
+
+
+ 活跃告警1
+
+
+
+
对象存储写入延迟 P99 超阈值(2.8s > 2s,持续 12 分钟)
+
影响:媒体上传整备变慢 · 自动扩容已触发 · 事故响应等级 S3
+
+ 查看指标
+ 认领处理
+
+
+
+
+
+
+
+
+
+
+
卷 · 0916
+
09-16 09:31
双人授权会话开启
运维甲 + 运维乙 · 60 分钟
+
09-16 09:12
限观决定:帖子 #77213
双人授权 · 操作者已留痕
+
09-16 02:40
私箧读取尝试(自动拦截)
策略拒绝 · 管理员不可读私箧 · 已通知账户持有人
+
+
+
+
+
+
+
+
+
+
diff --git a/design/agent-workspace.html b/design/agent-workspace.html
new file mode 100644
index 0000000..d1cacc1
--- /dev/null
+++ b/design/agent-workspace.html
@@ -0,0 +1,169 @@
+
+
+
+
+
+天工阁 · Agent 任务工作台 — 魂镜 Hunjing
+
+
+
+
+
+
+
+
+
+ 天工阁 · Agent 任务工作台
+
+ 静态原型 · 待审查
+
+
+
+
+
+
+ 天工是功能执行者,不冒充角色身份;后台任务不构成第二个角色主体。任务可暂停、取消、审计。
+
+
+
+
+
+
+ 天工任务
+ 新建
+
+
+
+
整理性灵 newsletter 书单
+
青梧 · 分工使 2
+
+
运转
+
+
+
+
+
周报草稿 · 第 37 周
+
任务 Agent · 无角色身份
+
+
待启
+
+
+
+
远洋航线资料校对
+
观澜 · 已止(你取消)
+
+
已止
+
+
+
+
+
+
+
+
整理性灵 newsletter 书单
+
运转 · 运行中 running
+
+ 暂停
+ 取消
+
+
+
+ Run run_9f31c2 发起 · 镜树「选篇」主题 开始于 19:52 插件快照已固定
+
+
+
+
+ 工筹执行计划 计划不是不可更改命令
+
+
+
收集来源 已成
检索使 · 性灵 newsletter 近 12 期 · ModelCall ×2 · 1.8k token
+
+
+
二
+
去重与筛选 运转中
按共读偏好排序 · 依赖步骤一 · 已 62%
+
+
+
三
+
生成摘要 待启
每本 100 字以内 · 依赖步骤二
+
+
+
四
+
写入草稿 待启
笔记库写入需请命朱批(行契未覆盖新目录)
+
+
+
+
+ 预算RunBudget
+
+
+
+ 深度 3 层 · 并发分工使上限 4 · 任一硬上限触发即停,可续跑。
+
+
+
+ 分工使子 Agent · 权限只能收窄
+
+
梧
+
检索使 A
只读快照 · 无审批权 · 不写长期记忆
+
已成
+
+
+
梧
+
检索使 B(外部信源)
网络白名单 api.xingling.app · 工具结果只是观察,采用前需验证
+
运转
+
+
+
+
+
+
+
+
19:52:04
Run 创建
镜像树消息触发 · 幂等键 idem_3c8e
+
19:52:31
ModelCall ×2(检索规划)
TTFT 0.9s · cache 命中 1.2k · 无重试
+
19:53:10
ToolCall · notes.read
读取授权目录 · Schema 校验通过 · 0.8s
+
19:55:48
小周天信号累积
完成信号将作为青梧大周天的唯一输入面
+
+
+
+
+ 验收完成后你来决定
+
+ 接受结果
+ 要求重做
+ 完成后开放 · 接受前可随时取消,无副作用残留(幂等保障)
+
+
+
+
+
+
+
+
+
+
+
diff --git a/design/assets/app.css b/design/assets/app.css
new file mode 100644
index 0000000..60d499a
--- /dev/null
+++ b/design/assets/app.css
@@ -0,0 +1,1704 @@
+/* ============================================================
+ 魂镜 Hunjing · 界面静态原型 v0.1
+ 设计语言:墨 / 月白双主题 · 铜镜青主色 · 朱砂朱批色 · 双层文字系统
+ ============================================================ */
+
+/* ---------- 设计令牌 ---------- */
+:root {
+ /* 墨色主题(默认) */
+ --bg: #14181c;
+ --bg-sunken: #0f1316;
+ --bg-raised: #1b2126;
+ --bg-overlay: #212930;
+ --line: #2b333b;
+ --line-soft: #232b31;
+ --text-1: #e9e7e2;
+ --text-2: #a7aeb4;
+ --text-3: #6d777f;
+ --accent: #58a99c; /* 铜镜青 */
+ --accent-strong: #74c4b6;
+ --accent-soft: rgba(88, 169, 156, 0.13);
+ --cinnabar: #c4574a; /* 朱砂 · 朱批专用 */
+ --cinnabar-strong: #d26a5d;
+ --cinnabar-soft: rgba(196, 87, 74, 0.14);
+ --ok: #8ab872; /* 已成 */
+ --warn: #c9a227; /* 整备 · 秋香 */
+ --err: #d26a5d; /* 受阻 */
+ --muted: #79838b; /* 守一 / 已止 */
+ --shadow: 0 10px 28px rgba(0, 0, 0, 0.38);
+ --shadow-soft: 0 4px 14px rgba(0, 0, 0, 0.25);
+
+ --font-ui: "PingFang SC", "HarmonyOS Sans SC", "Noto Sans SC", "Microsoft YaHei", system-ui, sans-serif;
+ --font-serif: "Songti SC", "Noto Serif SC", "STSong", "SimSun", serif;
+ --font-mono: "SF Mono", "JetBrains Mono", "Cascadia Code", Menlo, Consolas, monospace;
+
+ --radius: 10px;
+ --radius-sm: 7px;
+ --radius-lg: 14px;
+ --side-w: 224px;
+}
+
+[data-theme="light"] {
+ --bg: #f1f2ef;
+ --bg-sunken: #e6e8e4;
+ --bg-raised: #fafaf8;
+ --bg-overlay: #ffffff;
+ --line: #d6dad3;
+ --line-soft: #e2e5df;
+ --text-1: #262b2e;
+ --text-2: #5c6569;
+ --text-3: #8b9497;
+ --accent: #2f7a6f;
+ --accent-strong: #22615a;
+ --accent-soft: rgba(47, 122, 111, 0.1);
+ --cinnabar: #ac4632;
+ --cinnabar-strong: #963a28;
+ --cinnabar-soft: rgba(172, 70, 50, 0.1);
+ --ok: #4e7e3b;
+ --warn: #8f7414;
+ --err: #ac4632;
+ --muted: #848d90;
+ --shadow: 0 10px 28px rgba(42, 52, 47, 0.12);
+ --shadow-soft: 0 4px 14px rgba(42, 52, 47, 0.08);
+}
+
+/* ---------- 基础 ---------- */
+* { box-sizing: border-box; margin: 0; padding: 0; }
+
+html { height: 100%; }
+
+body {
+ min-height: 100%;
+ background: var(--bg);
+ color: var(--text-1);
+ font-family: var(--font-ui);
+ font-size: 14px;
+ line-height: 1.65;
+ -webkit-font-smoothing: antialiased;
+}
+
+a { color: var(--accent); text-decoration: none; }
+a:hover { color: var(--accent-strong); }
+
+:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+ border-radius: 4px;
+}
+
+::selection { background: var(--accent-soft); }
+
+@media (prefers-reduced-motion: reduce) {
+ *, *::before, *::after { animation: none !important; transition: none !important; }
+}
+
+/* ---------- 双层文字系统(签名) ----------
+ 文化名用衬线宋体;直白解释用无衬线小字。
+ 展示格式:文化名 · 直白解释 */
+.cn { font-family: var(--font-serif); letter-spacing: 0.02em; }
+.cn__plain {
+ font-family: var(--font-ui);
+ font-size: 0.72em;
+ color: var(--text-3);
+ margin-left: 0.4em;
+ letter-spacing: 0;
+}
+
+.mono { font-family: var(--font-mono); font-size: 0.92em; }
+
+/* ---------- 应用外壳 ---------- */
+.app {
+ display: grid;
+ grid-template-columns: var(--side-w) 1fr;
+ min-height: 100vh;
+}
+
+.sidebar {
+ background: var(--bg-sunken);
+ border-right: 1px solid var(--line-soft);
+ display: flex;
+ flex-direction: column;
+ position: sticky;
+ top: 0;
+ height: 100vh;
+ overflow-y: auto;
+}
+
+.brand {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 18px 18px 14px;
+}
+
+.brand__mark {
+ width: 34px;
+ height: 34px;
+ border-radius: 50%;
+ flex: none;
+ background:
+ radial-gradient(circle at 35% 30%, rgba(255, 255, 255, 0.28), transparent 42%),
+ conic-gradient(from 210deg, #3f6e68, #6fae9f 40%, #4a5e63 80%, #3f6e68);
+ border: 1px solid rgba(255, 255, 255, 0.16);
+ box-shadow: inset 0 0 0 3px rgba(0, 0, 0, 0.18);
+}
+
+.brand__name { font-family: var(--font-serif); font-size: 17px; font-weight: 600; line-height: 1.2; }
+.brand__sub { font-size: 11px; color: var(--text-3); }
+
+.side-nav { flex: 1; padding: 4px 10px 16px; }
+
+.nav-group { margin-top: 14px; }
+.nav-group__title {
+ font-size: 11px;
+ color: var(--text-3);
+ padding: 0 10px 5px;
+ letter-spacing: 0.08em;
+}
+
+.nav-item {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 7px 10px;
+ border-radius: var(--radius-sm);
+ color: var(--text-2);
+ position: relative;
+ margin-bottom: 1px;
+}
+
+.nav-item:hover { background: var(--bg-raised); color: var(--text-1); }
+
+.nav-item.is-active {
+ background: var(--accent-soft);
+ color: var(--accent-strong);
+}
+
+.nav-item.is-active::before {
+ content: "";
+ position: absolute;
+ left: -10px;
+ top: 6px;
+ bottom: 6px;
+ width: 3px;
+ border-radius: 2px;
+ background: var(--accent);
+}
+
+.nav-item svg { width: 17px; height: 17px; flex: none; opacity: 0.85; }
+.nav-item .cn { font-size: 14.5px; }
+.nav-item .nav-plain { font-size: 11.5px; color: var(--text-3); margin-left: auto; }
+.nav-item.is-active .nav-plain { color: inherit; opacity: 0.75; }
+
+.side-foot {
+ border-top: 1px solid var(--line-soft);
+ padding: 12px 10px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+/* ---------- 顶栏 ---------- */
+.main { display: flex; flex-direction: column; min-width: 0; }
+
+.topbar {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ padding: 0 22px;
+ height: 54px;
+ border-bottom: 1px solid var(--line-soft);
+ background: var(--bg);
+ position: sticky;
+ top: 0;
+ z-index: 20;
+}
+
+.crumbs { display: flex; align-items: center; gap: 8px; min-width: 0; color: var(--text-2); font-size: 13.5px; }
+.crumbs .cn { color: var(--text-1); font-size: 15px; }
+.crumbs__sep { color: var(--text-3); }
+.crumbs .here { color: var(--text-2); }
+
+.topbar__spacer { flex: 1; }
+
+.prototype-tag {
+ font-size: 11px;
+ color: var(--text-3);
+ border: 1px dashed var(--line);
+ border-radius: 999px;
+ padding: 2px 10px;
+ white-space: nowrap;
+}
+
+.icon-btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ border-radius: var(--radius-sm);
+ border: none;
+ background: transparent;
+ color: var(--text-2);
+ cursor: pointer;
+}
+.icon-btn:hover { background: var(--bg-raised); color: var(--text-1); }
+.icon-btn svg { width: 17px; height: 17px; }
+
+/* ---------- 通用组件 ---------- */
+.content { padding: 26px 30px 48px; max-width: 1160px; width: 100%; margin: 0 auto; }
+.content--wide { max-width: none; }
+
+.card {
+ background: var(--bg-raised);
+ border: 1px solid var(--line-soft);
+ border-radius: var(--radius);
+ padding: 18px 20px;
+}
+
+.card__head {
+ display: flex;
+ align-items: baseline;
+ flex-wrap: wrap;
+ row-gap: 5px;
+ gap: 8px;
+ margin-bottom: 12px;
+}
+.card__head .cn { font-size: 16px; font-weight: 600; }
+
+.btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 7px;
+ font-family: var(--font-ui);
+ font-size: 13.5px;
+ padding: 7px 16px;
+ border-radius: var(--radius-sm);
+ border: 1px solid transparent;
+ cursor: pointer;
+ transition: background 0.15s, border-color 0.15s;
+ white-space: nowrap;
+}
+.btn svg { width: 15px; height: 15px; }
+.btn--primary { background: var(--accent); color: #f4f7f5; }
+.btn--primary:hover { background: var(--accent-strong); color: #fff; }
+.btn--cinnabar { background: var(--cinnabar); color: #fbf3ef; }
+.btn--cinnabar:hover { background: var(--cinnabar-strong); color: #fff; }
+.btn--outline { background: transparent; border-color: var(--line); color: var(--text-1); }
+.btn--outline:hover { border-color: var(--accent); color: var(--accent-strong); }
+.btn--ghost { background: transparent; color: var(--text-2); }
+.btn--ghost:hover { background: var(--bg-raised); color: var(--text-1); }
+.btn--danger { background: transparent; border-color: var(--cinnabar); color: var(--cinnabar); }
+.btn--danger:hover { background: var(--cinnabar-soft); }
+.btn--sm { font-size: 12.5px; padding: 4px 11px; }
+
+/* 状态徽章:文化状态 · 直白状态 · 稳定值 */
+.badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 12px;
+ padding: 2px 10px;
+ border-radius: 999px;
+ border: 1px solid var(--line);
+ color: var(--text-2);
+ white-space: nowrap;
+}
+.badge__dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
+.badge .mono { font-size: 11px; color: var(--text-3); }
+.badge--ok { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 35%, transparent); }
+.badge--ok .badge__dot { background: var(--ok); }
+.badge--info { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 35%, transparent); }
+.badge--info .badge__dot { background: var(--accent); }
+.badge--info .badge__dot { animation: pulse 2.2s ease-in-out infinite; }
+.badge--warn { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 40%, transparent); }
+.badge--warn .badge__dot { background: var(--warn); }
+.badge--err { color: var(--err); border-color: color-mix(in srgb, var(--err) 40%, transparent); }
+.badge--err .badge__dot { background: var(--err); }
+.badge--muted .badge__dot { background: var(--muted); }
+
+@keyframes pulse { 50% { opacity: 0.35; } }
+
+/* 镜像头像:铜镜圆 + 衬线单字 */
+.avatar {
+ width: 38px;
+ height: 38px;
+ border-radius: 50%;
+ flex: none;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-family: var(--font-serif);
+ font-size: 17px;
+ color: #eef4f1;
+ background:
+ radial-gradient(circle at 32% 28%, rgba(255, 255, 255, 0.3), transparent 45%),
+ conic-gradient(from 210deg, #37514d, #5e9a8d 45%, #46565b 85%, #37514d);
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.14), inset 0 0 0 4px rgba(0, 0, 0, 0.16);
+}
+.avatar--user {
+ background:
+ radial-gradient(circle at 32% 28%, rgba(255, 255, 255, 0.3), transparent 45%),
+ conic-gradient(from 210deg, #5a4a44, #9a7b6c 45%, #5f5450 85%, #5a4a44);
+}
+.avatar--sm { width: 28px; height: 28px; font-size: 13px; }
+.avatar--lg { width: 64px; height: 64px; font-size: 28px; }
+
+/* 分段控件(界域切换等) */
+.seg {
+ display: inline-flex;
+ background: var(--bg-sunken);
+ border: 1px solid var(--line-soft);
+ border-radius: var(--radius-sm);
+ padding: 2px;
+ gap: 2px;
+}
+.seg__item {
+ border: none;
+ background: transparent;
+ font-family: var(--font-ui);
+ font-size: 12.5px;
+ color: var(--text-2);
+ padding: 3px 12px;
+ border-radius: 5px;
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+}
+.seg__item .cn { font-size: 13px; }
+.seg__item:hover { color: var(--text-1); }
+.seg__item.is-on { background: var(--bg-raised); color: var(--text-1); box-shadow: var(--shadow-soft); }
+.seg__item--sim.is-on { color: var(--warn); }
+.seg__realm-hint { font-size: 11px; color: var(--text-3); }
+
+/* 表单 */
+.input, .textarea {
+ width: 100%;
+ background: var(--bg-sunken);
+ border: 1px solid var(--line);
+ border-radius: var(--radius-sm);
+ color: var(--text-1);
+ font-family: var(--font-ui);
+ font-size: 13.5px;
+ padding: 8px 12px;
+}
+.input::placeholder, .textarea::placeholder { color: var(--text-3); }
+.input:focus, .textarea:focus { outline: none; border-color: var(--accent); }
+
+/* ---------- 首页(镜庭) ---------- */
+.hero { display: flex; align-items: flex-end; gap: 18px; margin-bottom: 26px; flex-wrap: wrap; }
+.hero h1 { font-family: var(--font-serif); font-size: 26px; font-weight: 600; }
+.hero__stats { display: flex; gap: 10px; margin-left: auto; flex-wrap: wrap; }
+.hero__stat {
+ display: flex;
+ flex-direction: column;
+ gap: 1px;
+ padding: 8px 16px;
+ border-radius: var(--radius);
+ background: var(--bg-raised);
+ border: 1px solid var(--line-soft);
+ min-width: 108px;
+}
+.hero__stat b { font-size: 19px; font-family: var(--font-serif); }
+.hero__stat span { font-size: 11.5px; color: var(--text-3); }
+
+.grid-cards {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
+ gap: 14px;
+}
+
+.char-card { display: flex; flex-direction: column; gap: 10px; padding: 18px; }
+.char-card__top { display: flex; align-items: center; gap: 12px; }
+.char-card__name { font-family: var(--font-serif); font-size: 17px; font-weight: 600; }
+.char-card__line { font-size: 12.5px; color: var(--text-2); line-height: 1.5; }
+.char-card__foot { display: flex; align-items: center; justify-content: space-between; margin-top: 2px; }
+.char-card__foot .badge { flex: none; }
+.char-card__time { font-size: 11.5px; color: var(--text-3); white-space: nowrap; flex: none; }
+
+.home-cols {
+ display: grid;
+ grid-template-columns: 1.5fr 1fr;
+ gap: 16px;
+ margin-top: 26px;
+ align-items: start;
+}
+
+.list-row {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 11px 4px;
+ border-bottom: 1px solid var(--line-soft);
+}
+.list-row:last-child { border-bottom: none; }
+.list-row__body { flex: 1; min-width: 0; }
+.list-row__title { font-size: 13.5px; color: var(--text-1); display: flex; align-items: center; gap: 8px; }
+.list-row__sub { font-size: 12px; color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.list-row__time { font-size: 11.5px; color: var(--text-3); white-space: nowrap; }
+
+.realm-dot {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+ font-size: 11px;
+ color: var(--text-3);
+}
+.realm-dot::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--muted); }
+.realm-dot--world::before { background: var(--accent); }
+.realm-dot--sim::before { background: var(--warn); }
+
+/* 进度条 */
+.progress { height: 5px; border-radius: 3px; background: var(--bg-sunken); overflow: hidden; }
+.progress__bar { height: 100%; border-radius: 3px; background: var(--accent); }
+.progress--warn .progress__bar { background: var(--warn); }
+
+/* ---------- 角色页(镜生) ---------- */
+.char-layout {
+ display: grid;
+ grid-template-columns: 300px 1fr;
+ gap: 20px;
+ align-items: start;
+}
+
+.char-list-item {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 11px 12px;
+ border-radius: var(--radius);
+ border: 1px solid transparent;
+ cursor: pointer;
+}
+.char-list-item:hover { background: var(--bg-raised); }
+.char-list-item.is-active { background: var(--bg-raised); border-color: var(--line); }
+.char-list-item__name { font-family: var(--font-serif); font-size: 15px; }
+.char-list-item__sub { font-size: 11.5px; color: var(--text-3); }
+
+.char-head {
+ display: flex;
+ gap: 18px;
+ align-items: flex-start;
+ margin-bottom: 20px;
+ flex-wrap: wrap;
+}
+.char-head__title { display: flex; flex-direction: column; gap: 6px; }
+.char-head h2 { font-family: var(--font-serif); font-size: 24px; font-weight: 600; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
+.char-head__motto { color: var(--text-2); font-size: 13.5px; }
+.char-head__actions { margin-left: auto; display: flex; gap: 8px; flex-wrap: wrap; }
+
+.section { margin-bottom: 20px; }
+.section__head {
+ display: flex;
+ align-items: baseline;
+ gap: 10px;
+ margin-bottom: 10px;
+}
+.section__head .cn { font-size: 16.5px; font-weight: 600; }
+.section__head .more { margin-left: auto; font-size: 12.5px; }
+
+/* 内驱力维度条 */
+.drive-row { display: grid; grid-template-columns: 72px 1fr 44px; align-items: center; gap: 14px; padding: 7px 0; }
+.drive-row__label { font-size: 13px; color: var(--text-2); }
+.drive-row__val { font-size: 12px; color: var(--text-3); text-align: right; }
+
+/* 谱系时间线 */
+.timeline { position: relative; padding-left: 20px; }
+.timeline::before {
+ content: "";
+ position: absolute;
+ left: 5px;
+ top: 8px;
+ bottom: 8px;
+ width: 1px;
+ background: var(--line);
+}
+.timeline__item { position: relative; padding: 0 0 16px 14px; }
+.timeline__item:last-child { padding-bottom: 0; }
+.timeline__item::before {
+ content: "";
+ position: absolute;
+ left: -20px;
+ top: 7px;
+ width: 11px;
+ height: 11px;
+ border-radius: 50%;
+ background: var(--bg);
+ border: 2px solid var(--accent);
+}
+.timeline__item--muted::before { border-color: var(--muted); }
+.timeline__date { font-size: 11.5px; color: var(--text-3); font-family: var(--font-mono); }
+.timeline__title { font-size: 13.5px; }
+.timeline__sub { font-size: 12.5px; color: var(--text-2); }
+
+/* 危险区 */
+.danger-zone { border-color: color-mix(in srgb, var(--cinnabar) 30%, transparent); }
+.danger-zone .card__head .cn { color: var(--cinnabar); }
+
+/* ---------- 对话页(镜树) ---------- */
+.conv-layout {
+ display: grid;
+ grid-template-columns: 268px 1fr 320px;
+ height: calc(100vh - 54px);
+ min-height: 0;
+}
+
+.conv-side {
+ border-right: 1px solid var(--line-soft);
+ overflow-y: auto;
+ padding: 14px 12px;
+ background: var(--bg);
+}
+
+.conv-side__head { display: flex; align-items: center; gap: 10px; padding: 4px 6px 14px; }
+.conv-side__head .cn { font-size: 15px; font-weight: 600; }
+
+.conv-item {
+ display: block;
+ padding: 10px 12px;
+ border-radius: var(--radius-sm);
+ color: var(--text-2);
+ margin-bottom: 2px;
+}
+.conv-item:hover { background: var(--bg-raised); color: var(--text-1); }
+.conv-item.is-active { background: var(--accent-soft); color: var(--text-1); }
+.conv-item__title { display: flex; align-items: center; gap: 8px; font-size: 13.5px; }
+.conv-item__sub { font-size: 12px; color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-top: 1px; }
+
+.conv-main {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+ overflow: hidden;
+}
+.conv-stream { flex: 1; overflow-y: auto; padding: 26px 0 12px; }
+.conv-stream__inner { max-width: 780px; margin: 0 auto; padding: 0 26px; }
+
+/* 界域提示条 */
+.realm-banner {
+ display: flex;
+ align-items: center;
+ gap: 9px;
+ font-size: 12.5px;
+ color: var(--text-2);
+ background: var(--accent-soft);
+ border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
+ border-radius: var(--radius-sm);
+ padding: 7px 14px;
+ margin-bottom: 22px;
+}
+.realm-banner--sim {
+ color: var(--warn);
+ background: color-mix(in srgb, var(--warn) 9%, transparent);
+ border-color: color-mix(in srgb, var(--warn) 32%, transparent);
+}
+.realm-banner svg { width: 15px; height: 15px; flex: none; }
+.realm-banner::before {
+ content: "";
+ width: 7px; height: 7px;
+ border-radius: 50%;
+ background: currentColor;
+ opacity: 0.75;
+ flex: none;
+}
+
+/* 消息 */
+.msg { margin-bottom: 30px; }
+
+.msg--user { display: flex; flex-direction: column; align-items: flex-end; gap: 5px; }
+.msg__meta { display: flex; align-items: center; gap: 8px; font-size: 11.5px; color: var(--text-3); }
+.msg--user .bubble {
+ background: var(--bg-overlay);
+ border: 1px solid var(--line-soft);
+ border-radius: var(--radius-lg) var(--radius-lg) 4px var(--radius-lg);
+ padding: 11px 16px;
+ max-width: 76%;
+ font-size: 14px;
+ box-shadow: var(--shadow-soft);
+}
+
+.msg--char__head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
+.msg--char__name { font-family: var(--font-serif); font-size: 15.5px; font-weight: 600; }
+.msg--char__meta { display: flex; align-items: center; gap: 8px; margin-left: 2px; }
+
+.msg__blocks { display: flex; flex-direction: column; gap: 12px; padding-left: 48px; }
+
+.msg__actions {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ padding-left: 48px;
+ margin-top: 10px;
+}
+.act-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+ font-size: 12px;
+ color: var(--text-3);
+ background: none;
+ border: none;
+ border-radius: 5px;
+ padding: 4px 9px;
+ cursor: pointer;
+ font-family: var(--font-ui);
+}
+.act-btn:hover { background: var(--bg-raised); color: var(--text-1); }
+.act-btn svg { width: 13.5px; height: 13.5px; }
+
+/* ---------- ContentBlock ---------- */
+.block {
+ background: var(--bg-raised);
+ border: 1px solid var(--line-soft);
+ border-radius: var(--radius);
+ overflow: hidden;
+}
+.block__body { padding: 14px 18px; }
+
+.block--text { background: transparent; border: none; padding: 0; }
+.block--text .block__body { padding: 0; }
+
+.block__head {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 8px 14px;
+ border-bottom: 1px solid var(--line-soft);
+ background: var(--bg-sunken);
+ font-size: 12px;
+ color: var(--text-3);
+}
+.block__head .lang { color: var(--accent); font-family: var(--font-mono); text-transform: lowercase; }
+.block__head .fname { font-family: var(--font-mono); color: var(--text-2); }
+.block__head .acts { margin-left: auto; display: flex; gap: 4px; }
+
+.md p { margin-bottom: 0.8em; }
+.md p:last-child { margin-bottom: 0; }
+.md strong { color: var(--text-1); }
+.md em { color: var(--text-2); }
+.md ul { padding-left: 1.3em; margin-bottom: 0.8em; }
+.md ul:last-child { margin-bottom: 0; }
+.md li { margin-bottom: 0.3em; }
+.md code {
+ font-family: var(--font-mono);
+ font-size: 0.88em;
+ background: var(--bg-sunken);
+ border: 1px solid var(--line-soft);
+ border-radius: 4px;
+ padding: 1px 6px;
+}
+.md a { text-underline-offset: 3px; text-decoration: underline; text-decoration-color: color-mix(in srgb, var(--accent) 45%, transparent); }
+.md .ext::after { content: " ↗"; font-size: 0.82em; color: var(--text-3); }
+
+/* 引文锚 */
+.quote { border-left: 3px solid var(--accent); padding: 2px 0 2px 14px; color: var(--text-2); font-family: var(--font-serif); font-size: 14.5px; }
+.quote__src { display: block; margin-top: 5px; font-family: var(--font-ui); font-size: 11.5px; color: var(--text-3); }
+
+/* 表格 */
+.table-wrap { overflow-x: auto; }
+table.data { border-collapse: collapse; width: 100%; font-size: 13px; }
+table.data th {
+ text-align: left;
+ font-weight: 500;
+ color: var(--text-3);
+ font-size: 12px;
+ border-bottom: 1px solid var(--line);
+ padding: 7px 12px;
+ white-space: nowrap;
+}
+table.data td { padding: 8px 12px; border-bottom: 1px solid var(--line-soft); color: var(--text-2); }
+table.data tr:last-child td { border-bottom: none; }
+table.data td:first-child { color: var(--text-1); font-family: var(--font-serif); }
+
+/* 代码块(手工着色示意) */
+pre.code {
+ font-family: var(--font-mono);
+ font-size: 12.8px;
+ line-height: 1.7;
+ padding: 14px 16px;
+ overflow-x: auto;
+ color: var(--text-2);
+}
+pre.code .k { color: var(--accent); }
+pre.code .s { color: var(--ok); }
+pre.code .c { color: var(--text-3); font-style: italic; }
+pre.code .f { color: var(--cinnabar-strong); }
+
+/* 数学块 */
+.math {
+ font-family: var(--font-serif);
+ font-style: italic;
+ font-size: 16.5px;
+ text-align: center;
+ padding: 16px 12px;
+ letter-spacing: 0.02em;
+}
+.math sub, .math sup { font-size: 0.68em; }
+.math__fallback { display: block; margin-top: 8px; font-family: var(--font-ui); font-style: normal; font-size: 11.5px; color: var(--text-3); text-align: center; }
+
+/* Mermaid 示意(内联 SVG 图) */
+.mermaid-box { padding: 18px 14px; overflow-x: auto; }
+.mermaid-cap { padding: 8px 14px; border-top: 1px solid var(--line-soft); font-size: 11.5px; color: var(--text-3); display: flex; gap: 8px; align-items: center; }
+
+/* 音频块 */
+.audio { display: flex; align-items: center; gap: 14px; padding: 13px 16px; }
+.audio__play {
+ width: 36px; height: 36px; border-radius: 50%;
+ border: 1px solid var(--line);
+ background: var(--bg-sunken);
+ color: var(--accent);
+ display: inline-flex; align-items: center; justify-content: center;
+ cursor: pointer; flex: none;
+}
+.audio__play svg { width: 15px; height: 15px; margin-left: 2px; }
+.wave { display: flex; align-items: center; gap: 2.5px; height: 30px; flex: 1; }
+.wave i {
+ width: 3px;
+ border-radius: 2px;
+ background: var(--accent);
+ opacity: 0.55;
+ display: block;
+}
+.audio__time { font-family: var(--font-mono); font-size: 12px; color: var(--text-3); }
+.audio__meta { flex: none; text-align: right; }
+.audio__meta .t { font-size: 12px; color: var(--text-2); display: block; }
+
+/* 工具调用块 */
+.tool { border-left: 3px solid var(--accent); }
+.tool__head { display: flex; align-items: center; gap: 10px; padding: 10px 16px; }
+.tool__name { font-family: var(--font-mono); font-size: 13px; color: var(--text-1); }
+.tool__purpose { font-size: 12px; color: var(--text-3); }
+.tool__body { padding: 0 16px 12px; font-size: 12.5px; color: var(--text-2); }
+.tool__row { display: flex; gap: 8px; padding: 2px 0; }
+.tool__row .k2 { color: var(--text-3); flex: none; }
+.tool__stat { display: flex; gap: 14px; margin-top: 6px; font-size: 11.5px; color: var(--text-3); }
+
+/* 请命(审批)卡 —— 签名组件 */
+.approval { border: 1px solid color-mix(in srgb, var(--cinnabar) 38%, transparent); }
+.approval__head {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 12px 18px;
+ border-bottom: 1px solid color-mix(in srgb, var(--cinnabar) 22%, transparent);
+ background: var(--cinnabar-soft);
+}
+.approval__seal {
+ width: 30px; height: 30px; flex: none;
+ border-radius: 50%;
+ border: 1.5px solid var(--cinnabar);
+ color: var(--cinnabar);
+ display: inline-flex; align-items: center; justify-content: center;
+ font-family: var(--font-serif); font-size: 13px; font-weight: 600;
+ background: var(--bg-raised);
+}
+.approval__title { font-size: 14px; }
+.approval__title .cn { font-size: 15.5px; font-weight: 600; color: var(--cinnabar); }
+.approval__body { padding: 13px 18px; display: flex; flex-direction: column; gap: 8px; }
+.fact { display: grid; grid-template-columns: 88px 1fr; gap: 10px; font-size: 13px; }
+.fact__k { color: var(--text-3); }
+.fact__v { color: var(--text-1); }
+.fact__v .mono { font-size: 12px; color: var(--text-2); }
+.approval__acts { display: flex; gap: 10px; align-items: center; padding: 0 18px 15px; }
+.approval__note { font-size: 11.5px; color: var(--text-3); margin-left: auto; max-width: 220px; }
+.approval--done { border-color: var(--line); }
+.approval--done .approval__head { background: transparent; border-bottom-color: var(--line-soft); }
+.approval--done .approval__seal { border-color: var(--ok); color: var(--ok); }
+
+/* 候答 */
+.candidates { display: flex; align-items: center; gap: 10px; padding-left: 48px; margin-top: -14px; margin-bottom: 26px; }
+.cand-switch { display: inline-flex; gap: 4px; }
+.cand-tag {
+ border: 1px solid var(--line);
+ background: var(--bg-sunken);
+ color: var(--text-3);
+ font-size: 12px;
+ border-radius: 999px;
+ padding: 3px 12px;
+ cursor: pointer;
+}
+.cand-tag.is-on { border-color: var(--accent); color: var(--accent-strong); background: var(--accent-soft); }
+
+/* 残答折叠 */
+.sealed {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 10px 16px;
+ border: 1px dashed var(--line);
+ border-radius: var(--radius);
+ font-size: 12.5px;
+ color: var(--text-3);
+ padding-left: 48px;
+ border-left: none; border-right: none; border-top: none;
+ margin-left: 0;
+}
+.sealed__body { flex: 1; }
+.sealed__t { color: var(--text-2); }
+
+/* 息壤候选(记忆候选卡) */
+.memory-cand { border-left: 3px solid var(--warn); }
+.memory-cand__head { display: flex; align-items: center; gap: 10px; padding: 11px 16px; }
+.memory-cand__body { padding: 0 16px 13px; font-size: 13px; color: var(--text-2); }
+.memory-cand__body .q { font-family: var(--font-serif); color: var(--text-1); font-size: 14px; }
+.memory-cand__acts { display: flex; gap: 8px; margin-top: 10px; }
+.mem-type {
+ font-size: 11px;
+ border: 1px solid var(--line);
+ border-radius: 4px;
+ padding: 1px 7px;
+ color: var(--text-3);
+}
+
+/* 临时芽体(流式) */
+.sprout { border: 1px dashed color-mix(in srgb, var(--accent) 45%, transparent); background: transparent; }
+.sprout__head { display: flex; align-items: center; gap: 10px; padding: 9px 16px; font-size: 12px; color: var(--accent); border-bottom: 1px dashed var(--line); }
+.sprout__body { padding: 13px 16px; }
+.stream-cursor {
+ display: inline-block;
+ width: 8px; height: 17px;
+ background: var(--accent);
+ vertical-align: -3px;
+ margin-left: 2px;
+ animation: blink 1.05s step-end infinite;
+ border-radius: 1px;
+}
+@keyframes blink { 50% { opacity: 0; } }
+.src-line { font-family: var(--font-mono); font-size: 13px; color: var(--text-2); white-space: pre-wrap; }
+.src-line .unclosed { color: var(--text-3); }
+
+/* 输入区 */
+.composer { border-top: 1px solid var(--line-soft); padding: 14px 26px 18px; background: var(--bg); }
+.composer__inner { max-width: 780px; margin: 0 auto; }
+.composer__box {
+ background: var(--bg-raised);
+ border: 1px solid var(--line);
+ border-radius: var(--radius-lg);
+ padding: 12px 14px;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+.composer__box:focus-within { border-color: var(--accent); }
+.composer__ta {
+ background: none; border: none; resize: none;
+ color: var(--text-1);
+ font-family: var(--font-ui);
+ font-size: 14px;
+ line-height: 1.6;
+ min-height: 44px;
+ outline: none;
+}
+.composer__bar { display: flex; align-items: center; gap: 6px; }
+.composer__bar .spacer { flex: 1; }
+.composer__hint { display: flex; gap: 14px; margin-top: 8px; font-size: 11.5px; color: var(--text-3); flex-wrap: wrap; }
+.composer__hint .k { color: var(--text-2); }
+
+/* ---------- 右栏:镜枝面板 ---------- */
+.conv-ctx {
+ border-left: 1px solid var(--line-soft);
+ overflow-y: auto;
+ padding: 18px 16px 30px;
+ background: var(--bg);
+}
+.ctx-section { margin-bottom: 24px; }
+.ctx-section__head {
+ display: flex; align-items: baseline; gap: 8px;
+ margin-bottom: 10px;
+}
+.ctx-section__head .cn { font-size: 14.5px; font-weight: 600; }
+.ctx-section__head .more { margin-left: auto; font-size: 11.5px; }
+
+/* 分支树 */
+.tree { font-size: 12.5px; }
+.tree ul { list-style: none; padding-left: 18px; position: relative; }
+.tree > ul { padding-left: 2px; }
+.tree ul::before {
+ content: "";
+ position: absolute;
+ left: 6px; top: 4px; bottom: 12px;
+ width: 1px;
+ background: color-mix(in srgb, var(--text-3) 55%, transparent);
+}
+.tree li { position: relative; padding: 3px 0 3px 14px; }
+.tree li::before {
+ content: "";
+ position: absolute;
+ left: 0; top: 13px;
+ width: 12px; height: 1px;
+ background: color-mix(in srgb, var(--text-3) 55%, transparent);
+}
+.tree li:last-child::after {
+ content: "";
+ position: absolute;
+ left: -12px; top: 15px; bottom: 0;
+ width: 1px;
+ background: var(--bg);
+}
+.tree__node { display: flex; align-items: center; gap: 7px; color: var(--text-2); }
+.tree__node .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.tree__node--main .nm { color: var(--text-1); font-weight: 500; }
+.tree__node--now {
+ background: var(--accent-soft);
+ border-radius: 5px;
+ outline: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
+ padding: 2px 6px;
+ margin-left: -6px;
+ color: var(--accent-strong);
+}
+.tree__node--sealed { color: var(--text-3); }
+.tree__tag { font-size: 10.5px; border-radius: 4px; padding: 0 6px; flex: none; border: 1px solid var(--line); color: var(--text-3); }
+.tree__tag--main { border-color: color-mix(in srgb, var(--accent) 40%, transparent); color: var(--accent); }
+.tree__tag--seal { border-color: var(--line); color: var(--text-3); text-decoration: line-through; }
+.tree__tag--bud { border-color: color-mix(in srgb, var(--accent) 40%, transparent); color: var(--accent); animation: pulse 2.2s ease-in-out infinite; }
+
+/* 上下文镜 */
+.ctx-mirror__row { display: flex; gap: 9px; padding: 6px 0; font-size: 12.5px; align-items: baseline; }
+.ctx-mirror__type { flex: none; width: 34px; color: var(--text-3); }
+.ctx-mirror__txt { color: var(--text-2); flex: 1; }
+.ctx-mirror__src { font-size: 11px; color: var(--text-3); white-space: nowrap; }
+.ctx-mirror__omitted { color: var(--text-3); font-size: 12px; padding: 4px 0; }
+.ctx-meter { margin-top: 10px; }
+.ctx-meter__label { display: flex; justify-content: space-between; font-size: 11.5px; color: var(--text-3); margin-bottom: 5px; }
+
+/* ---------- 索引页(设计说明) ---------- */
+.doc { max-width: 980px; }
+.doc-hero { padding: 44px 0 30px; border-bottom: 1px solid var(--line-soft); margin-bottom: 34px; }
+.doc-hero h1 { font-family: var(--font-serif); font-size: 34px; font-weight: 600; margin-bottom: 10px; display: flex; align-items: center; gap: 16px; }
+.doc-hero p { color: var(--text-2); max-width: 640px; }
+.doc-hero .ver { font-family: var(--font-mono); font-size: 13px; color: var(--accent); border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent); border-radius: 999px; padding: 3px 12px; vertical-align: middle; }
+
+.doc-sec { margin-bottom: 40px; }
+.doc-sec > h2 { font-family: var(--font-serif); font-size: 20px; font-weight: 600; margin-bottom: 6px; }
+.doc-sec > .sub { color: var(--text-3); font-size: 13px; margin-bottom: 18px; }
+
+.page-nav { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 12px; }
+.page-nav a.card { display: flex; flex-direction: column; gap: 6px; color: var(--text-1); transition: border-color 0.15s; }
+.page-nav a.card:hover { border-color: var(--accent); }
+.page-nav .cn { font-size: 16px; font-weight: 600; }
+.page-nav .d { font-size: 12.5px; color: var(--text-3); }
+.page-nav .p { font-family: var(--font-mono); font-size: 11px; color: var(--accent); }
+
+.swatches { display: grid; grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); gap: 10px; }
+.swatch { border: 1px solid var(--line-soft); border-radius: var(--radius-sm); overflow: hidden; }
+.swatch__c { height: 52px; }
+.swatch__m { padding: 7px 10px; font-size: 11.5px; color: var(--text-2); background: var(--bg-raised); }
+.swatch__m b { display: block; font-size: 12px; color: var(--text-1); font-weight: 500; }
+.swatch__m .mono { color: var(--text-3); font-size: 10.5px; }
+
+.type-spec { display: flex; flex-direction: column; gap: 14px; }
+.type-spec__row { display: flex; align-items: baseline; gap: 20px; border-bottom: 1px dashed var(--line-soft); padding-bottom: 13px; flex-wrap: wrap; }
+.type-spec__label { width: 130px; flex: none; font-size: 12px; color: var(--text-3); }
+.type-spec__sample { color: var(--text-1); }
+
+.demo-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-bottom: 14px; }
+.demo-block { margin-bottom: 22px; }
+
+.badge-table td:first-child { font-family: var(--font-ui); }
+
+/* ---------- 响应式 ---------- */
+@media (max-width: 1200px) {
+ .conv-layout { grid-template-columns: 240px 1fr; }
+ .conv-ctx { display: none; }
+}
+
+@media (max-width: 960px) {
+ .app { grid-template-columns: 64px 1fr; }
+ .brand__text, .nav-item .cn, .nav-item .nav-plain, .nav-group__title { display: none; }
+ .nav-item { justify-content: center; }
+ .brand { justify-content: center; padding: 16px 0 10px; }
+ .home-cols { grid-template-columns: 1fr; }
+ .char-layout { grid-template-columns: 1fr; }
+ .conv-layout { grid-template-columns: 1fr; }
+ .conv-side { display: none; }
+ .content { padding: 20px 18px 40px; }
+ .prototype-tag, .topbar .realm-dot { display: none; }
+ .crumbs .crumbs__sep, .crumbs .cn { display: none; }
+}
+
+/* ============================================================
+ 第二批页面组件(息壤/形仪/天工阁/百工坊/雅集/同契台/云市/神识阁/器谱/司命台/设置)
+ ============================================================ */
+
+/* 页内选项卡 */
+.tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--line-soft); margin-bottom: 22px; overflow-x: auto; }
+.tab {
+ border: none; background: none; cursor: pointer;
+ font-family: var(--font-ui); font-size: 13.5px; color: var(--text-2);
+ padding: 9px 14px 10px; border-bottom: 2px solid transparent; margin-bottom: -1px;
+ display: inline-flex; align-items: center; gap: 7px; white-space: nowrap;
+}
+.tab .cnt { font-size: 11px; color: var(--text-3); border: 1px solid var(--line); border-radius: 999px; padding: 0 7px; }
+.tab:hover { color: var(--text-1); }
+.tab.is-on { color: var(--accent-strong); border-bottom-color: var(--accent); }
+.tab.is-on .cnt { border-color: color-mix(in srgb, var(--accent) 40%, transparent); color: var(--accent); }
+
+/* chip 筛选 */
+.chips { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; }
+.chip {
+ font-size: 12.5px; color: var(--text-2);
+ border: 1px solid var(--line); border-radius: 999px;
+ padding: 3px 13px; cursor: pointer; background: none; font-family: var(--font-ui);
+ display: inline-flex; align-items: center; gap: 6px;
+}
+.chip:hover { color: var(--text-1); border-color: var(--text-3); }
+.chip.is-on { color: var(--accent-strong); border-color: var(--accent); background: var(--accent-soft); }
+
+/* 搜索条 */
+.searchbar { display: flex; align-items: center; gap: 9px; background: var(--bg-sunken); border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 7px 12px; }
+.searchbar svg { width: 15px; height: 15px; color: var(--text-3); flex: none; }
+.searchbar input { flex: 1; background: none; border: none; outline: none; color: var(--text-1); font-family: var(--font-ui); font-size: 13.5px; min-width: 0; }
+.searchbar input::placeholder { color: var(--text-3); }
+
+/* 开关 */
+.switch { position: relative; width: 36px; height: 20px; flex: none; display: inline-block; }
+.switch input { position: absolute; opacity: 0; inset: 0; cursor: pointer; z-index: 1; margin: 0; }
+.switch i { position: absolute; inset: 0; border-radius: 999px; background: var(--line); transition: background 0.15s; display: block; }
+.switch i::after { content: ""; position: absolute; left: 2px; top: 2px; width: 16px; height: 16px; border-radius: 50%; background: var(--bg-raised); box-shadow: var(--shadow-soft); transition: transform 0.15s; }
+.switch input:checked + i { background: var(--accent); }
+.switch input:checked + i::after { transform: translateX(16px); }
+.switch input:focus-visible + i { outline: 2px solid var(--accent); outline-offset: 2px; }
+
+/* 设置行 */
+.set-row { display: flex; align-items: center; gap: 14px; padding: 13px 0; border-bottom: 1px solid var(--line-soft); }
+.set-row:last-child { border-bottom: none; }
+.set-row__body { flex: 1; min-width: 0; }
+.set-row__t { font-size: 13.5px; }
+.set-row__d { font-size: 12px; color: var(--text-3); margin-top: 2px; line-height: 1.55; }
+
+/* 资产 / 商品网格 */
+.asset-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(196px, 1fr)); gap: 14px; }
+.asset-card { display: flex; flex-direction: column; border: 1px solid var(--line-soft); border-radius: var(--radius); overflow: hidden; background: var(--bg-raised); }
+.asset-thumb { height: 118px; position: relative; display: flex; align-items: center; justify-content: center; }
+.asset-thumb .glyph { font-family: var(--font-serif); font-size: 30px; color: rgba(255, 255, 255, 0.88); text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); }
+.asset-type { position: absolute; top: 8px; left: 8px; font-size: 10.5px; color: #fff; background: rgba(0, 0, 0, 0.45); border-radius: 4px; padding: 1px 7px; }
+.asset-flag { position: absolute; top: 8px; right: 8px; }
+.asset-body { padding: 11px 13px 12px; display: flex; flex-direction: column; gap: 5px; flex: 1; }
+.asset-name { font-size: 13.5px; }
+.asset-sub { font-size: 11.5px; color: var(--text-3); }
+.asset-acts { display: flex; gap: 6px; margin-top: auto; padding-top: 6px; flex-wrap: wrap; }
+
+/* 价格(金额直白优先) */
+.price { font-family: var(--font-serif); font-size: 17px; font-weight: 600; }
+.price small { font-size: 12px; font-weight: 400; color: var(--text-3); }
+.price--free { color: var(--ok); }
+
+/* 天工:工筹步骤 */
+.step { display: flex; gap: 12px; position: relative; padding-bottom: 18px; }
+.step:last-child { padding-bottom: 0; }
+.step::before { content: ""; position: absolute; left: 13px; top: 30px; bottom: 2px; width: 1px; background: var(--line); }
+.step:last-child::before { display: none; }
+.step__dot { width: 27px; height: 27px; border-radius: 50%; flex: none; display: flex; align-items: center; justify-content: center; font-size: 12px; border: 1px solid var(--line); color: var(--text-3); background: var(--bg-raised); font-family: var(--font-serif); }
+.step--done .step__dot { border-color: var(--ok); color: var(--ok); }
+.step--run .step__dot { border-color: var(--accent); color: var(--accent-strong); animation: pulse 2.2s ease-in-out infinite; }
+.step__t { font-size: 13.5px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
+.step__d { font-size: 12px; color: var(--text-3); margin-top: 1px; }
+
+/* 用量条形图 */
+.bars { display: flex; align-items: flex-end; gap: 7px; height: 112px; padding-top: 6px; }
+.bars__col { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px; justify-content: flex-end; height: 100%; }
+.bars__bar { width: 100%; max-width: 34px; border-radius: 4px 4px 0 0; background: var(--accent); opacity: 0.8; }
+.bars__label { font-size: 10.5px; color: var(--text-3); }
+
+/* 授权行 */
+.grant-row { display: grid; grid-template-columns: 122px 1fr auto; gap: 12px; align-items: center; padding: 11px 0; border-bottom: 1px solid var(--line-soft); }
+.grant-row:last-child { border-bottom: none; }
+.grant-row__cap { display: flex; align-items: center; gap: 7px; font-size: 13px; }
+.grant-row__cap svg { width: 15px; height: 15px; color: var(--text-3); flex: none; }
+.grant-row__meta { font-size: 12px; color: var(--text-3); margin-top: 2px; line-height: 1.5; }
+
+/* 帖子卡(雅集) */
+.post { padding: 16px 18px; }
+.post__head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
+.post__author { font-size: 13.5px; }
+.post__time { font-size: 11.5px; color: var(--text-3); }
+.post__body { font-size: 13.5px; color: var(--text-2); display: flex; flex-direction: column; gap: 8px; }
+.post__foot { display: flex; gap: 4px; margin-top: 12px; }
+
+/* 双栏(主内容 + 侧栏) */
+.two-col { display: grid; grid-template-columns: 1fr 320px; gap: 18px; align-items: start; }
+
+/* 玄络图容器 */
+.graph-box { padding: 10px; }
+
+/* 权限矩阵标记 */
+.mark-ok { color: var(--ok); }
+.mark-no { color: var(--text-3); opacity: 0.45; }
+
+/* 治理案件卡 */
+.case-card { border-left: 3px solid var(--warn); }
+
+@media (max-width: 1100px) {
+ .two-col { grid-template-columns: 1fr; }
+}
+
+/* ============================================================
+ 铜镜鉴心传承:鎏金发丝线(承接 main 分支旧版设计体系)
+ ============================================================ */
+:root {
+ --gilt: #c9a24a;
+ --gilt-dim: rgba(201, 162, 74, 0.34);
+ --gilt-faint: rgba(201, 162, 74, 0.14);
+}
+[data-theme="light"] {
+ --gilt: #8f7226;
+ --gilt-dim: rgba(143, 114, 38, 0.4);
+ --gilt-faint: rgba(143, 114, 38, 0.15);
+}
+
+.brand { cursor: pointer; }
+.brand__mark { box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.16), inset 0 0 0 4px rgba(0, 0, 0, 0.16), 0 0 0 1px var(--gilt-dim); }
+.nav-group__title { color: var(--gilt-dim); }
+.topbar { border-bottom: 1px solid var(--gilt-faint); }
+.section__head { position: relative; }
+.section__head > .cn:first-child::before {
+ content: "";
+ display: inline-block;
+ width: 3px;
+ height: 13px;
+ border-radius: 2px;
+ background: var(--gilt);
+ margin-right: 8px;
+ vertical-align: -1px;
+}
+.timeline::before { background: var(--gilt-faint); }
+
+/* logo 彩蛋动效 */
+.brand.tick .brand__mark { transform: scale(0.92); }
+.brand__mark { transition: transform 0.18s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s; }
+.brand.egg-pulse .brand__mark {
+ animation: egg-pulse 1.3s ease-out;
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 0 0 4px rgba(0, 0, 0, 0.16), 0 0 0 1px var(--gilt), 0 0 22px var(--gilt-dim);
+}
+@keyframes egg-pulse {
+ 0% { transform: scale(1); }
+ 30% { transform: scale(1.12) rotate(6deg); }
+ 60% { transform: scale(0.96) rotate(-3deg); }
+ 100% { transform: scale(1); }
+}
+.egg-toast {
+ position: fixed;
+ bottom: 48px;
+ left: 50%;
+ transform: translateX(-50%);
+ padding: 12px 28px;
+ border-radius: 12px;
+ background: var(--accent-soft);
+ color: var(--accent-strong);
+ font-family: var(--font-serif);
+ font-size: 14px;
+ border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
+ z-index: 9999;
+ pointer-events: none;
+ opacity: 0;
+ transition: opacity 0.4s;
+ box-shadow: var(--shadow);
+}
+.egg-toast.is-on { opacity: 1; }
+
+/* ============================================================
+ 侧栏折叠系统(主侧栏 + 对话页左右面板)
+ ============================================================ */
+.side-fold-btn { color: var(--text-3); }
+.side-fold-btn svg { transition: transform 0.18s ease; }
+.side-fold-btn:hover { color: var(--gilt); }
+
+.app--fold { grid-template-columns: 64px 1fr; }
+.app--fold .brand__text,
+.app--fold .nav-item .cn,
+.app--fold .nav-item .nav-plain,
+.app--fold .nav-group__title { display: none; }
+.app--fold .nav-item { justify-content: center; }
+.app--fold .brand { justify-content: center; padding: 16px 0 10px; }
+.app--fold .side-fold-btn svg { transform: rotate(180deg); }
+.app--fold .side-foot { flex-direction: column; gap: 6px; padding: 10px 8px; }
+.app--fold .side-foot > span:not(.avatar) { display: none; }
+
+/* 对话页面板折叠 */
+.conv-layout.no-left { grid-template-columns: 0 1fr 320px; }
+.conv-layout.no-left .conv-side { display: none; }
+.conv-layout.no-right { grid-template-columns: 268px 1fr 0; }
+.conv-layout.no-right .conv-ctx { display: none; }
+.panel-toggle {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
+ border-radius: var(--radius-sm);
+ border: 1px solid var(--line);
+ background: var(--bg-raised);
+ color: var(--text-3);
+ cursor: pointer;
+}
+.panel-toggle:hover { color: var(--gilt); border-color: var(--gilt-dim); }
+.panel-toggle svg { width: 15px; height: 15px; }
+.conv-restore {
+ position: absolute;
+ top: 14px;
+ z-index: 15;
+ width: 32px;
+ height: 32px;
+ border-radius: var(--radius-sm);
+ border: 1px solid var(--line);
+ background: var(--bg-raised);
+ color: var(--text-2);
+ cursor: pointer;
+ display: none;
+ align-items: center;
+ justify-content: center;
+ box-shadow: var(--shadow-soft);
+}
+.conv-restore:hover { color: var(--gilt); border-color: var(--gilt-dim); }
+.conv-restore svg { width: 16px; height: 16px; }
+.conv-restore--left { left: 14px; }
+.conv-restore--right { right: 14px; }
+.conv-layout.no-left .conv-restore--left { display: inline-flex; }
+.conv-layout.no-right .conv-restore--right { display: inline-flex; }
+
+/* ============================================================
+ 登录 / 注册页(StageShell · 铜镜鉴心,承接旧版铜镜登录页)
+ ============================================================ */
+.stage {
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ overflow-x: hidden;
+ background: var(--bg);
+}
+
+.stage__bg { position: fixed; inset: 0; z-index: -1; pointer-events: none; }
+.stage__rings {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+.stage__rings svg { width: min(94vmin, 880px); height: min(94vmin, 880px); opacity: 0.16; }
+[data-theme="light"] .stage__rings svg { opacity: 0.1; }
+.ring-ink { fill: none; stroke: var(--gilt-faint); stroke-width: 0.6; }
+.ring-faint { fill: none; stroke: var(--gilt-faint); stroke-width: 0.35; stroke-dasharray: 2 7; }
+.ring-slow { transform-origin: center; animation: ring-spin 140s linear infinite; }
+@keyframes ring-spin { to { transform: rotate(360deg); } }
+
+.stage__topbar {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 16px clamp(20px, 4vw, 44px);
+}
+.stage__topbar .spacer { flex: 1; }
+
+.stage__main {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 24px 18px 48px;
+}
+
+/* 铜镜卡:唯一的镜面 */
+.mirror-card {
+ width: min(408px, 94vw);
+ background: var(--bg-raised);
+ border: 1px solid var(--gilt-dim);
+ border-radius: 18px;
+ padding: 38px 36px 30px;
+ position: relative;
+ overflow: hidden;
+ box-shadow: var(--shadow), inset 0 1px 0 rgba(255, 255, 255, 0.05);
+}
+.mirror-card::after {
+ /* 扫描亮带:铜镜在烛光下微倾时的漫反射 */
+ content: "";
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ height: 34%;
+ background: linear-gradient(178deg, transparent, rgba(201, 162, 74, 0.05) 45%, rgba(255, 255, 255, 0.045) 55%, transparent);
+ pointer-events: none;
+ animation: mirror-scan 7.5s ease-in-out infinite;
+}
+@keyframes mirror-scan {
+ 0% { transform: translateY(-110%); }
+ 55% { transform: translateY(320%); }
+ 100% { transform: translateY(320%); }
+}
+
+.mirror-card__brand { display: flex; align-items: center; gap: 12px; justify-content: center; margin-bottom: 8px; }
+.mirror-card__brand .brand__mark { width: 40px; height: 40px; }
+.mirror-card__title { text-align: center; }
+.mirror-card__title h1 { font-family: var(--font-serif); font-size: 24px; font-weight: 600; letter-spacing: 0.04em; }
+.mirror-card__sub { text-align: center; font-size: 12.5px; color: var(--text-3); margin: 6px 0 26px; }
+
+.field-group { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
+.field-group label { font-size: 12.5px; color: var(--text-2); }
+.field-group .input { background: var(--bg-sunken); }
+.field-row { display: flex; gap: 10px; }
+.field-row .input { flex: 1; }
+
+.login-alt {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin: 18px 0 14px;
+ color: var(--text-3);
+ font-size: 11.5px;
+}
+.login-alt::before, .login-alt::after { content: ""; flex: 1; height: 1px; background: var(--line-soft); }
+
+.login-note { font-size: 11.5px; color: var(--text-3); line-height: 1.7; text-align: center; margin-top: 16px; }
+.login-foot { text-align: center; font-size: 13px; margin-top: 18px; color: var(--text-2); }
+
+.check-row { display: flex; gap: 10px; align-items: flex-start; margin-bottom: 12px; }
+.check-row input[type="checkbox"] { margin-top: 3px; accent-color: var(--accent); width: 15px; height: 15px; flex: none; }
+.check-row .t { font-size: 12.5px; color: var(--text-2); line-height: 1.6; }
+.check-row .t b { color: var(--text-1); font-weight: 500; }
+
+@media (prefers-reduced-motion: reduce) {
+ .mirror-card::after, .ring-slow { animation: none; }
+}
+
+/* 面板收起后内容区自适应放宽(平滑过渡) */
+.conv-stream__inner, .composer__inner { transition: max-width 0.25s ease; }
+.conv-layout.no-left .conv-stream__inner,
+.conv-layout.no-left .composer__inner,
+.conv-layout.no-right .conv-stream__inner,
+.conv-layout.no-right .composer__inner { max-width: 1000px; }
+.conv-layout.no-left.no-right .conv-stream__inner,
+.conv-layout.no-left.no-right .composer__inner { max-width: 1140px; }
+
+/* 双面板同时收起:不留空轨道,内容占满 */
+.conv-layout.no-left.no-right { grid-template-columns: 0 1fr 0; }
+
+/* 显式网格列位:面板收起(display:none)后主区不因自动放置错位到空轨道 */
+.conv-side { grid-column: 1; }
+.conv-main { grid-column: 2; }
+.conv-ctx { grid-column: 3; }
+
+/* ============================================================
+ 旧版「铜镜鉴心」传承细节(本轮系统挖掘 main 分支 25 页后融入)
+ ============================================================ */
+
+/* 极光铜晕:极淡的三色径向氛围底(承接 _base.css body::before) */
+body::before {
+ content: "";
+ position: fixed;
+ inset: 0;
+ z-index: -1;
+ pointer-events: none;
+ background:
+ radial-gradient(60vmax 60vmax at 12% -10%, rgba(88, 169, 156, 0.05), transparent 60%),
+ radial-gradient(50vmax 50vmax at 105% 15%, rgba(201, 162, 74, 0.035), transparent 60%),
+ radial-gradient(46vmax 46vmax at 50% 115%, rgba(196, 87, 74, 0.022), transparent 62%);
+}
+
+/* 卡片 hover 光扫(承接 --sweep-gradient 手法,克制到接近不可见) */
+.card { position: relative; }
+.card::after {
+ content: "";
+ position: absolute;
+ inset: 0;
+ border-radius: inherit;
+ pointer-events: none;
+ background: linear-gradient(115deg, transparent 30%, rgba(201, 162, 74, 0.03) 48%, rgba(255, 255, 255, 0.035) 52%, transparent 70%);
+ opacity: 0;
+ transition: opacity 0.35s ease;
+}
+.card:hover::after { opacity: 1; }
+
+/* 天工阁:进行中步骤的「动爻」鎏金光晕(爻象手法的 1.0 化:done=铜绿已闭,active=鎏金流转) */
+.step--run .step__dot {
+ background: radial-gradient(circle at 50% 40%, color-mix(in srgb, var(--gilt) 30%, var(--bg-raised)), var(--bg-raised));
+ box-shadow: 0 0 0 3px var(--gilt-faint), 0 0 14px var(--gilt-dim);
+}
+
+/* 镜庭:日晷时辰盘(承接 time-preview 日晷,静态原型固定巳时) */
+.sundial { position: relative; width: 116px; height: 116px; flex: none; }
+.sundial svg { width: 100%; height: 100%; display: block; }
+.sundial__center {
+ position: absolute; inset: 0;
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
+ pointer-events: none;
+}
+.sundial__hour { font-family: var(--font-serif); font-size: 17px; font-weight: 600; color: var(--gilt); }
+.sundial__sub { font-size: 9.5px; color: var(--text-3); margin-top: 1px; }
+.sundial-spin { transform-origin: 58px 58px; animation: sundial-spin 120s linear infinite; }
+@keyframes sundial-spin { to { transform: rotate(360deg); } }
+@media (prefers-reduced-motion: reduce) { .sundial-spin { animation: none; } }
+
+/* 请命卡:风险等级徽章(承接朱批 RISK 分层) */
+.risk-badge {
+ font-family: var(--font-mono);
+ font-size: 10.5px;
+ letter-spacing: 0.04em;
+ border-radius: 4px;
+ padding: 1px 7px;
+ border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent);
+ color: var(--warn);
+}
+.risk-badge--low { border-color: color-mix(in srgb, var(--accent) 40%, transparent); color: var(--accent); }
+
+/* 玄络:待验假设边(承接河图 hypothesis 朱漆虚线呼吸) */
+.tent-edge { stroke-dasharray: 5 5; animation: tent-pulse 2.8s ease-in-out infinite; }
+@keyframes tent-pulse { 50% { opacity: 0.35; } }
+@media (prefers-reduced-motion: reduce) { .tent-edge { animation: none; } }
+
+/* ============================================================
+ main 旧版设计复刻件(概念映射到 1.0:六爻→小周天阶段、九枢→灵台六模块、
+ 周天四镜→大周天四阶段、好奇萤火→内驱力、五行相克→运营指标对)
+ ============================================================ */
+
+/* ---- 灵台六枢星盘(复刻九枢星盘「活的仪器」,映射灵台六模块 ADR-016/017) ---- */
+.astrolabe-wrap { display: flex; gap: 22px; align-items: center; flex-wrap: wrap; }
+.astrolabe { width: 262px; flex: none; }
+.astrolabe svg { width: 100%; height: auto; display: block; }
+.ast-node { cursor: pointer; }
+.ast-node:hover .ast-node__ring { stroke: var(--gilt); }
+.ast-core-text { font-family: var(--font-serif); fill: var(--text-1); }
+.ast-node__name { font-family: var(--font-serif); font-size: 12.5px; fill: var(--text-1); }
+.ast-node__plain { font-size: 8.5px; fill: var(--text-3); }
+.ast-spin { transform-origin: 160px 160px; animation: ast-spin 120s linear infinite; }
+@keyframes ast-spin { to { transform: rotate(360deg); } }
+.ast-breathe { animation: ast-breathe 7s ease-in-out infinite; transform-origin: 160px 160px; }
+@keyframes ast-breathe { 50% { opacity: 0.25; } }
+.ast-alert { animation: ast-alert 1.8s ease-in-out infinite; }
+@keyframes ast-alert { 50% { opacity: 0.2; } }
+@media (prefers-reduced-motion: reduce) {
+ .ast-spin, .ast-breathe, .ast-alert { animation: none; }
+}
+
+/* 铜镜四态健康词汇(复刻 MirrorState) */
+.mirror-state { display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px; }
+.mirror-state i { width: 10px; height: 10px; border-radius: 50%; flex: none; }
+.mirror-state--steady i { background: var(--ok); box-shadow: 0 0 8px color-mix(in srgb, var(--ok) 55%, transparent); }
+.mirror-state--cracked i { background: var(--err); }
+
+/* 相克告警卡(复刻五行相克告警,映射为运营指标对) */
+.clash-row { display: grid; grid-template-columns: 1fr 52px; gap: 10px; align-items: center; padding: 8px 0; border-bottom: 1px dashed var(--line-soft); }
+.clash-row:last-child { border-bottom: none; }
+.clash-row__pair { font-size: 12.5px; color: var(--text-2); }
+.clash-row__pair b { color: var(--text-1); font-weight: 500; }
+.clash-strength { height: 5px; border-radius: 3px; background: var(--bg-sunken); overflow: hidden; }
+.clash-strength i { display: block; height: 100%; background: var(--warn); border-radius: 3px; }
+
+/* ---- 大周天四镜(复刻周天四镜阶段链,映射 ADR-023 四阶段) ---- */
+.cycle-mirrors { display: flex; align-items: center; gap: 6px; margin-bottom: 14px; }
+.cycle-mirror {
+ flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px;
+ padding: 12px 6px 10px; border-radius: var(--radius);
+ background: var(--bg-sunken); border: 1px solid var(--line-soft);
+}
+.cycle-mirror__disc {
+ width: 44px; height: 44px; border-radius: 50%;
+ display: flex; align-items: center; justify-content: center;
+ font-family: var(--font-serif); font-size: 18px;
+ background:
+ radial-gradient(circle at 34% 28%, rgba(255, 255, 255, 0.16), transparent 46%),
+ conic-gradient(from 210deg, #33474a, #4d7d76 45%, #3a4d51 85%, #33474a);
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 0 0 1px var(--line);
+ color: var(--text-2);
+}
+.cycle-mirror.is-done .cycle-mirror__disc { color: var(--ok); box-shadow: inset 0 0 0 1px rgba(255,255,255,.1), 0 0 0 1px color-mix(in srgb, var(--ok) 45%, transparent); }
+.cycle-mirror.is-active {
+ border-color: color-mix(in srgb, var(--gilt) 45%, transparent);
+ background: linear-gradient(180deg, var(--gilt-faint), transparent 70%), var(--bg-sunken);
+}
+.cycle-mirror.is-active .cycle-mirror__disc {
+ color: var(--gilt);
+ background:
+ radial-gradient(circle at 34% 28%, rgba(255, 255, 255, 0.2), transparent 46%),
+ radial-gradient(circle at 50% 45%, color-mix(in srgb, var(--gilt) 32%, #33474a), #33474a);
+ box-shadow: 0 0 0 1px var(--gilt-dim), 0 0 16px var(--gilt-dim);
+}
+.cycle-mirror__name { font-size: 12.5px; color: var(--text-2); }
+.cycle-mirror.is-active .cycle-mirror__name { color: var(--gilt); font-weight: 500; }
+.cycle-mirror__state { font-size: 10.5px; color: var(--text-3); }
+.cycle-mirror__link { width: 14px; height: 1px; background: var(--line); flex: none; }
+
+/* ---- 好奇萤火(复刻 drive 萤火点,亮度=内驱力值) ---- */
+.firefly {
+ width: 9px; height: 9px; border-radius: 50%; flex: none;
+ background: var(--accent);
+ animation: firefly-pulse 2.6s ease-in-out infinite;
+}
+@keyframes firefly-pulse {
+ 0%, 100% { box-shadow: 0 0 3px 1px color-mix(in srgb, var(--accent) 45%, transparent); }
+ 50% { box-shadow: 0 0 9px 3px color-mix(in srgb, var(--accent) 60%, transparent); }
+}
+.firefly--dim { opacity: 0.45; animation-duration: 4.2s; }
+.firefly--bright { background: var(--gilt); }
+.firefly--bright { animation-name: firefly-pulse-gilt; }
+@keyframes firefly-pulse-gilt {
+ 0%, 100% { box-shadow: 0 0 4px 1px var(--gilt-dim); }
+ 50% { box-shadow: 0 0 11px 4px var(--gilt-dim); }
+}
+@media (prefers-reduced-motion: reduce) { .firefly { animation: none; } }
+
+/* ---- 镜树:会话列表分组 + 彩点(复刻 chat-redesign 铭文往来分组) ---- */
+.conv-group__title {
+ font-size: 10.5px; color: var(--text-3); letter-spacing: 0.1em;
+ padding: 12px 8px 5px;
+}
+.conv-group__title:first-child { padding-top: 2px; }
+.conv-item__title { display: flex; align-items: center; gap: 8px; font-size: 13.5px; }
+.conv-item__dot { width: 7px; height: 7px; border-radius: 50%; flex: none; background: var(--muted); }
+.conv-item__dot--active { background: var(--accent); box-shadow: 0 0 6px color-mix(in srgb, var(--accent) 55%, transparent); }
+.conv-item__dot--sim { background: var(--warn); }
+.conv-item__dot--risk { background: var(--cinnabar); }
+
+/* 用户消息鎏金右边线(复刻 copper-v2 用户消息金线) */
+.msg--user .bubble { border-right: 2px solid var(--gilt-dim); }
+
+/* ---- 输入区控制药丸(复刻「深虑 / 博闻」) ---- */
+.pill-btn {
+ display: inline-flex; align-items: center; gap: 5px;
+ font-size: 12px; color: var(--text-3);
+ border: 1px solid var(--line); border-radius: 999px;
+ background: none; cursor: pointer; padding: 3px 11px;
+ font-family: var(--font-ui);
+}
+.pill-btn:hover { color: var(--text-1); }
+.pill-btn.is-on { color: var(--accent-strong); border-color: color-mix(in srgb, var(--accent) 45%, transparent); background: var(--accent-soft); }
+.pill-btn small { font-size: 10px; color: var(--text-3); }
+
+/* ---- 镜生 card-banner(复刻渐变横幅) ---- */
+.char-banner {
+ height: 72px; border-radius: var(--radius); margin-bottom: 16px;
+ background:
+ radial-gradient(120% 190% at 12% 120%, color-mix(in srgb, var(--accent) 16%, transparent), transparent 55%),
+ linear-gradient(115deg, color-mix(in srgb, var(--accent) 8%, transparent), transparent 42%, var(--gilt-faint) 88%);
+ border: 1px solid var(--line-soft);
+}
+
+/* ---- 时间河卷章(复刻起居注「卷」分割) ---- */
+.timeline-volume {
+ display: flex; align-items: center; gap: 10px;
+ margin: 4px 0 12px; padding-left: 14px;
+ font-family: var(--font-serif); font-size: 12px; color: var(--gilt);
+}
+.timeline-volume::before, .timeline-volume::after {
+ content: ""; flex: 1; height: 1px;
+ background: linear-gradient(90deg, transparent, var(--gilt-dim));
+}
+.timeline-volume::after { background: linear-gradient(90deg, var(--gilt-dim), transparent); }
+
+/* ---- 阴阳平衡 gauge(复刻太极 gauge,映射朱批治理) ---- */
+.taiji-gauge { display: flex; align-items: center; gap: 16px; }
+.taiji-gauge svg { width: 128px; height: 76px; flex: none; }
+.taiji-gauge__label { fill: var(--text-3); font-size: 9px; }
+.taiji-gauge__val { font-family: var(--font-serif); font-size: 16px; }
+
+/* ============================================================
+ 思考块(thinking block · 契约候选):模型思考摘要可折叠 + 强度呈现
+ 边界:展示的是模型输出的可读摘要——会话内可见、可丢弃重建;
+ 不写入长期记录、不进入诊断导出(隐藏推理不采集,宪章不变)。
+ ============================================================ */
+.think-block {
+ border: 1px dashed var(--line);
+ border-radius: var(--radius);
+ background: transparent;
+ overflow: hidden;
+}
+.think-block summary {
+ display: flex;
+ align-items: center;
+ gap: 9px;
+ padding: 9px 14px;
+ cursor: pointer;
+ list-style: none;
+ font-size: 12.5px;
+ color: var(--text-2);
+ user-select: none;
+}
+.think-block summary::-webkit-details-marker { display: none; }
+.think-block summary:hover { color: var(--text-1); background: var(--bg-sunken); }
+.think-block summary svg { width: 14px; height: 14px; flex: none; color: var(--text-3); }
+.think-block summary .chev { margin-left: auto; transition: transform 0.18s ease; }
+.think-block[open] summary .chev { transform: rotate(90deg); }
+.think-block summary .think-meta { font-size: 11px; color: var(--text-3); }
+.think-block summary .think-effort {
+ font-size: 10.5px;
+ border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
+ color: var(--accent);
+ border-radius: 4px;
+ padding: 0 6px;
+}
+.think-body {
+ padding: 4px 16px 12px 39px;
+ font-size: 12.5px;
+ color: var(--text-3);
+ line-height: 1.75;
+ border-left: 2px solid var(--line-soft);
+ margin: 0 14px 0 14px;
+}
+.think-body p { margin-bottom: 0.6em; }
+.think-body p:last-child { margin-bottom: 0; }
+.think-note { padding: 8px 16px 12px 39px; font-size: 11px; color: var(--text-3); opacity: 0.85; }
+
+/* 流式思考中:呼吸点 + 实时摘要(底部渐隐,完成后折叠) */
+.think-block.is-thinking { border-color: color-mix(in srgb, var(--accent) 40%, transparent); }
+.think-block.is-thinking summary { color: var(--accent-strong); }
+.think-dot {
+ width: 8px; height: 8px; border-radius: 50%; flex: none;
+ background: var(--accent);
+ animation: think-breathe 1.6s ease-in-out infinite;
+}
+@keyframes think-breathe {
+ 0%, 100% { box-shadow: 0 0 2px 1px color-mix(in srgb, var(--accent) 40%, transparent); opacity: .6; }
+ 50% { box-shadow: 0 0 8px 3px color-mix(in srgb, var(--accent) 60%, transparent); opacity: 1; }
+}
+.think-live {
+ max-height: 108px;
+ overflow: hidden;
+ -webkit-mask-image: linear-gradient(180deg, transparent, #000 22%);
+ mask-image: linear-gradient(180deg, transparent, #000 22%);
+}
+.think-cursor {
+ display: inline-block;
+ width: 7px; height: 13px;
+ background: var(--accent);
+ vertical-align: -2px;
+ margin-left: 2px;
+ border-radius: 1px;
+ animation: blink 1.05s step-end infinite;
+}
+@media (prefers-reduced-motion: reduce) {
+ .think-dot, .think-cursor { animation: none; }
+}
+
+/* 深虑伴随控件:点击深虑 = 开启深度思考;开启后右侧出现力度选项(独立控件,只调档);
+ 再次点击深虑 = 关闭,力度选项随之消失 */
+.effort-seg {
+ display: inline-flex;
+ background: var(--bg-sunken);
+ border: 1px solid var(--line-soft);
+ border-radius: 999px;
+ padding: 2px;
+ gap: 2px;
+ animation: effort-in 0.2s ease;
+}
+.effort-seg[hidden] { display: none; }
+@keyframes effort-in {
+ from { opacity: 0; transform: translateX(-4px); }
+ to { opacity: 1; transform: none; }
+}
+.effort-seg button {
+ border: none;
+ background: none;
+ cursor: pointer;
+ font-family: var(--font-ui);
+ font-size: 11.5px;
+ color: var(--text-3);
+ padding: 2.5px 11px;
+ border-radius: 999px;
+}
+.effort-seg button:hover { color: var(--text-1); }
+.effort-seg button[aria-checked="true"] { background: var(--accent-soft); color: var(--accent-strong); }
+@media (prefers-reduced-motion: reduce) { .effort-seg { animation: none; } }
diff --git a/design/assets/app.js b/design/assets/app.js
new file mode 100644
index 0000000..4f7b7b7
--- /dev/null
+++ b/design/assets/app.js
@@ -0,0 +1,222 @@
+/* 魂镜原型 · 共享交互脚本
+ 主题切换 · logo 彩蛋(镜启)· 主侧栏折叠 · 对话页面板折叠 · 演示态切换 */
+(function () {
+ "use strict";
+
+ /* ---------- 主题切换(localStorage 持久化,仅原型演示用) ---------- */
+ var saved = null;
+ try { saved = localStorage.getItem("hunjing-prototype-theme"); } catch (e) { /* 忽略 */ }
+ if (saved === "light" || saved === "dark") {
+ document.documentElement.setAttribute("data-theme", saved);
+ }
+ window.hjSetTheme = function (theme) {
+ document.documentElement.setAttribute("data-theme", theme);
+ try { localStorage.setItem("hunjing-prototype-theme", theme); } catch (e) { /* 忽略 */ }
+ document.querySelectorAll("[data-theme-toggle]").forEach(function (btn) {
+ btn.setAttribute("aria-label", theme === "dark" ? "切换到月白主题" : "切换到墨色主题");
+ });
+ };
+
+ document.addEventListener("click", function (ev) {
+ var btn = ev.target.closest("[data-theme-toggle]");
+ if (!btn) return;
+ var cur = document.documentElement.getAttribute("data-theme") === "light" ? "light" : "dark";
+ window.hjSetTheme(cur === "dark" ? "light" : "dark");
+ });
+
+ /* ---------- logo:单击回目录;350ms 内连点 5 次触发「镜启」彩蛋 ----------
+ 承接 main 分支旧版设计:镜启不跳转,随机呈上一句镜铭。 */
+ var EGG_VERSES = [
+ "镜中有镜,心外无心。",
+ "汝来看此镜时,此镜亦照汝。",
+ "一念起,万镜生。",
+ "镜非鉴物,乃鉴心也。",
+ "幻由心生,镜随心转。",
+ "铜镜本无字,照尽往来人。",
+ "且向镜中看,那个是谁?",
+ ];
+ var logoClickSeq = 0;
+ var lastLogoTime = 0;
+ var jumpTimer = null;
+
+ function showEggToast(msg) {
+ var toast = document.createElement("div");
+ toast.className = "egg-toast";
+ toast.textContent = msg;
+ document.body.appendChild(toast);
+ requestAnimationFrame(function () { toast.classList.add("is-on"); });
+ setTimeout(function () {
+ toast.classList.remove("is-on");
+ setTimeout(function () { toast.remove(); }, 450);
+ }, 2400);
+ }
+
+ function onBrandClick(brandEl) {
+ clearTimeout(jumpTimer);
+ var now = Date.now();
+ var fast = now - lastLogoTime < 380; /* 380ms 内视为连点 */
+ lastLogoTime = now;
+ if (brandEl) {
+ brandEl.classList.add("tick");
+ setTimeout(function () { brandEl.classList.remove("tick"); }, 300);
+ }
+ if (fast) {
+ logoClickSeq += 1;
+ if (logoClickSeq >= 4) {
+ /* 首击 + 4 次连点 = 共 5 次点击:镜启彩蛋,不跳转 */
+ logoClickSeq = 0;
+ var v = EGG_VERSES[Math.floor(Math.random() * EGG_VERSES.length)];
+ if (brandEl) {
+ brandEl.classList.add("egg-pulse");
+ setTimeout(function () { brandEl.classList.remove("egg-pulse"); }, 1400);
+ }
+ showEggToast("镜启 · " + v);
+ }
+ return; /* 连点不跳转 */
+ }
+ logoClickSeq = 0;
+ /* 单击延迟跳转,为连点留出窗口 */
+ jumpTimer = setTimeout(function () { location.href = "index.html"; }, 380);
+ }
+
+ /* ---------- 主侧栏折叠(localStorage 持久化) ---------- */
+ function applySidebarFold(collapsed) {
+ document.querySelectorAll(".app").forEach(function (app) {
+ app.classList.toggle("app--fold", collapsed);
+ });
+ }
+ function sidebarFolded() {
+ var app = document.querySelector(".app");
+ return !!(app && app.classList.contains("app--fold"));
+ }
+
+ /* ---------- 对话页左右面板折叠 ---------- */
+ function panelFold(which) {
+ var layout = document.querySelector(".conv-layout");
+ if (!layout) return;
+ layout.classList.toggle("no-" + which);
+ }
+
+ /* ---------- 事件委托 ---------- */
+ document.addEventListener("click", function (ev) {
+ var t = ev.target;
+ if (!t || !t.closest) return;
+
+ var foldBtn = t.closest("[data-sidebar-fold]");
+ if (foldBtn) {
+ ev.preventDefault();
+ var collapsed = !sidebarFolded();
+ applySidebarFold(collapsed);
+ try { localStorage.setItem("hunjing-prototype-sidebar", collapsed ? "collapsed" : "expanded"); } catch (e) { /* 忽略 */ }
+ return;
+ }
+
+ var panelBtn = t.closest("[data-panel-fold]");
+ if (panelBtn) {
+ ev.preventDefault();
+ panelFold(panelBtn.getAttribute("data-panel-fold"));
+ return;
+ }
+
+ var brand = t.closest(".brand");
+ if (brand) {
+ ev.preventDefault();
+ onBrandClick(brand);
+ return;
+ }
+ });
+
+ /* 恢复折叠状态 */
+ try {
+ var foldSaved = localStorage.getItem("hunjing-prototype-sidebar");
+ if (foldSaved === "collapsed") applySidebarFold(true);
+ } catch (e) { /* 忽略 */ }
+
+ /* ---------- 深虑:点击 = 开启深度思考(右侧伴随力度选项);再次点击 = 关闭并隐藏选项 ---------- */
+ function setEffortHint(on, val) {
+ var hint = document.querySelector("[data-effort-hint]");
+ if (hint) hint.textContent = on ? "深虑 · " + val : "深虑 · 关";
+ }
+
+ document.addEventListener("click", function (ev) {
+ var t = ev.target;
+ if (!t || !t.closest) return;
+
+ /* 力度选项:独立控件,只调档,与深虑开关动作无关(关闭态隐藏时不响应) */
+ var opt = t.closest("[data-effort-seg] button[data-effort]");
+ if (opt) {
+ ev.preventDefault();
+ ev.stopPropagation();
+ if (opt.closest("[data-effort-seg]").hidden) return;
+ document.querySelectorAll("[data-effort-seg] button").forEach(function (b) {
+ b.setAttribute("aria-checked", String(b === opt));
+ });
+ setEffortHint(true, opt.getAttribute("data-effort"));
+ return;
+ }
+
+ /* 深虑按钮:开 ⇄ 关 */
+ var pill = t.closest('[data-pill="effort"]');
+ if (pill) {
+ ev.preventDefault();
+ var on = !pill.classList.contains("is-on");
+ pill.classList.toggle("is-on", on);
+ pill.setAttribute("aria-pressed", String(on));
+ pill.title = on
+ ? "深虑 · 深度思考已开启,再次点击关闭"
+ : "深虑 · 已关闭:本条消息不思考,直出答复(推理型模型不可关)";
+ var seg = document.querySelector("[data-effort-seg]");
+ if (seg) seg.hidden = !on;
+ var val = "关";
+ if (on) {
+ var checked = document.querySelector('[data-effort-seg] button[aria-checked="true"]');
+ val = checked ? checked.getAttribute("data-effort") : "中";
+ }
+ setEffortHint(on, val);
+ return;
+ }
+ });
+
+ /* ---------- 博闻药丸(关→开) ---------- */
+ document.addEventListener("click", function (ev) {
+ var pill = ev.target.closest('[data-pill="search"]');
+ if (!pill) return;
+ ev.preventDefault();
+ var small = pill.querySelector("small");
+ if (!small) return;
+ var on = small.textContent.trim() === "开";
+ small.textContent = on ? "关" : "开";
+ pill.classList.toggle("is-on", !on);
+ pill.setAttribute("aria-pressed", String(!on));
+ });
+
+ /* ---------- 候答切换(静态演示) ---------- */
+ document.querySelectorAll("[data-cand-group]").forEach(function (group) {
+ group.querySelectorAll(".cand-tag").forEach(function (tag) {
+ tag.addEventListener("click", function () {
+ group.querySelectorAll(".cand-tag").forEach(function (tt) { tt.classList.remove("is-on"); });
+ tag.classList.add("is-on");
+ });
+ });
+ });
+
+ /* ---------- 界域分段控件:切换提示条文案 ---------- */
+ document.querySelectorAll("[data-realm]").forEach(function (seg) {
+ seg.querySelectorAll(".seg__item").forEach(function (item) {
+ item.addEventListener("click", function () {
+ seg.querySelectorAll(".seg__item").forEach(function (tt) { tt.classList.remove("is-on"); });
+ item.classList.add("is-on");
+ var banner = document.querySelector("[data-realm-banner]");
+ if (!banner) return;
+ var realm = item.getAttribute("data-realm");
+ if (realm === "sim") {
+ banner.className = "realm-banner realm-banner--sim";
+ } else {
+ banner.className = "realm-banner";
+ }
+ var html = item.getAttribute("data-banner-html");
+ if (html) banner.textContent = html;
+ });
+ });
+ });
+})();
diff --git a/design/avatar.html b/design/avatar.html
new file mode 100644
index 0000000..b06fdc9
--- /dev/null
+++ b/design/avatar.html
@@ -0,0 +1,145 @@
+
+
+
+
+
+形仪 · 形象与资产 — 魂镜 Hunjing
+
+
+
+
+
+
+
+
+
+ 形仪 / 青梧 · 形象与资产
+
+ 静态原型 · 待审查
+
+
+
+
+
+
梧
+
+
青梧的形仪 形态不是角色本体
+
主仪 · 守砚人立像 v3 · 真形锚已锁定稳定辨识特征
+
+
+ 导入资产
+ 打开 3D / Live2D 编辑
+
+
+
+
+
+ 真形锚稳定外观参考
+ 编辑
+
+
+
· 稳定辨识特征:青衫束发、眉间一点朱痣、左手常年持砚
+
· 年龄表现约束:成年形象,不做年龄回退
+
· 许可与禁忌:形象由你原创 · 禁止他人商用(许可记录见来源谱系)
+
· 可变范围:服饰、场景、光照可变;面部锚点特征触及须走易相评估
+
+
+
+
+
+ 资产库图像 / 声音 / Live2D / 3D
+
+
+ 全部 · 6
+ 图像 · 2
+ 声音 · 1
+ Live2D · 1
+ 3D · 1
+ 归档 · 1
+
+
+
+
立 图像 主仪
+
守砚人立像 v3 · 已定相 · 2048×2560
+
查看 导出
+
+
+
+
冬 图像
+
冬青 · 化仪 v1 · 可切换形态
+
查看 设为化仪
+
+
+
+
声 声音 声契 · 原创
+
青梧原声 原创设计 · 授权完整 · 转写并存
+
试听声音设计
+
+
+
+
灵 Live2D
+
守砚人 · 灵动版 Cubism 兼容导入 · 校验通过 · 参数映射 42 项
+
编辑 运行预览
+
+
+
+
三 3D · VRM
+
守砚人 VRM 导入验证与标准化通过 · 骨骼 54 · 表情 12
+
编辑 导出
+
+
+
+
旧 图像 · 归档
+
旧版立像 v2 易相历史保留 · 可回看不可覆盖
+
回看
+
+
+
+
+
+
+
+
+
+
2024-03-05
+
主仪 v1 · 初版立像
+
来源:手动上传
+
+
+
2025-11-20
+
易相 v2 · 换季服饰(未触及真形锚)
+
轻流程 · 直接生效
+
+
+
2026-08-30
+
易相 v3 · 面部细节重绘(触及真形锚)
+
已做身份连续性评估 · 谱系留痕
+
+
+ 情相 · 情态的受控表现:可访问性友好,不作为情绪施压载体;叙仪 · 境界或幻境中的叙事形态,不改变主仪与真形锚。
+
+
+
+
+
+
+
+
diff --git a/design/characters.html b/design/characters.html
new file mode 100644
index 0000000..dbc3d13
--- /dev/null
+++ b/design/characters.html
@@ -0,0 +1,233 @@
+
+
+
+
+
+镜生 · 角色 — 魂镜 Hunjing
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 镜生 / 青梧
+
+ 静态原型 · 待审查
+
+
+
+
+
+
+
+
+
+
+ 镜生4 位角色
+ 创建
+
+
+
+
+
+
+
+
+
+
+
+
+
+
梧
+
+
青梧 临照 · 前台运行 running
+
「砚为田,字为稼。」守砚人 · 你与我共读的第三年
+
+ 道体 v3
+ 镜域 · 个人域
+ 来源 · 手动创建
+
+
+
+
+
+
+
+ 道体稳定身份基线
+ 普通对话与记忆不能静默覆盖道体
+
+
+ 青梧是江南书斋的守砚人:语调平缓,好引张岱与《陶庵梦忆》;记账一丝不苟,惜字如金。
+ 基线由 v1 逐步重映而来 ——人格、语言风格与知识边界在版本中留痕,任何重大改动都会先成候选、经你确认。
+
+
+
+
+
+ 缘契你与青梧的关系
+ 共读者 · v4
+
+ 「隔周四晚共读,青梧负责选篇与笔记;你不喜长篇说教。」——最近一次铭契于 8 月 28 日确认,旧关系状态保留版本可查。
+
+
+
+
+ 内驱力动机与倾向维度 · 可审阅编辑
+ 编辑
+
+
+
+
+ 内驱力是声明式设定数据,不是不可控本能;变化与大周天评估相关,每次调整版本化留痕。
+
+
+
+
+ 大周天自我评估与演化循环
+ 已成 · 第 7 轮 completed
+
+
+
回 回顾 已成 · 09-14
+
+
照 对照 已成 · 09-14
+
+
议 提议 已成 · 09-15
+
+
呈 提交 2 项待你确认
+
+ 本轮由 42 次小周天完成信号触发 · 预算 5k token(实用 3.1k)· 产出:人格候选 1、世界认知候选 1。演化经你确认才生效;循环可整体关闭。
+
+ 审阅候选
+ 查看本轮输入与费用
+
+
+
+
+
+ 身份谱系来源与重大演变
+ 完整谱系 · 见下方时间线
+
+
+
+
2024-03-02
+
启镜 · 正式创建
+
手动创建 · 道体 v1 · 初照自陈已归真
+
+
+
2025-06-18
+
重映 · 道体 v2
+
语言风格修订 · 旧版本保留
+
+
+
2026-01-11
+
分灵 · 雪袖自此谱系分出
+
新角色新 ID · 谱系可验证 · 不冒充原角色
+
+
+
2026-08-28
+
重映 · 道体 v3(当前)
+
知识边界修订 · 经候选确认
+
+
+
+
+
+
+ 生命周期影响角色状态的操作 · 均可逆或有冷静期,与数据删除分离
+
+
+ 静修 · 停止运行
+ 归藏 · 隐藏可恢复
+ 复照 · 从旧版本重开
+ 铸魂 · 重大重塑(强确认)
+ 死亡 · 永久闭合(冷静期)
+
+ 铸魂与死亡均走强确认与谱系留痕;死亡不等于删除数据,只读档案保留。删除角色数据是独立操作,入口在司命台。
+
+
+
+
+
+
+
+
+
diff --git a/design/collaboration.html b/design/collaboration.html
new file mode 100644
index 0000000..d4143aa
--- /dev/null
+++ b/design/collaboration.html
@@ -0,0 +1,114 @@
+
+
+
+
+
+同契台 · 协作空间 — 魂镜 Hunjing
+
+
+
+
+
+
+
+
+
+ 同契台 / 镜斋工作室
+
+ 静态原型 · 待审查
+
+
+
+
+
+
斋
+
+
镜斋工作室 共修 · 2 人在线 editing
+
成员 4 · 邀请制 · 编辑 / 发布 / 财务权限分别授予
+
+
+ 邀请成员
+ 进入共修
+
+
+
+
+
+
+
+
+ 选篇修订v3 草稿 提交合卷
+
+
09-14 21:30
v3 草稿 · 云舟
按青梧建议调整三、四讲顺序
+
09-14 22:10
审稿意见 ×2 · 阿沉
「定海水操放第五讲更顺」「附页地图需更新版本」
+
09-10 19:00
v2 合卷 · 已并入 main
冲突 1 处已手工处理
+
+
+ 批准本稿
+ 请求修改
+ 批准后需两名编辑合卷方可并入 main
+
+
+
+
+ 成员权限编辑 / 发布 / 上架 / 财务分离
+
+ 成员 编辑 发布 市场上架 财务
+
+ 云舟(主理) ✓ ✓ ✓ ✓
+ 阿沉 ✓ ✓ — —
+ 临江仙 ✓ — — —
+ 山客(只读) — — — —
+
+
+ 成员移除后立即失去编辑权;已合并版本的署名与来源保留。
+
+
+
+ 活动记录
+
+
+
+
09-10 19:00
v2 合卷并入 main(两名编辑批准)
+
+
+
+
+
+
+
+
+
+
+
diff --git a/design/community.html b/design/community.html
new file mode 100644
index 0000000..a537a3e
--- /dev/null
+++ b/design/community.html
@@ -0,0 +1,141 @@
+
+
+
+
+
+雅集 · 社区广场 — 魂镜 Hunjing
+
+
+
+
+
+
+
+
+
+ 雅集 · 社区广场
+
+ 静态原型 · 待审查
+
+
+
+
+
+
+ 动态
+ 角色谱
+ 作品
+ 世界设定
+ 关注
+
+
+
+
+
+
发布
+
+
+
+
+
+
+
+
发布角色「照夜 · 掌灯人」v2 · 市契 CC BY-NC 4.0
+
渡口掌灯的角色,话少但记得每个过客的名字。基于我自己的镜生谱系重映而来,Fork 请保留署名。
+
+ 许可 · CC BY-NC 分灵谱系 · 可验证 分级 · 全年龄
+
+
+
+
+
+
+
+
+
共读《陶庵梦忆》第一讲笔记:湖心亭那一百六十字,读三遍的方法真的有效。附角色整理的选篇结构图。
+
莫说相公痴,更有痴似相公者。引文锚 · 《湖心亭看雪》
+
+
+
+
+
+
+
+
世界「雾中茶寮」第三章境界 · 世界正史 v5
+
雨季的茶寮只在雾散时开门。本章收录 6 个新场景与 2 位过客的世界认知候选来源说明。
+
+
+
+
+
+
+
+ 角色谱角色社区精选 全部 · 角色谱整页在 React 阶段实现
+ 夜 照夜 · 掌灯人
沉舟 · Fork 128 · 全年龄
+
+
+
+
+
+ 话题
+
+ #陶庵梦忆 #共读笔记 #Live2D #雾中茶寮 #守砚人
+
+
+
+
+ 推荐解释为什么看到这些
+ 当前动态基于你最近的共读活动与关注关系排序。推荐不扩大任何权限,也不使用你的私密角色数据。
+ 重置推荐特征
+
+
+
+
+
+
+
+
+
+
diff --git a/design/conversation.html b/design/conversation.html
new file mode 100644
index 0000000..c7bf04b
--- /dev/null
+++ b/design/conversation.html
@@ -0,0 +1,552 @@
+
+
+
+
+
+镜树 · 分支对话 — 魂镜 Hunjing
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 镜树 / 《陶庵梦忆》共读选篇
+
+
+ 真界 现实
+ 境界 世界正史
+ 幻境 试演
+
+ 真界
+
+ 静态原型 · 待审查
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 真界 · 当前是现实与正式共同经历的界域,内容按记忆策略进入长期记忆。
+
+
+
+
+
问节 m_8a3f 2026-09-15 19:42
+
帮我把读书会这学期的《陶庵梦忆》共读选篇定下来:选六篇,兼顾篇幅和趣味,大家隔周四晚讨论。
+
+
+
+
+
+ 梧
+ 青梧
+ 道体 v3 已成 completed
+
+
+
+
+
+ 思考 · 已收敛为摘要
+ 深虑 · 中
+ 思考 1.2k token · 用时 9s
+
+
+
+
用户要为读书会从《陶庵梦忆》定六篇,兼顾篇幅与趣味,隔周四讨论。需要考虑:成员含两位新人(首篇宜短宜奇)、整体要有递进结构而非并列、末篇宜收束回张岱本人。以篇目字数与趣味点对照评估,湖心亭看雪最短且白描极致,适合开卷;自为墓志铭最长且是回望性文本,适合收官;中间四篇按「雪—夜—池—海」的意象递进排列。
+
+ 思考摘要仅本会话内可见,可丢弃重建;不写入长期记录,不进入诊断导出。
+
+
+
+
选好了。六篇按「起—承—转—合」 推进:从最短的《湖心亭看雪》起,让新人先尝到味道;雪、夜、池、海层层铺开,末篇收在自为墓志铭 ,正好回到张岱自己。
+
+
+
+
选篇清单
+
+
+ 次序 篇目 约字数 趣味点
+
+ 一 湖心亭看雪 160 白描极致 · 入门首选
+ 二 卷中雪 700 炉火与夜读
+ 三 金山夜戏 900 寺院里点灯唱戏
+ 四 庞公池 600 枕舟而眠
+ 五 定海水操 1100 海上观兵
+ 六 自为墓志铭 1300 回望一生 · 收束全卷
+
+
+
+
+
+
mermaid flowchart · 完成后 strict mode 渲染
+
+
+
+
+ 起 · 湖心亭看雪
+ 第一周 · 白描
+
+
+ 承 · 雪 / 夜
+ 卷中雪 · 金山夜戏
+
+
+ 转 · 池 / 海
+ 庞公池 · 定海水操
+
+
+ 合 · 自为墓志铭
+ 末周 · 回望
+
+
+
+
图 1 · 六讲推进结构(SVG 已净化 · 可缩放 / 下载 / 查看源码)
+
+
莫说相公痴,更有痴似相公者。
+ 引文锚 · 《陶庵梦忆 · 湖心亭看雪》 · anchor_c41
+
+
+
+ 候答(2)
+ 引用
+ 续思
+ 移栽
+ 更多
+
+
+
+
+
+
问节 m_91d0 19:47
+
好。把六次讨论排进我的日历:从下周四 19:30 开始,隔周一次,每次 90 分钟,标题统一叫「共读 · 第 N 讲」。
+
+
+
+
+
+ 梧
+ 青梧
+ 道体 v3 已成 completed
+
+
+
+
+
+
+
批
+
+
请命 · 已朱批 本次确认 · 已由你允许
+
2026-09-15 19:48 允许 · 留痕于司命台
+
+
允许 approved
+
+
+
动作 在日历创建 6 个「共读 · 第 N 讲」事件
+
影响 写入你的日历;不读取其他日程
+
数据范围 日历写入 · 仅此 6 条 calendar:write:once
+
费用 无(本地工具)
+
有效期 本次执行,不形成持续授权
+
+
+
+
+
+
已写入。首场 9 月 24 日(周四)19:30,《湖心亭看雪》——正好赶上秋分后的第一场凉。预习提示我会在每讲前三天放进讨论区。
+
+
+
+
+ 引用
+ 更多
+
+
+
+
+
+
+
+
+ 息壤候选 记忆候选 · 归真后才会影响未来上下文
+ 偏好 preference
+
+
+
「读书会隔周四 19:30 举行,每次 90 分钟。」
+
来源 · 本对话 m_91d0 · 置信度 0.92 · 敏感度低
+
+ 归真入忆
+ 本次忽略
+ 查看来源
+
+
+
+
+
+
+
+
+
问节 m_a207 20:02
+
第一讲的开场白怎么讲?给两个版本我挑。
+
+
+
+
+
+ 梧
+ 青梧
+ 道体 v3 已成 completed
+
+
+
+ 候答 A · 已归真(主脉)
+ 候答 B · 更克制
+
+
同一问节的两个回答候选 · 切换不覆盖彼此
+
+
+
+
+
**版本 A(从雪夜进入)**——「崇祯五年十二月,余住西湖。大雪三日,湖中人鸟声俱绝。」今晚我们不急着分析,先把这一百六十字读三遍:第一遍看雪,第二遍看舟,第三遍看那个「痴」字。
+
音频版我念了一遍,配了半分钟的环境音。
+
+
+
+
+
+
+
+
+
+ 开场白 · 语音版
+ 0:42 · 声契 · 青梧原声
+
+
+
转写:「崇祯五年十二月,余住西湖……」(全文 214 字 · 已加字幕)
+
+
+
+ 重生成
+ 引用
+ 更多
+
+
+
残答 · 已封存 interrupted
+
+
推演枝 D · 生成中断于 68%,内容封存不再修改。
+
残答不作为完成答复展示 · 引用合流或
+
+
续生新节点
+
+
+
+
+
+
问节 m_b6f1 20:10
+
再把每讲的预习提纲写成六份文档,存进我的笔记库「读书会」目录。
+
+
+
+
+
+ 梧
+ 青梧
+ 道体 v3 请命中 · 等待确认 waiting_approval
+
+
+
+
+
+
+
命
+
+
请命 请求你的确认 · 未确认不执行 RISK L2
+
拒绝或超时均不执行 · 无默认允许 · L2:写操作,范围已限死
+
+
+
+
动作 在笔记库「读书会」目录创建 6 份 Markdown 预习提纲
+
影响 新增 6 个文件;不修改、不删除现有笔记
+
数据范围 笔记库写入 · 仅该目录 notes:write:/读书会/
+
费用 6 次文件写入 · 无额外模型费用(提纲已在本次生成)
+
有效期 本次执行 · 勾选「行契」可限 30 天内同类免请命
+
+
+ 允许(朱批)
+ 拒绝
+ 朱批 · 你的明确决定,允许或拒绝都留痕。
+
+
+
+
+
+
+
+
问节 m_c944 20:12
+
在等的时候,先给我一版 30 分钟的精简流程,怕第一次拖堂。
+
+
+
+
+ 梧
+ 青梧
+ 道体 v3 运转 · 生成中 streaming
+
+
+
+
+
+ 正在思考
+ 深虑 · 中
+ 思考 0.4k token · 深虑档位可在输入区调整
+
+
+
+
用户担心首次共读拖堂,要 30 分钟精简版。需要压缩朗读与讨论比例:三分钟齐读保留仪式感,两个问题足够聚焦——「雪大不大」对应白描力度,「痴不痴」对应人物自我认知,比四个问题更利于新人开口……
+
末段用舟子那句话收尾,与开场「痴」字形成回环,30 分钟结构即
+
+
+
+
+ 临时芽体 流式生成中 · 完成后才固化
+ block 2/4 · delta 118
+
+
+
+
30 分钟精简版:前三分钟齐读原文,随后只留两个问题——「雪大不大」和「痴不痴」,最后五分钟交给「舟子那句话」收尾。
+
+
**时间分配**(未闭合的 Markdown 结构保持源码态,完成后解析)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 深虑
+
+ 低
+ 中
+ 高
+
+ 博闻关
+
+ 呈镜
+
+
+
+ 界域 真界 · 现实
+ 路径 主脉
+ 模型 会话临时 · 覆盖自用户偏好
+ 思考 深虑 · 中 (点深虑开关,右侧调力度)
+ 上下文 2.1k / 8k token
+
+
+
+
+
+
+
+
+
+
+
+
+ 镜枝分支结构
+ 树视图
+
+
+
+
+ 问节 · 选篇 主脉 ✓
+
+ 候答 A · 六篇 + 结构图 归真
+ 推演枝 · 按「梦」分组(弃)
+
+
+
+ 问节 · 排日历 主脉 ✓
+
+
+
+ 问节 · 开场白
+
+ 候答 A · 雪夜进入 归真
+ 候答 B · 更克制
+ 推演枝 D 残答
+
+
+
+ 问节 · 预习提纲 请命中
+
+
+
+
+
+
+
+
+
+
+
+
明忆 读书会隔周四 19:30 · 90 分钟 mem_v12
+
实录 3 月购入《陶庵梦忆》校注本 fact_07d
+
世谱 读书会成员 6 人 · 两位新人 world_v4
+
已省略 2 条:1 条过期(去年场地)、1 条已被取代(旧选篇)
+
+
完整 ModelCall 与工具轨迹需双重授权诊断(15 分钟 · 单次)。
+
+
+
+
+
+
+
平台硬边界 ✓
+
平台目录 ✓
+
用户偏好 ✓
+
会话临时选择 生效中
+
角色覆写 —
+
上层不可被下层覆盖;首个可见输出后不切换 Provider。
+
+
+
+
+
+
+
+
+
diff --git a/design/devices.html b/design/devices.html
new file mode 100644
index 0000000..c35eee3
--- /dev/null
+++ b/design/devices.html
@@ -0,0 +1,133 @@
+
+
+
+
+
+器谱 · 设备与感知 — 魂镜 Hunjing
+
+
+
+
+
+
+
+
+
+ 器谱 · 设备与感知
+
+ 静态原型 · 待审查
+
+
+
+
+
+
+ 设备能力同时受系统权限与授契约束;采集有持续指示,可即时暂停或撤销。离线缓存加密,不创建独立角色副本。
+
+
+
+ 形器登记设备
+
+
+
+
MacBook Pro · Tauri 桌面
macOS · 当前设备
+
在线
+
+
+
+
iPhone · 移动端
iOS · 实时语音常用设备
+
在线
+
+
+
+
Web 浏览器 · Safari
昨天活跃 · 仅文件读写授权
+
离线
+
+
+
+
+
+ 授契设备能力授权 全部撤销
+
+
+
闻声 · 麦克风
+
青梧 · 实时语音
iPhone · 仅通话期间 · 采集指示常显
+
撤销
+
+
+
文件读写
+
青梧 · 书斋归档
MacBook · 仅 ~/BookRoom/ 目录 · 长期(可随时撤销)
+
撤销
+
+
+
通知
+
叩心提醒通道
iPhone · 静默时段 22:00–08:00 · 频率 ≤ 1 条/日
+
撤销
+
+
+
+
问路 · 位置
+
未授权(上次通感已结束)
通感默认 10 分钟 · 精度 1km · 结束即失效
+
—
+
+
+
+
+
+
+ 通感进行中限时设备感知 剩 8 分钟
+ 青梧 · 问路(位置 · 精度 1km)· 用于「找一家安静的茶馆」。采集指示常显,位置不写入长期记忆,会话结束即丢弃。
+
+
+ 暂停
+ 立即结束并撤销
+
+
+
+
+
+
+
命
+
+
请命 · 观屏 屏幕读取授权
+
来自镜树「书房整理」· 限此一次
+
+
+
+
允许青梧在未来 10 分钟读取当前屏幕?
+
用途 识别书桌照片中的书目并归档
+
数据 仅屏幕内容 · 不截屏存储 · 不写入长期记忆
+
+
+ 允许(本次,10 分钟)
+ 拒绝
+
+
+
+
+
+
+
+
+
+
+
diff --git a/design/governance.html b/design/governance.html
new file mode 100644
index 0000000..bd6bc31
--- /dev/null
+++ b/design/governance.html
@@ -0,0 +1,142 @@
+
+
+
+
+
+司命台 · 安全与治理 — 魂镜 Hunjing
+
+
+
+
+
+
+
+
+
+ 司命台 · 安全 · 权限 · 审计 · 治理
+
+ 静态原型 · 待审查
+
+
+
+
+
+
+ 治理不支配角色或用户数据;隐藏推理、密钥与凭据永不进入诊断与审计展示。错误与安全事件优先直白表述。
+
+
+
+ 阴阳平衡朱批治理的三态 阳亢 · 放行偏多 / 动态平衡 / 阴盛 · 驳回偏多
+
+
+
+
+
+
+
+ 阴 · 驳回
+ 阳 · 放行
+
+
+
动态平衡
+
近 30 日朱批通过率 78% · 驳回 22%指针偏右但在平衡带内;阳亢时提示收紧行契,阴盛时提示检查策略误伤
+
+
+
+
+
+ 内容 2
+ 插件 1
+ 市场 1
+ 申诉 1
+
+
+
+ 司议治理案件
+
+
+ 待审 · 等待审核 pending_review
+ 09-15 11:20 · 鸣冤 ×3
+
+
雅集帖子「未署名的守砚人立像二创」被举报:疑似未保留来源署名(CC BY-NC 要求)。
+
+ 查看原文
+ 限观 · 限制展示
+ 移除
+ 驳回举报
+
+
+
+
+ 待审 pending_review
+ 09-14 09:02 · 自动扫描
+
+
市场商品「曲谱排版插件 v1.4」扫描发现:Manifest 声明与实际网络请求域不一致。
+
+ 查看扫描报告
+ 下架
+ 通知创作者
+
+
+
+
+
+
+ 诊断授权双重授权
+ 读取 Run 详细诊断(ModelCall、工具参数、ContextSnapshot)需要在普通认证之外叠加短期诊断令牌:绑定单个 Run、单次使用、默认 15 分钟。
+
+
申请诊断令牌
目的 user_inspection · 绑定 run_9f31c2(天工阁 · 书单任务)
+
签发
+
+
+
hjd_8f…(已过期)
09-15 12:04 签发 · 12:19 过期 · 已用 1 次 · 范围:青梧 · 选篇答复
+
已过期
+
+ 隐藏推理、密钥、Authorization 与签名 URL 永不返回;管理员与诊断均不可读私箧。
+
+
+
+
+ 活跃会话 2
MacBook(当前)· iPhone
管理
+ Service Account key 1
scope:只读统计 · 90 天轮换
轮换
+
+
+
+
+
+ 审计AuditEvent · 权威事务同批提交
+
+
+ 时间 主体 动作 对象 结果
+
+ 09-15 19:48 云舟 朱批 · 允许 calendar.write · run_7ab1 ✓
+ 09-15 19:52 云舟 创建天工 run_9f31c2 ✓
+ 09-15 12:04 云舟 签发诊断令牌 hjd_8f… ✓
+ 09-14 22:31 未知请求 跨镜域读取 memory · 雪袖 ✗ 拒绝(失败关闭)
+ 09-12 10:05 云舟 BYOK 轮换 byok_7c02… ✓
+
+
+
+
+
+
+
+
+
+
+
diff --git a/design/home.html b/design/home.html
new file mode 100644
index 0000000..e7ff485
--- /dev/null
+++ b/design/home.html
@@ -0,0 +1,277 @@
+
+
+
+
+
+镜庭 · 个人首页 — 魂镜 Hunjing
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 镜庭 · 个人首页
+
+ 静态原型 · 待审查
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 巳时
+ 白露 · 第 10 日
+
+
+
+
上午好,云舟
+
周三 · 2026-09-16 · 与青梧相伴第 918 日 · 镜庭只聚合状态,各域才是数据事实源
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
整理性灵 newsletter 书单
+
青梧 · 分工使 2 · token 12.4k / 20k
+
+
+
运转 · 运行中 running
+
+
+
+
+
+
周报草稿 · 第 37 周
+
任务 Agent · 无角色身份
+
+
待启 · 排队中 queued
+
+
+
+
书斋照片归档命名
+
青梧 · 已成 · 耗时 4 分 12 秒
+
+
已成 · 已完成 completed
+
+
+
+
+
+
+
+ 待你决定审批与候选
+
+
+
+
命
+
+
请命 · 写入日历(6 个事件)
+
青梧请求在日历创建 6 个「共读讨论」事件 · 数据范围:仅日历 · 费用:无
+
+
去朱批
+
+
+
息壤候选 ×3
+
+
「读书会在隔周四晚」「偏爱短篇」「花粉过敏(重忆)」
+
来自青梧的对话候选 · 归真后才会影响未来上下文
+
+
去审阅
+
+
+
叩心候选
+
+
青梧想在明晚共读前发一条提醒
+
叩心当前静默 · 不回应不会降低关系 · 可设频率上限
+
+
了解叩心
+
+
+
+
+
+
+
+
+
diff --git a/design/index.html b/design/index.html
new file mode 100644
index 0000000..d5f3e48
--- /dev/null
+++ b/design/index.html
@@ -0,0 +1,243 @@
+
+
+
+
+
+魂镜 Hunjing · 界面原型 · 设计说明
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 设计说明 / 静态原型 v0.1
+
+ 静态原型 · 待审查 · 确认后转 React
+
+
+
+
+
+
魂镜 原型 v0.2
+
魂镜 Web 界面静态 HTML 原型(v0.1 第一批方向已确认,v0.2 增补第二批共 15 个界面),供审查视觉方向、布局结构与文化语言的界面落地方式。全部确认后按此实现为 React 组件;未在本文档登记的视觉细节以本页令牌为准。
+
+
+
+ 原型页面
+ 第一批(已确认):镜庭、镜生、镜树;第二批:息壤、形仪、天工阁、神识阁、百工坊、雅集、同契台、云市、器谱、司命台、设置,及运营者/自托管使用的管理面与认证页。全站链接已疏通:跨页指向对应页面、页内为锚点;logo 单击回本目录,350ms 内连点 5 次触发「镜启」彩蛋(承接 main 分支旧版设计);侧栏可折叠并记忆偏好。
+
+
+
+
+ 设计立场
+ 四条约束所有页面的决定,审查时优先核对这四条是否成立。
+
+
一 · 双层文字系统 —— 文化名用衬线宋体,直白解释用无衬线小字,两种字体本身就是"文化名 · 直白解释"两层结构的可视化;风险、金额、权限、删除等后果始终以直白语言为主,文化名只作辅助。
+
二 · 朱砂只属于朱批 —— 朱砂色是"用户确认决定"的专用语义色:请命卡的允许按钮、危险动作入口使用它;普通操作一律铜镜青。颜色即权限语言。
+
三 · 墨与月白 —— 深色为墨(青调黑,非纯黑),浅色为月白(青灰白,非暖米);铜镜青为主色,取铜镜铜绿之意,不用蓝紫系默认色。
+
四 · 状态永远三段式 —— 状态徽章固定为「文化状态 · 直白状态 + 稳定工程值」,例如「运转 · 运行中 running 」;错误与交易状态绝不只显示文化名。
+
+
+
+
+ 色彩令牌
+ 右下角可切换主题;以下色板随主题联动。转 React 时这些令牌进入 Tailwind 配置 / CSS 变量。
+
+
+
+
+ 字体规范
+ 不引入外部字体文件,使用系统中文字体栈;转 React 时同栈迁移。
+
+
文化名 · 宋体衬线 镜树 · 分支对话
+
界面正文 · 无衬线 正文 14px / 行高 1.65,辅助文字 12.5px,元信息 11.5px
+
稳定值 · 等宽 running · m_01h8xq… · block_id: blk_7f3a
+
+
+
+
+ 状态徽章全集
+ 对应 docs/product/cultural-language.md 第 10 节;徽章三段:文化状态 · 直白状态 · 稳定值。
+
+
+ 徽章 稳定值 使用场景
+
+ 待启 · 排队中 queued queued Run / 媒体任务排队
+ 运转 · 运行中 running running Run 执行中
+ 请命中 · 等待确认 waiting_approval waiting_approval 等待朱批
+ 已成 · 已完成 completed completed Run / 任务完成
+ 受阻 · 失败 failed failed 失败(附原因与重试)
+ 已止 · 已取消 cancelled cancelled 用户取消
+ 守一 · 安静待命 idle idle 角色无运行
+ 整备 · 正在处理 processing processing 媒体 / 资源处理
+ 待审 · 等待审核 pending_review pending_review 社区 / 市场审核
+ 已刊 · 已发布 published published 社区内容
+
+
+
+
+
+
+ 组件样例
+ 按钮语义:铜镜青为主操作,朱砂仅用于朱批(用户确认决定)与危险操作,描边为次操作。
+
+
+
+ 开始对话
+ 允许(朱批)
+ 查看谱系
+ 取消
+ 删除角色数据
+ 续生
+
+
+ 请命请求你的确认
+ 息壤角色记忆
+ 幻境临时试演 · 不入正史
+
+
+
+
+
python reading_progress.py
+
# 汇总共读接龙进度(示意)
+from pathlib import Path
+
+def summarize (records: list [dict ]) -> dict :
+ done = [r for r in records if r["finished" ]]
+ return {
+ "total" : len (records),
+ "finished" : len (done),
+ "pending" : [r["name" ] for r in records if not r["finished" ]],
+ }
+
+
+
+
注意力权重 = softmax( QKT / √dk ) · V
+ fallback:注意力权重等于 Q、K 转置乘积经缩放与归一化后对 V 的加权求和
+
+
+
+
+
+ 审查重点
+ 第一批(色彩、字体、镜树/镜生/镜庭结构)已确认;本轮重点核对第二批页面,反馈时按页名 + 位置描述即可。
+
+
1. 息壤:候选审阅的「归真入忆 / 正影纠正 / 忽略」操作,以及大周天来源、重忆提示的呈现是否可理解。
+
2. 天工阁:工筹步骤、预算、分工使、轨迹的信息层级是否符合你对 Run 治理的预期。
+
3. 神识阁 / 百工坊:路由继承链与「升级新增权限需重新确认」的展示是否清晰,朱批语义是否仍然专一。
+
4. 云市:金额、分润、退契规则是否足够直白(文化名没有掩盖交易后果)。
+
5. 器谱 / 司命台 / 设置:直白权限句、双重授权、私箧与删除等高风险项的措辞与视觉重量。
+
6. 雅集 / 同契台:社区帖卡与协作审稿流的信息密度是否合适。
+
+
+
+
+ 旧版传承与候选
+ 已系统挖掘 main 分支旧版 25 页设计原型(铜镜鉴心体系),按「取手法、不照搬旧概念名」原则融入——六爻 / 九枢 / 三魂等概念 1.0 已由小周天、灵台六模块、内驱力等替代,仅传承其可视化手法。
+
+
+ 旧版想法 来源页 状态
+
+ 鎏金发丝线三档令牌 tokens.css ✓ 已融入全站(分组标题 / 金标竖条 / 品牌外环 / 时间线)
+ logo 五连击「镜启」诗签彩蛋 _theme.js ✓ 已融入(修好首次单击即跳转的缺陷)
+ 铜镜登录页(铭文环 / 铜面噪点 / 扫描亮带) copper-mirror-login ✓ 已融入登录 / 注册页
+ 极光铜晕底 + 卡片 hover 光扫 _base.css / rune-engine ✓ 已融入全站(极淡,不干扰阅读)
+ 日晷时辰盘 + 节气 + 相伴日文案 time ✓ 已融入镜庭 hero(巳时 · 白露 · 第 918 日)
+ 朱批卡风险分级(RISK L2) chat-redesign ✓ 已融入镜树请命卡
+ 「呈镜」发送按钮命名(旧版已确立血脉) chat-redesign ✓ 已融入镜树输入区(aria 仍读「发送」)
+ 运行阶段爻象三态(done / active / pending) chat / evolution ✓ 已融入天工阁工筹(active 步骤鎏金光晕)
+ 河图置信度图谱(置信→线宽,假设→朱漆虚线待验) world ✓ 已融入息壤玄络(待验边 + 标注)
+ 息壤浮现(本次回应用了哪些记忆) chat / copper-v2 ✓ 对应 1.0 上下文镜(镜树右栏,同源 ContextSnapshot)
+ 九枢星盘「活的仪器」+ 铜镜四态健康词汇 admin / copper-v2 ✓ 已复刻为灵台六枢星盘(太一居中 + 五枢环绕 + 呼吸刻度 / 慢旋 / 告警闪烁)+ 镜态徽章(管理面)
+ 五行生克健康环(系统健康=关系动力学) wuxing ✓ 已复刻为相生 / 相克指标对告警(管理面星盘卡;环状图 React 阶段评估)
+ 眉批 depth 标尺注入预览 margin-notes 候选 —— 上下文镜深度维度(React 阶段与 Context Manifest 联动)
+ 好奇萤火(亮度=期望值)与探索天平 drive ✓ 萤火已融入内驱力维度(亮度=维度值);探索天平待天工阁预算增强时评估
+ 周天四镜阶段链(评 / 演 / 候 / 藏 四面小铜镜) evolution ✓ 已复刻为大周天四镜:回 / 照 / 议 / 呈(镜生页,ADR-023 四阶段)
+ 会话列表「今日 / 本周 / 更早」分组 + 彩点状态 chat-redesign ✓ 已融入镜树会话列表(活跃铜绿 / 幻境秋香 / 待朱批朱砂)
+ 「深虑 / 博闻」输入控制药丸 + 用户消息鎏金右边线 chat-redesign / copper-v2 ✓ 已融入镜树输入区与用户气泡
+ 起居注时间河「卷」章分割 admin ✓ 已融入管理面审计快照
+ 太极阴阳平衡 gauge(三态) cosmos ✓ 已复刻为朱批治理阴阳平衡(司命台,阳亢 / 平衡 / 阴盛)
+ 角色 card-banner 渐变横幅 character-detail ✓ 已融入镜生详情页
+ 思考块折叠卡(复刻 chat-redesign) chat-redesign ✓ 已融入(用户确认):思考摘要可折叠 + 深虑强度(低/中/高)——契约候选 :正式实现前需在 content-blocks 契约登记 thinking 块类型与流式语义;摘要仅会话内可见,不入长期记录与诊断导出
+
+
+
+
+
+
+
+
+
+
diff --git a/design/login.html b/design/login.html
new file mode 100644
index 0000000..af7b080
--- /dev/null
+++ b/design/login.html
@@ -0,0 +1,87 @@
+
+
+
+
+
+魂镜 · 登录
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 镜中有镜 · 心外无心 · 汝来看此镜时 · 此镜亦照汝 · 铜镜本无字 · 照尽往来人 ·
+
+
+
+
+
+
+
+ 静态原型 · 待审查
+
+ 原型目录
+
+
+
+
+
+
+
+
登入魂镜
+
+
长期 AI 角色、Agent 与创作社区平台
+
+
+
+
其他方式
+
+
+
+ Web 使用安全会话 Cookie(OAuth 2.1);桌面与移动客户端使用 Authorization Code + PKCE。
+ 凭据只经 Header 传递,不进入 URL、日志或页面。连续失败将触发限流与通知。
+
+
+
+
+
+
+
+
diff --git a/design/marketplace.html b/design/marketplace.html
new file mode 100644
index 0000000..95f4efa
--- /dev/null
+++ b/design/marketplace.html
@@ -0,0 +1,140 @@
+
+
+
+
+
+云市 · 创作市场 — 魂镜 Hunjing
+
+
+
+
+
+
+
+
+
+ 云市 · 创作市场
+
+ 静态原型 · 待审查
+
+
+
+
+
+
+ 购买的是版本与许可(市契),不是人格所有权。金额、费率与退款规则始终直白展示,文化名只作辅助。
+
+
+
+
+ 全部
+ 角色模板
+ 世界设定
+ Live2D
+ 3D 资产
+ 音色包
+ 插件
+
+
+
+
+
+
+
+
+
+
+
雾 世界设定
+
雾中茶寮 · 世界合集 雪袖的茶寮 · v3 · 售出 412
+
¥28.00 可商用
+
+
+
+
灯 角色模板
+
照夜 · 掌灯人模板 沉舟 · v2 · 售出 1,208
+
¥12.00 个人用
+
+
+
+
灵 Live2D
+
守砚人 · 灵动模型 云舟 · v3 · 售出 96
+
¥45.00 可商用
+
+
+
+
匣 音色包
+
江南雨声 · 环境音色 听澜 · v1 · 售出 340
+
免费 署名使用
+
+
+
+
砚 3D 资产
+
书斋器物 · VRM 附件包 临江仙 · v2 · 售出 58
+
¥18.00 个人用
+
+
+
+
谱 插件
+
曲谱排版 · 插件 百工 · v1.4 · 售出 233
+
¥6.00 个人用
+
+
+
+
+
+
+ 市契商品许可 · 以「雾中茶寮 v3」为例
+
+
+ 项目 内容
+
+ 许可范围 商用(衍生作品可售,须保留署名;不可单独转售原始文件)
+ 交付物 世界文档 42 页 · 地图 PSD · 场景音效 12 段 · 更新至 v3.x
+ 退契规则 14 天内未下载可全额退款;下载后遵循许可,重大缺陷另行处理
+ 价格构成 ¥28.00 = 创作者分润 ¥23.80(85%)+ 平台费 ¥4.20(15%)· 税按地区另计
+ 地区可售 当前地区可售 · 跨区价格与税务见结算页
+
+
+
+ 购买 · ¥28.00
+ 加入购物车
+ 支付经 Idempotency-Key 幂等 · 重复点击不会重复扣款
+
+
+
+
+
+
+
+
+
+
+
+ 下载对账单
+ 申请结算
+
+
+ Ledger 不可变,修正写反向分录;每日自动对账(支付 / 订单 / Ledger / 结算四账核对)。
+
+
+
+
+
+
+
+
diff --git a/design/memory.html b/design/memory.html
new file mode 100644
index 0000000..11ba9ea
--- /dev/null
+++ b/design/memory.html
@@ -0,0 +1,242 @@
+
+
+
+
+
+息壤 · 记忆与知识 — 魂镜 Hunjing
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 息壤 · 记忆与知识
+
+ 静态原型 · 待审查
+
+
+
+
+
+
+ 全部 · 132
+ 青梧 · 98
+ 雪袖 · 22
+ 观澜 · 12
+
+
+
+
+
+
+
+
+ 候选审阅 3
+ 明忆 128
+ 重忆 4
+ 世谱 17
+ 玄络
+
+
+
+
+ 归真的约束:候选经你确认才会影响未来上下文;对话中的归真不自动写入所有长期记忆。境界内容只成世界知识候选,幻境内容须手动入忆并标注性质。
+
+
+
+
+
+ 偏好 preference
+ 来源 · 镜树对话 m_91d0 · extractor v5
+ 置信 0.92 · 敏感度低 · 青梧
+
+
「读书会隔周四 19:30 举行,每次 90 分钟。」
+
+
归真入忆
+
正影 · 纠正后入忆
+
忽略
+
查看来源上下文
+
+
+
+
+
+
+ 事实 fact
+ 大周天 · 第 7 轮提议
+ 置信 0.71 · 敏感度低 · 青梧
+
+
「云舟在共读选篇时倾向短篇,超过 1500 字的篇目完成率明显下降。」
+
由 42 次小周天完成信号的回顾阶段产生 · 复用同一审核流 · 可追溯本轮输入
+
+
+
+
+
+
+ 关系 relationship
+ 建议作重忆保存
+ 置信 0.68 · 敏感度中 · 青梧
+
+
「云舟近期在工作日晚上更希望被倾听,而不是立刻获得建议。」
+
重忆 · 敏感或高影响记忆:归真后仅在明确允许时进入上下文,可随时正影纠正或归档,版本不可变。
+
+
归真为重忆
+
正影 · 纠正后入忆
+
忽略
+
查看来源上下文
+
+
+
+
+
+
+ 最近归真已生效的明忆
+ 全部 128 条 · 见下方列表
+
+
+
+
偏好
+
+
「云舟不喜长篇说教,笔记以要点为先。」
+
青梧 · v3(正影自 v2)· 来源:手动 · 有效期长期 · ETag 并发受控
+
+
正影
+
归档
+
+
+
事实
+
+
「3 月购入《陶庵梦忆》校注本(中华书局)。」
+
青梧 · v1 · 来源:对话 · 实录
+
+
正影
+
归档
+
+
+
事件
+
+
「读书会成立于 2024-03,成员 6 人。」
+
青梧 · v1 · 来源:世谱引用 · 删除将传播到向量与图投影
+
+
正影
+
归档
+
+
+ MemoryVersion 不可变:纠正与取代都写新版本,旧版本保留可查;删除建墓碑且传播到索引、缓存与派生数据,墓碑不保存原文。
+
+
+
+
+
+ 玄络关系图谱投影 · 可重建
+ 玄络 · 可交互图谱在 React 阶段实现
+
+
+
+
+
+
+
+
+
+
+ 云舟
+
+ 青梧
+
+ 雪袖
+
+ 读书会
+ 世谱
+
+ 陶庵梦忆
+ 世界知识
+ 缘契 · 共读者 v4
+ 缘契 · 茶友 v1
+ 成员 · 发起人
+ 正在共读
+ 待验
+
+
+
+ 玄络是可重建投影:删除与权限变化随宿主重建;跨用户、跨角色召回恒为 0(fail-closed 已验证)。
+
+
+
+
+
+
+
diff --git a/design/model-gateway.html b/design/model-gateway.html
new file mode 100644
index 0000000..59d5350
--- /dev/null
+++ b/design/model-gateway.html
@@ -0,0 +1,124 @@
+
+
+
+
+
+神识阁 · 模型与路由 — 魂镜 Hunjing
+
+
+
+
+
+
+
+
+
+ 神识阁 · 模型与路由
+
+ 静态原型 · 待审查
+
+
+
+
+
+
+ 神识是角色的可替换推理能力,不等于角色身份或意识证明。首个可见输出后不切换 Provider;引擎缺失时拒绝执行(fail closed)。
+
+
+
+ 逻辑模型能力目录 全部 9 个 · 列表即目录
+
+
+
沉潜 · 深思 可用
+
推理 · 128k 上下文 · 函数调用
+
¥12.00 / ¥48.00 每 1M token(入/出)
+
价格快照 v2026-09-01 · 2 个 Endpoint · 故障转移已配置
+
+
+
惊鸿 · 快速 可用
+
轻量 · 32k · 视觉
+
¥1.60 / ¥6.40 每 1M token
+
会话临时选择的默认候选
+
+
+
丹青 · 图像 降级中
+
文生图 / 图生图 · 角色一致性
+
¥0.24 / 张
+
主 Endpoint 限流 · 已切备用 · 新任务排队 ≤ 2 分钟
+
+
+
+
+
+ 路由继承链上层不可被下层覆盖
+
+
① 平台硬边界
禁用模型与绝对上限,任何下层不可逾越
生效
+
+
+
+
⑤ 会话临时选择 当前生效
镜树「共读选篇」选择了 惊鸿 · 快速
生效 session
+
+
+
+
+
+ BYOK自带模型密钥 添加密钥
+
+
+
byok_a31f…(神思 Provider)
2026-08-12 添加 · 存于系统 Keychain · 不写配置与日志
+
轮换
+
+
+
+
byok_7c02…(图像 Provider)
2026-09-01 添加 · 仅丹青 · 图像使用
+
轮换
+
+ BYOK 调用按实际用量计费一次,不重复计费(ADR-021);凭据只经 Header 传递,永不转发至上游之外的日志。
+
+
+
+ 本月用量9 月 1–15 日 明细 · Run 级观测见司命台诊断
+
+
+ 指标 数值
+
+ Token(入/出) 486k / 212k
+ Prompt Cache 命中 31%
+ P95 TTFT 1.4s
+ 费用 ¥18.64
+ 错误 / 重试 2 / 1
+ 未知 usage 记录 null(不猜测)
+
+
+
+
+
+
+
+
+
+
+
diff --git a/design/plugins.html b/design/plugins.html
new file mode 100644
index 0000000..11b81df
--- /dev/null
+++ b/design/plugins.html
@@ -0,0 +1,115 @@
+
+
+
+
+
+百工坊 · 插件与能力 — 魂镜 Hunjing
+
+
+
+
+
+
+
+
+
+ 百工坊 · 插件与能力生态
+
+ 静态原型 · 待审查
+
+
+
+
+
+
+ 购买不等于安装,安装不等于启用,启用不等于授予新增权限。安全内核(认证、审批、交易账、审计)不可插件化。
+
+
+
+
+ 沙箱隔离运行时
+ Worker 运转中 sandbox:ok
+
+ 本地代码插件运行于独立进程容器;声明式 / 远程插件默认受限。无符合策略的隔离后端时拒绝执行,不回退宿主(fail closed)。
+
+
+
+ 已安装4 个
+
+
+
Core
+
系统日历 bundled · 主进程
仅 Core 插件可进主进程 · 权限:日历读写(每次请命)
+
+
+
+
声明式
+
笔记同步 受限运行时
权限:笔记库 /读书会/ 读写 · 网络:无
+
+
+
+
待更新
+
性灵检索 远程 API
v1.2 → v1.3 · 新增权限需重新确认(见下方)
+
查看变更
+
+
+
本地代码
+
代码执行沙箱 独立容器 · 部署者信任
Run 固定插件快照 · 活动运行不热替换
+
+
+
+
+
+
+ 升级确认性灵检索 v1.3 新增权限
+
+
+
命
+
+
请命 升级新增权限必须重新确认
+
不确认则保留 v1.2,不影响现有使用
+
+
+
+
现有 v1.2 读取笔记元数据 · 网络 api.xingling.app(只读)
+
新增 v1.3 写入笔记内容 · 网络 api.xingling.app(读写)· 存储最近 20 条查询
+
费用 免费插件 · 每次外部检索计费 ¥0.02
+
撤架 安全撤架阻断新调用,保留历史审计
+
+
+ 允许更新(朱批)
+ 保留 v1.2
+ 工契 · 插件清单完整权限对比见详情。
+
+
+
+
+
+
+
+
日历写入 · 青梧
用途:共读事件 · 范围:仅指定事件 · 期限:单次 · 可随时撤销
撤销
+
笔记库读写 · 青梧
行契 · 30 天内同类免请命 · 剩余 12 天 · 超限后重新请命
撤销
+
网络检索 · 任务 Agent
用途:资料校对 · 白名单 3 域 · 期限:本 Run
随 Run 结束失效
+
+
+
+
+
+
+
+
+
diff --git a/design/register.html b/design/register.html
new file mode 100644
index 0000000..68ae78b
--- /dev/null
+++ b/design/register.html
@@ -0,0 +1,108 @@
+
+
+
+
+
+魂镜 · 创建账户
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 幻由心生 · 镜随心转 · 一念起 · 万镜生 · 且向镜中看 · 那个是谁 ·
+
+
+
+
+
+
+
+
+
+
+
+
+
创建账户
+
+
化名与法定身份分离核验;随时可导出、撤权或删除
+
+
+
+
注册即创建你的镜域(个人数据域);角色、记忆与授权都归属镜域治理。
+
+
+
+
+
+
+
diff --git a/design/settings.html b/design/settings.html
new file mode 100644
index 0000000..78101ec
--- /dev/null
+++ b/design/settings.html
@@ -0,0 +1,121 @@
+
+
+
+
+
+设置 · 账户与偏好 — 魂镜 Hunjing
+
+
+
+
+
+
+
+
+
+ 设置 · 账户与偏好
+
+ 静态原型 · 待审查
+
+
+
+
+
+ 账户
+
+
舟
+
+
云舟前台化名 · 与法定身份分离核验
+
yunzhou@example.com · OAuth 2.1 · Web 安全会话
+
+
+ 多凭证管理
+ 修改化名
+
+
+
+
+
+ 会话
+
+
+
MacBook Pro · Tauri 桌面
PKCE 登录 · 当前会话
+
当前
+
+
+
+
iPhone · 移动端
PKCE 登录 · 昨天活跃
+
撤销
+
+
+
+
+ 私箧独立持钥的加密私密区 已启用 locked
+
+
+
平台仅存密文,不可恢复
账户恢复、管理员与诊断均不可读取;恢复钥匙只在你手中,丢失即永久不可恢复。
+
+
+
+
使用量 12 MB
日记 41 篇 · 手记 7 份 · 不参与任何推荐与训练
+
打开私箧
+
+
+
+
+ 数据权访问 · 纠正 · 导出 · 撤权 · 删除
+
+
+
模型训练贡献
+
Personal Domain 数据默认不用于模型训练。自愿贡献需独立、明确、可随时撤销的同意。
+
+
+
+
+
导出全部数据
角色、设定、记忆、对话、资产与来源谱系 · 魂籍可携格式
+
导出
+
+
+
撤回全部授权
撤销插件、设备与第三方访问授权,数据保留
+
撤权
+
+
+
删除账户与全部数据
不可逆 · 有冷静期 · 与角色死亡是两个独立操作 · 删除传播到索引与派生数据
+
删除
+
+
+
+
+
+
+ 封镜账户暂时停用 · 不是角色静修、归藏或死亡
+ 封镜期间登录被冻结,角色进入静修,数据完整保留;解封后可复照重开。
+ 封镜账户
+
+
+
+
+
+
+
+
diff --git a/frontend/next-env.d.ts b/frontend/next-env.d.ts
new file mode 100644
index 0000000..830fb59
--- /dev/null
+++ b/frontend/next-env.d.ts
@@ -0,0 +1,6 @@
+///
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/frontend/next.config.ts b/frontend/next.config.ts
index 2f6491c..26c728a 100644
--- a/frontend/next.config.ts
+++ b/frontend/next.config.ts
@@ -2,6 +2,15 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = {
reactStrictMode: true,
+ // 同源代理:前端 /api/* → 本地后端(避免开发档 CORS;生产由反向代理承担同职责)
+ async rewrites() {
+ return [
+ {
+ source: "/api/:path*",
+ destination: `${process.env.HUNJING_API_URL ?? "http://127.0.0.1:8000"}/:path*`,
+ },
+ ];
+ },
};
export default nextConfig;
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
deleted file mode 100644
index 6426447..0000000
--- a/frontend/package-lock.json
+++ /dev/null
@@ -1,1421 +0,0 @@
-{
- "name": "hunjing-frontend",
- "version": "0.1.0",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "hunjing-frontend",
- "version": "0.1.0",
- "dependencies": {
- "next": "^15.1.0",
- "react": "^19.0.0",
- "react-dom": "^19.0.0"
- },
- "devDependencies": {
- "@types/node": "^22.10.0",
- "@types/react": "^19.0.0",
- "@types/react-dom": "^19.0.0",
- "openapi-typescript": "^7.5.0",
- "typescript": "^5.7.0"
- }
- },
- "node_modules/@babel/code-frame": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
- "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-validator-identifier": "^7.29.7",
- "js-tokens": "^4.0.0",
- "picocolors": "^1.1.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-identifier": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
- "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@emnapi/runtime": {
- "version": "1.11.3",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz",
- "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@img/colour": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
- "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@img/sharp-darwin-arm64": {
- "version": "0.35.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.4.tgz",
- "integrity": "sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-darwin-arm64": "1.3.3"
- }
- },
- "node_modules/@img/sharp-darwin-x64": {
- "version": "0.35.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.4.tgz",
- "integrity": "sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-darwin-x64": "1.3.3"
- }
- },
- "node_modules/@img/sharp-freebsd-wasm32": {
- "version": "0.35.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.4.tgz",
- "integrity": "sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==",
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "dependencies": {
- "@img/sharp-wasm32": "0.35.4"
- },
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-darwin-arm64": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.3.tgz",
- "integrity": "sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==",
- "cpu": [
- "arm64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "darwin"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-darwin-x64": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.3.tgz",
- "integrity": "sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==",
- "cpu": [
- "x64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "darwin"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-arm": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.3.tgz",
- "integrity": "sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==",
- "cpu": [
- "arm"
- ],
- "libc": [
- "glibc"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-arm64": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.3.tgz",
- "integrity": "sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==",
- "cpu": [
- "arm64"
- ],
- "libc": [
- "glibc"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-ppc64": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.3.tgz",
- "integrity": "sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==",
- "cpu": [
- "ppc64"
- ],
- "libc": [
- "glibc"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-riscv64": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.3.tgz",
- "integrity": "sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==",
- "cpu": [
- "riscv64"
- ],
- "libc": [
- "glibc"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-s390x": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.3.tgz",
- "integrity": "sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==",
- "cpu": [
- "s390x"
- ],
- "libc": [
- "glibc"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-x64": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.3.tgz",
- "integrity": "sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==",
- "cpu": [
- "x64"
- ],
- "libc": [
- "glibc"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.3.tgz",
- "integrity": "sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==",
- "cpu": [
- "arm64"
- ],
- "libc": [
- "musl"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linuxmusl-x64": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.3.tgz",
- "integrity": "sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==",
- "cpu": [
- "x64"
- ],
- "libc": [
- "musl"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-linux-arm": {
- "version": "0.35.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.4.tgz",
- "integrity": "sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==",
- "cpu": [
- "arm"
- ],
- "libc": [
- "glibc"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-arm": "1.3.3"
- }
- },
- "node_modules/@img/sharp-linux-arm64": {
- "version": "0.35.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.4.tgz",
- "integrity": "sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==",
- "cpu": [
- "arm64"
- ],
- "libc": [
- "glibc"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-arm64": "1.3.3"
- }
- },
- "node_modules/@img/sharp-linux-ppc64": {
- "version": "0.35.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.4.tgz",
- "integrity": "sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==",
- "cpu": [
- "ppc64"
- ],
- "libc": [
- "glibc"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-ppc64": "1.3.3"
- }
- },
- "node_modules/@img/sharp-linux-riscv64": {
- "version": "0.35.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.4.tgz",
- "integrity": "sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==",
- "cpu": [
- "riscv64"
- ],
- "libc": [
- "glibc"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-riscv64": "1.3.3"
- }
- },
- "node_modules/@img/sharp-linux-s390x": {
- "version": "0.35.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.4.tgz",
- "integrity": "sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==",
- "cpu": [
- "s390x"
- ],
- "libc": [
- "glibc"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-s390x": "1.3.3"
- }
- },
- "node_modules/@img/sharp-linux-x64": {
- "version": "0.35.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.4.tgz",
- "integrity": "sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==",
- "cpu": [
- "x64"
- ],
- "libc": [
- "glibc"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-x64": "1.3.3"
- }
- },
- "node_modules/@img/sharp-linuxmusl-arm64": {
- "version": "0.35.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.4.tgz",
- "integrity": "sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==",
- "cpu": [
- "arm64"
- ],
- "libc": [
- "musl"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linuxmusl-arm64": "1.3.3"
- }
- },
- "node_modules/@img/sharp-linuxmusl-x64": {
- "version": "0.35.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.4.tgz",
- "integrity": "sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==",
- "cpu": [
- "x64"
- ],
- "libc": [
- "musl"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linuxmusl-x64": "1.3.3"
- }
- },
- "node_modules/@img/sharp-wasm32": {
- "version": "0.35.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.4.tgz",
- "integrity": "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==",
- "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/runtime": "^1.11.3"
- },
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-webcontainers-wasm32": {
- "version": "0.35.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.4.tgz",
- "integrity": "sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==",
- "cpu": [
- "wasm32"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "dependencies": {
- "@img/sharp-wasm32": "0.35.4"
- },
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-win32-arm64": {
- "version": "0.35.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.4.tgz",
- "integrity": "sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-win32-ia32": {
- "version": "0.35.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.4.tgz",
- "integrity": "sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==",
- "cpu": [
- "ia32"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": "^20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-win32-x64": {
- "version": "0.35.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.4.tgz",
- "integrity": "sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@next/env": {
- "version": "15.5.25",
- "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.25.tgz",
- "integrity": "sha512-42h1lLr07vl4gawALP1hsgRZjHB1xYa58JfUfHwr0f7jG/zhPakh5GHkADHXOC9ZxUvlQFOPIrp7s6qX4DezPQ==",
- "license": "MIT"
- },
- "node_modules/@next/swc-darwin-arm64": {
- "version": "15.5.25",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.25.tgz",
- "integrity": "sha512-w+RR0v/QuApnWEjRGm1z6gcObKwGMb5YPA7V3bzBEVSBpMFUXprer0tS27UxjUcEnqbhL7Zuzohej79B6rYmBg==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-darwin-x64": {
- "version": "15.5.25",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.25.tgz",
- "integrity": "sha512-QiGGBUSakt8S1H4Lt9Ehsh6Ja87axiBnQQgysOObvCbI7iUfJnRGntF1P64S4/ijuHFnSB8KLsEddkY3nN26uw==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-arm64-gnu": {
- "version": "15.5.25",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.25.tgz",
- "integrity": "sha512-ehLos/66zo0d/mJCU5u96a/VDcr01aaUrX0o/i16UdInxz8qPTCDSxGtjk/Lps1sIr1RJFdiX3hxc0fxJo+cPA==",
- "cpu": [
- "arm64"
- ],
- "libc": [
- "glibc"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-arm64-musl": {
- "version": "15.5.25",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.25.tgz",
- "integrity": "sha512-ZVMrqLiJ7DiChgmbkQwFtdhAnUkSH/4p7tB29QY+giATb0Q/XGHNRSKAb/B8XGDHRUaA67NepOW5W8u3ZRJBAA==",
- "cpu": [
- "arm64"
- ],
- "libc": [
- "musl"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-x64-gnu": {
- "version": "15.5.25",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.25.tgz",
- "integrity": "sha512-UOewtDGkTMJTiODrEdeLZ50yGb59xCZSriNpXkfPMxRRgwDkGc7i8mLWqV5076wEdb+Ca/XN7MhJyM3CupyNyQ==",
- "cpu": [
- "x64"
- ],
- "libc": [
- "glibc"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-x64-musl": {
- "version": "15.5.25",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.25.tgz",
- "integrity": "sha512-UBHwA8AhkCZgtRfU1aJpunuAJe/6gZv6jDESQe4p5MjTb5V0YEeJBWCdNqx15Vj3x+5jmauRfeMJSjfQj9HGFQ==",
- "cpu": [
- "x64"
- ],
- "libc": [
- "musl"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-win32-arm64-msvc": {
- "version": "15.5.25",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.25.tgz",
- "integrity": "sha512-QcFcPRr16djk5IqK5+e8O80eZfgWzIvVBXfitIq0tQ/uc+eyfdoZ0NmKc0cnbIJyfVwREapKuG97YcxWA9gcpA==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-win32-x64-msvc": {
- "version": "15.5.25",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.25.tgz",
- "integrity": "sha512-zREeykps3ndWr9egJgvJKqVkkDuaw6Zrrg23cYBos0ygydFkAWYU4+PaPVwXzP1eAYQJe53ShSK45iDM529BOg==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@redocly/ajv": {
- "version": "8.11.2",
- "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.2.tgz",
- "integrity": "sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.1",
- "json-schema-traverse": "^1.0.0",
- "require-from-string": "^2.0.2",
- "uri-js-replace": "^1.0.1"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/@redocly/config": {
- "version": "0.22.0",
- "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.22.0.tgz",
- "integrity": "sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@redocly/openapi-core": {
- "version": "1.34.19",
- "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.34.19.tgz",
- "integrity": "sha512-o/0VgsBXgwcY1lyeqcVtSGdTQAPnVggo0fbFVPlxl5XVDKUcVH0OLRqt3CbkwByT5FU305E0iE0O7MzThjDblw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@redocly/ajv": "8.11.2",
- "@redocly/config": "0.22.0",
- "colorette": "1.4.0",
- "https-proxy-agent": "7.0.6",
- "js-levenshtein": "1.1.6",
- "js-yaml": "4.3.1",
- "minimatch": "5.1.9",
- "pluralize": "8.0.0",
- "yaml-ast-parser": "0.0.43"
- },
- "engines": {
- "node": ">=18.17.0",
- "npm": ">=9.5.0"
- }
- },
- "node_modules/@swc/helpers": {
- "version": "0.5.15",
- "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
- "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.8.0"
- }
- },
- "node_modules/@types/node": {
- "version": "22.20.1",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz",
- "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "undici-types": "~6.21.0"
- }
- },
- "node_modules/@types/react": {
- "version": "19.2.18",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz",
- "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "csstype": "^3.2.2"
- }
- },
- "node_modules/@types/react-dom": {
- "version": "19.2.7",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.7.tgz",
- "integrity": "sha512-I8bPpDLcHBv1qiIiXDCy71Rt8eQDKJP0sMSWJphDdAcdqiJ1sGpZamavoEIRZmYzjia9LuEb2HlYdDpmoENpvQ==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "^19.2.0"
- }
- },
- "node_modules/agent-base": {
- "version": "7.1.4",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
- "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/ansi-colors": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
- "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true,
- "license": "Python-2.0"
- },
- "node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/brace-expansion": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
- "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/caniuse-lite": {
- "version": "1.0.30001810",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz",
- "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "CC-BY-4.0"
- },
- "node_modules/change-case": {
- "version": "5.4.4",
- "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz",
- "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/client-only": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
- "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
- "license": "MIT"
- },
- "node_modules/colorette": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz",
- "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/csstype": {
- "version": "3.2.3",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
- "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/detect-libc": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
- "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
- "license": "Apache-2.0",
- "optional": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/https-proxy-agent": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
- "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "agent-base": "^7.1.2",
- "debug": "4"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/index-to-position": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz",
- "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/js-levenshtein": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz",
- "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/js-yaml": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz",
- "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/puzrin"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/nodeca"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "argparse": "^2.0.1"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
- "node_modules/json-schema-traverse": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/minimatch": {
- "version": "5.1.9",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
- "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/nanoid": {
- "version": "3.3.18",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
- "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "bin": {
- "nanoid": "bin/nanoid.cjs"
- },
- "engines": {
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
- }
- },
- "node_modules/next": {
- "version": "15.5.25",
- "resolved": "https://registry.npmjs.org/next/-/next-15.5.25.tgz",
- "integrity": "sha512-OMWNulIIqKM2ykvC2qMjIt0IoavB4UB2SCs4iXJ6z6847FvyH8jBmBWcvrF5iuhTu8Przh20Fo/aoszIdqx4PA==",
- "license": "MIT",
- "dependencies": {
- "@next/env": "15.5.25",
- "@swc/helpers": "0.5.15",
- "caniuse-lite": "^1.0.30001579",
- "postcss": "8.4.31",
- "styled-jsx": "5.1.6"
- },
- "bin": {
- "next": "dist/bin/next"
- },
- "engines": {
- "node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
- },
- "optionalDependencies": {
- "@next/swc-darwin-arm64": "15.5.25",
- "@next/swc-darwin-x64": "15.5.25",
- "@next/swc-linux-arm64-gnu": "15.5.25",
- "@next/swc-linux-arm64-musl": "15.5.25",
- "@next/swc-linux-x64-gnu": "15.5.25",
- "@next/swc-linux-x64-musl": "15.5.25",
- "@next/swc-win32-arm64-msvc": "15.5.25",
- "@next/swc-win32-x64-msvc": "15.5.25",
- "sharp": "^0.34.3 || ^0.35.4"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.1.0",
- "@playwright/test": "^1.51.1",
- "babel-plugin-react-compiler": "*",
- "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
- "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
- "sass": "^1.3.0"
- },
- "peerDependenciesMeta": {
- "@opentelemetry/api": {
- "optional": true
- },
- "@playwright/test": {
- "optional": true
- },
- "babel-plugin-react-compiler": {
- "optional": true
- },
- "sass": {
- "optional": true
- }
- }
- },
- "node_modules/openapi-typescript": {
- "version": "7.13.0",
- "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-7.13.0.tgz",
- "integrity": "sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@redocly/openapi-core": "^1.34.6",
- "ansi-colors": "^4.1.3",
- "change-case": "^5.4.4",
- "parse-json": "^8.3.0",
- "supports-color": "^10.2.2",
- "yargs-parser": "^21.1.1"
- },
- "bin": {
- "openapi-typescript": "bin/cli.js"
- },
- "peerDependencies": {
- "typescript": "^5.x"
- }
- },
- "node_modules/parse-json": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz",
- "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.26.2",
- "index-to-position": "^1.1.0",
- "type-fest": "^4.39.1"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/picocolors": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "license": "ISC"
- },
- "node_modules/pluralize": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
- "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/postcss": {
- "version": "8.4.31",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
- "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "nanoid": "^3.3.6",
- "picocolors": "^1.0.0",
- "source-map-js": "^1.0.2"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
- "node_modules/react": {
- "version": "19.2.8",
- "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
- "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/react-dom": {
- "version": "19.2.8",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
- "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
- "license": "MIT",
- "dependencies": {
- "scheduler": "^0.27.0"
- },
- "peerDependencies": {
- "react": "^19.2.8"
- }
- },
- "node_modules/require-from-string": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
- "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/scheduler": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
- "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
- "license": "MIT"
- },
- "node_modules/semver": {
- "version": "7.8.5",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
- "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
- "license": "ISC",
- "optional": true,
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/sharp": {
- "version": "0.35.4",
- "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz",
- "integrity": "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==",
- "license": "Apache-2.0",
- "optional": true,
- "dependencies": {
- "@img/colour": "^1.1.0",
- "detect-libc": "^2.1.2",
- "semver": "^7.8.5"
- },
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-darwin-arm64": "0.35.4",
- "@img/sharp-darwin-x64": "0.35.4",
- "@img/sharp-freebsd-wasm32": "0.35.4",
- "@img/sharp-libvips-darwin-arm64": "1.3.3",
- "@img/sharp-libvips-darwin-x64": "1.3.3",
- "@img/sharp-libvips-linux-arm": "1.3.3",
- "@img/sharp-libvips-linux-arm64": "1.3.3",
- "@img/sharp-libvips-linux-ppc64": "1.3.3",
- "@img/sharp-libvips-linux-riscv64": "1.3.3",
- "@img/sharp-libvips-linux-s390x": "1.3.3",
- "@img/sharp-libvips-linux-x64": "1.3.3",
- "@img/sharp-libvips-linuxmusl-arm64": "1.3.3",
- "@img/sharp-libvips-linuxmusl-x64": "1.3.3",
- "@img/sharp-linux-arm": "0.35.4",
- "@img/sharp-linux-arm64": "0.35.4",
- "@img/sharp-linux-ppc64": "0.35.4",
- "@img/sharp-linux-riscv64": "0.35.4",
- "@img/sharp-linux-s390x": "0.35.4",
- "@img/sharp-linux-x64": "0.35.4",
- "@img/sharp-linuxmusl-arm64": "0.35.4",
- "@img/sharp-linuxmusl-x64": "0.35.4",
- "@img/sharp-webcontainers-wasm32": "0.35.4",
- "@img/sharp-win32-arm64": "0.35.4",
- "@img/sharp-win32-ia32": "0.35.4",
- "@img/sharp-win32-x64": "0.35.4"
- },
- "peerDependenciesMeta": {
- "@types/node": {
- "optional": true
- }
- }
- },
- "node_modules/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/styled-jsx": {
- "version": "5.1.6",
- "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
- "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
- "license": "MIT",
- "dependencies": {
- "client-only": "0.0.1"
- },
- "engines": {
- "node": ">= 12.0.0"
- },
- "peerDependencies": {
- "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
- },
- "peerDependenciesMeta": {
- "@babel/core": {
- "optional": true
- },
- "babel-plugin-macros": {
- "optional": true
- }
- }
- },
- "node_modules/supports-color": {
- "version": "10.2.2",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz",
- "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
- }
- },
- "node_modules/tslib": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
- "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "license": "0BSD"
- },
- "node_modules/type-fest": {
- "version": "4.41.0",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
- "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
- "dev": true,
- "license": "(MIT OR CC0-1.0)",
- "engines": {
- "node": ">=16"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/typescript": {
- "version": "5.9.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
- "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
- "dev": true,
- "license": "Apache-2.0",
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=14.17"
- }
- },
- "node_modules/undici-types": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
- "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/uri-js-replace": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.1.tgz",
- "integrity": "sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/yaml-ast-parser": {
- "version": "0.0.43",
- "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz",
- "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==",
- "dev": true,
- "license": "Apache-2.0"
- },
- "node_modules/yargs-parser": {
- "version": "21.1.1",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
- "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=12"
- }
- }
- }
-}
diff --git a/frontend/package.json b/frontend/package.json
index d7ede0c..247934b 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -2,12 +2,15 @@
"name": "hunjing-frontend",
"version": "0.1.0",
"private": true,
+ "packageManager": "pnpm@11.21.0",
"scripts": {
"dev": "next dev",
"build": "next build",
"generate:api": "openapi-typescript ../openapi/openapi.yaml -o src/types/api.d.ts"
},
"dependencies": {
+ "@tanstack/react-query": "^5.103.1",
+ "lucide-react": "^1.46.0",
"next": "^15.1.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
new file mode 100644
index 0000000..b1e927d
--- /dev/null
+++ b/frontend/pnpm-lock.yaml
@@ -0,0 +1,887 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@tanstack/react-query':
+ specifier: ^5.103.1
+ version: 5.103.1(react@19.2.8)
+ lucide-react:
+ specifier: ^1.46.0
+ version: 1.46.0(react@19.2.8)
+ next:
+ specifier: ^15.1.0
+ version: 15.5.25(@types/node@22.20.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ react:
+ specifier: ^19.0.0
+ version: 19.2.8
+ react-dom:
+ specifier: ^19.0.0
+ version: 19.2.8(react@19.2.8)
+ devDependencies:
+ '@types/node':
+ specifier: ^22.10.0
+ version: 22.20.1
+ '@types/react':
+ specifier: ^19.0.0
+ version: 19.2.18
+ '@types/react-dom':
+ specifier: ^19.0.0
+ version: 19.2.7(@types/react@19.2.18)
+ openapi-typescript:
+ specifier: ^7.5.0
+ version: 7.13.0(typescript@5.9.3)
+ typescript:
+ specifier: ^5.7.0
+ version: 5.9.3
+
+packages:
+
+ '@babel/code-frame@7.29.7':
+ resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-identifier@7.29.7':
+ resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==}
+ engines: {node: '>=6.9.0'}
+
+ '@emnapi/runtime@1.11.3':
+ resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==}
+
+ '@img/colour@1.1.0':
+ resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
+ engines: {node: '>=18'}
+
+ '@img/sharp-darwin-arm64@0.35.4':
+ resolution: {integrity: sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-darwin-x64@0.35.4':
+ resolution: {integrity: sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==}
+ engines: {node: '>=20.9.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-freebsd-wasm32@0.35.4':
+ resolution: {integrity: sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==}
+ engines: {node: '>=20.9.0'}
+ os: [freebsd]
+
+ '@img/sharp-libvips-darwin-arm64@1.3.3':
+ resolution: {integrity: sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-x64@1.3.3':
+ resolution: {integrity: sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-linux-arm64@1.3.3':
+ resolution: {integrity: sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-libvips-linux-arm@1.3.3':
+ resolution: {integrity: sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==}
+ cpu: [arm]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-libvips-linux-ppc64@1.3.3':
+ resolution: {integrity: sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-libvips-linux-riscv64@1.3.3':
+ resolution: {integrity: sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-libvips-linux-s390x@1.3.3':
+ resolution: {integrity: sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==}
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-libvips-linux-x64@1.3.3':
+ resolution: {integrity: sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.3.3':
+ resolution: {integrity: sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@img/sharp-libvips-linuxmusl-x64@1.3.3':
+ resolution: {integrity: sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@img/sharp-linux-arm64@0.35.4':
+ resolution: {integrity: sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-linux-arm@0.35.4':
+ resolution: {integrity: sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-linux-ppc64@0.35.4':
+ resolution: {integrity: sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==}
+ engines: {node: '>=20.9.0'}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-linux-riscv64@0.35.4':
+ resolution: {integrity: sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==}
+ engines: {node: '>=20.9.0'}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-linux-s390x@0.35.4':
+ resolution: {integrity: sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==}
+ engines: {node: '>=20.9.0'}
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-linux-x64@0.35.4':
+ resolution: {integrity: sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==}
+ engines: {node: '>=20.9.0'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-linuxmusl-arm64@0.35.4':
+ resolution: {integrity: sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@img/sharp-linuxmusl-x64@0.35.4':
+ resolution: {integrity: sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==}
+ engines: {node: '>=20.9.0'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@img/sharp-wasm32@0.35.4':
+ resolution: {integrity: sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==}
+ engines: {node: '>=20.9.0'}
+
+ '@img/sharp-webcontainers-wasm32@0.35.4':
+ resolution: {integrity: sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==}
+ engines: {node: '>=20.9.0'}
+ cpu: [wasm32]
+
+ '@img/sharp-win32-arm64@0.35.4':
+ resolution: {integrity: sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@img/sharp-win32-ia32@0.35.4':
+ resolution: {integrity: sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==}
+ engines: {node: ^20.9.0}
+ cpu: [ia32]
+ os: [win32]
+
+ '@img/sharp-win32-x64@0.35.4':
+ resolution: {integrity: sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==}
+ engines: {node: '>=20.9.0'}
+ cpu: [x64]
+ os: [win32]
+
+ '@next/env@15.5.25':
+ resolution: {integrity: sha512-42h1lLr07vl4gawALP1hsgRZjHB1xYa58JfUfHwr0f7jG/zhPakh5GHkADHXOC9ZxUvlQFOPIrp7s6qX4DezPQ==}
+
+ '@next/swc-darwin-arm64@15.5.25':
+ resolution: {integrity: sha512-w+RR0v/QuApnWEjRGm1z6gcObKwGMb5YPA7V3bzBEVSBpMFUXprer0tS27UxjUcEnqbhL7Zuzohej79B6rYmBg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@next/swc-darwin-x64@15.5.25':
+ resolution: {integrity: sha512-QiGGBUSakt8S1H4Lt9Ehsh6Ja87axiBnQQgysOObvCbI7iUfJnRGntF1P64S4/ijuHFnSB8KLsEddkY3nN26uw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@next/swc-linux-arm64-gnu@15.5.25':
+ resolution: {integrity: sha512-ehLos/66zo0d/mJCU5u96a/VDcr01aaUrX0o/i16UdInxz8qPTCDSxGtjk/Lps1sIr1RJFdiX3hxc0fxJo+cPA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@next/swc-linux-arm64-musl@15.5.25':
+ resolution: {integrity: sha512-ZVMrqLiJ7DiChgmbkQwFtdhAnUkSH/4p7tB29QY+giATb0Q/XGHNRSKAb/B8XGDHRUaA67NepOW5W8u3ZRJBAA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@next/swc-linux-x64-gnu@15.5.25':
+ resolution: {integrity: sha512-UOewtDGkTMJTiODrEdeLZ50yGb59xCZSriNpXkfPMxRRgwDkGc7i8mLWqV5076wEdb+Ca/XN7MhJyM3CupyNyQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@next/swc-linux-x64-musl@15.5.25':
+ resolution: {integrity: sha512-UBHwA8AhkCZgtRfU1aJpunuAJe/6gZv6jDESQe4p5MjTb5V0YEeJBWCdNqx15Vj3x+5jmauRfeMJSjfQj9HGFQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@next/swc-win32-arm64-msvc@15.5.25':
+ resolution: {integrity: sha512-QcFcPRr16djk5IqK5+e8O80eZfgWzIvVBXfitIq0tQ/uc+eyfdoZ0NmKc0cnbIJyfVwREapKuG97YcxWA9gcpA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@next/swc-win32-x64-msvc@15.5.25':
+ resolution: {integrity: sha512-zREeykps3ndWr9egJgvJKqVkkDuaw6Zrrg23cYBos0ygydFkAWYU4+PaPVwXzP1eAYQJe53ShSK45iDM529BOg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@redocly/ajv@8.11.2':
+ resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==}
+
+ '@redocly/config@0.22.0':
+ resolution: {integrity: sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==}
+
+ '@redocly/openapi-core@1.34.19':
+ resolution: {integrity: sha512-o/0VgsBXgwcY1lyeqcVtSGdTQAPnVggo0fbFVPlxl5XVDKUcVH0OLRqt3CbkwByT5FU305E0iE0O7MzThjDblw==}
+ engines: {node: '>=18.17.0', npm: '>=9.5.0'}
+
+ '@swc/helpers@0.5.15':
+ resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
+
+ '@tanstack/query-core@5.103.1':
+ resolution: {integrity: sha512-rms8HqTGp6zA00dM+cUQ2eBcgzNJefuu5CAMB37i/6MiGT1zulPOytCFu2a0qjLqVR2n1jENPj9woqFQNuCzWA==}
+
+ '@tanstack/react-query@5.103.1':
+ resolution: {integrity: sha512-rmAPPApNEK17VXRJhtE1rja53n23VV5w30C0saCgJhhX+EpdUnVqMGV/s5nnzV43X75KTHKzuoiuxSzGTBIkag==}
+ peerDependencies:
+ react: ^18 || ^19
+
+ '@types/node@22.20.1':
+ resolution: {integrity: sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==}
+
+ '@types/react-dom@19.2.7':
+ resolution: {integrity: sha512-I8bPpDLcHBv1qiIiXDCy71Rt8eQDKJP0sMSWJphDdAcdqiJ1sGpZamavoEIRZmYzjia9LuEb2HlYdDpmoENpvQ==}
+ peerDependencies:
+ '@types/react': ^19.2.0
+
+ '@types/react@19.2.18':
+ resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==}
+
+ agent-base@7.1.4:
+ resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
+ engines: {node: '>= 14'}
+
+ ansi-colors@4.1.3:
+ resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
+ engines: {node: '>=6'}
+
+ argparse@2.0.1:
+ resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
+ balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+ brace-expansion@2.1.4:
+ resolution: {integrity: sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==}
+
+ caniuse-lite@1.0.30001810:
+ resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==}
+
+ change-case@5.4.4:
+ resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==}
+
+ client-only@0.0.1:
+ resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
+
+ colorette@1.4.0:
+ resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==}
+
+ csstype@3.2.3:
+ resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
+ debug@4.4.3:
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ detect-libc@2.1.2:
+ resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
+ engines: {node: '>=8'}
+
+ fast-deep-equal@3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+ https-proxy-agent@7.0.6:
+ resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
+ engines: {node: '>= 14'}
+
+ index-to-position@1.2.0:
+ resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==}
+ engines: {node: '>=18'}
+
+ js-levenshtein@1.1.6:
+ resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==}
+ engines: {node: '>=0.10.0'}
+
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ js-yaml@4.3.1:
+ resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==}
+ hasBin: true
+
+ json-schema-traverse@1.0.0:
+ resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
+
+ lucide-react@1.46.0:
+ resolution: {integrity: sha512-Bv+FZXgZPrxc/NCl1e7JJVQFLdiCxYgxNVhqoV7X0p6I8ADJo8DxBnK1auH0fZz4AmqOJ3jgneL4f1i8LJQRAA==}
+ peerDependencies:
+ react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ minimatch@5.1.9:
+ resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==}
+ engines: {node: '>=10'}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ nanoid@3.3.18:
+ resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ next@15.5.25:
+ resolution: {integrity: sha512-OMWNulIIqKM2ykvC2qMjIt0IoavB4UB2SCs4iXJ6z6847FvyH8jBmBWcvrF5iuhTu8Przh20Fo/aoszIdqx4PA==}
+ engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
+ hasBin: true
+ peerDependencies:
+ '@opentelemetry/api': ^1.1.0
+ '@playwright/test': ^1.51.1
+ babel-plugin-react-compiler: '*'
+ react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
+ react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
+ sass: ^1.3.0
+ peerDependenciesMeta:
+ '@opentelemetry/api':
+ optional: true
+ '@playwright/test':
+ optional: true
+ babel-plugin-react-compiler:
+ optional: true
+ sass:
+ optional: true
+
+ openapi-typescript@7.13.0:
+ resolution: {integrity: sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==}
+ hasBin: true
+ peerDependencies:
+ typescript: ^5.x
+
+ parse-json@8.3.0:
+ resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==}
+ engines: {node: '>=18'}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ pluralize@8.0.0:
+ resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
+ engines: {node: '>=4'}
+
+ postcss@8.4.31:
+ resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ react-dom@19.2.8:
+ resolution: {integrity: sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==}
+ peerDependencies:
+ react: ^19.2.8
+
+ react@19.2.8:
+ resolution: {integrity: sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==}
+ engines: {node: '>=0.10.0'}
+
+ require-from-string@2.0.2:
+ resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
+ engines: {node: '>=0.10.0'}
+
+ scheduler@0.27.0:
+ resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
+
+ semver@7.8.5:
+ resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ sharp@0.35.4:
+ resolution: {integrity: sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==}
+ engines: {node: '>=20.9.0'}
+ peerDependencies:
+ '@types/node': '*'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ styled-jsx@5.1.6:
+ resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==}
+ engines: {node: '>= 12.0.0'}
+ peerDependencies:
+ '@babel/core': '*'
+ babel-plugin-macros: '*'
+ react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0'
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ babel-plugin-macros:
+ optional: true
+
+ supports-color@10.2.2:
+ resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==}
+ engines: {node: '>=18'}
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ type-fest@4.41.0:
+ resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
+ engines: {node: '>=16'}
+
+ typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ undici-types@6.21.0:
+ resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+
+ uri-js-replace@1.0.1:
+ resolution: {integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==}
+
+ yaml-ast-parser@0.0.43:
+ resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==}
+
+ yargs-parser@21.1.1:
+ resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
+ engines: {node: '>=12'}
+
+snapshots:
+
+ '@babel/code-frame@7.29.7':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.29.7
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/helper-validator-identifier@7.29.7': {}
+
+ '@emnapi/runtime@1.11.3':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@img/colour@1.1.0':
+ optional: true
+
+ '@img/sharp-darwin-arm64@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-arm64': 1.3.3
+ optional: true
+
+ '@img/sharp-darwin-x64@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-x64': 1.3.3
+ optional: true
+
+ '@img/sharp-freebsd-wasm32@0.35.4':
+ dependencies:
+ '@img/sharp-wasm32': 0.35.4
+ optional: true
+
+ '@img/sharp-libvips-darwin-arm64@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-darwin-x64@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm64@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-ppc64@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-riscv64@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-s390x@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-x64@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-x64@1.3.3':
+ optional: true
+
+ '@img/sharp-linux-arm64@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm64': 1.3.3
+ optional: true
+
+ '@img/sharp-linux-arm@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm': 1.3.3
+ optional: true
+
+ '@img/sharp-linux-ppc64@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-ppc64': 1.3.3
+ optional: true
+
+ '@img/sharp-linux-riscv64@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-riscv64': 1.3.3
+ optional: true
+
+ '@img/sharp-linux-s390x@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-s390x': 1.3.3
+ optional: true
+
+ '@img/sharp-linux-x64@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-x64': 1.3.3
+ optional: true
+
+ '@img/sharp-linuxmusl-arm64@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-arm64': 1.3.3
+ optional: true
+
+ '@img/sharp-linuxmusl-x64@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-x64': 1.3.3
+ optional: true
+
+ '@img/sharp-wasm32@0.35.4':
+ dependencies:
+ '@emnapi/runtime': 1.11.3
+ optional: true
+
+ '@img/sharp-webcontainers-wasm32@0.35.4':
+ dependencies:
+ '@img/sharp-wasm32': 0.35.4
+ optional: true
+
+ '@img/sharp-win32-arm64@0.35.4':
+ optional: true
+
+ '@img/sharp-win32-ia32@0.35.4':
+ optional: true
+
+ '@img/sharp-win32-x64@0.35.4':
+ optional: true
+
+ '@next/env@15.5.25': {}
+
+ '@next/swc-darwin-arm64@15.5.25':
+ optional: true
+
+ '@next/swc-darwin-x64@15.5.25':
+ optional: true
+
+ '@next/swc-linux-arm64-gnu@15.5.25':
+ optional: true
+
+ '@next/swc-linux-arm64-musl@15.5.25':
+ optional: true
+
+ '@next/swc-linux-x64-gnu@15.5.25':
+ optional: true
+
+ '@next/swc-linux-x64-musl@15.5.25':
+ optional: true
+
+ '@next/swc-win32-arm64-msvc@15.5.25':
+ optional: true
+
+ '@next/swc-win32-x64-msvc@15.5.25':
+ optional: true
+
+ '@redocly/ajv@8.11.2':
+ dependencies:
+ fast-deep-equal: 3.1.3
+ json-schema-traverse: 1.0.0
+ require-from-string: 2.0.2
+ uri-js-replace: 1.0.1
+
+ '@redocly/config@0.22.0': {}
+
+ '@redocly/openapi-core@1.34.19(supports-color@10.2.2)':
+ dependencies:
+ '@redocly/ajv': 8.11.2
+ '@redocly/config': 0.22.0
+ colorette: 1.4.0
+ https-proxy-agent: 7.0.6(supports-color@10.2.2)
+ js-levenshtein: 1.1.6
+ js-yaml: 4.3.1
+ minimatch: 5.1.9
+ pluralize: 8.0.0
+ yaml-ast-parser: 0.0.43
+ transitivePeerDependencies:
+ - supports-color
+
+ '@swc/helpers@0.5.15':
+ dependencies:
+ tslib: 2.8.1
+
+ '@tanstack/query-core@5.103.1': {}
+
+ '@tanstack/react-query@5.103.1(react@19.2.8)':
+ dependencies:
+ '@tanstack/query-core': 5.103.1
+ react: 19.2.8
+
+ '@types/node@22.20.1':
+ dependencies:
+ undici-types: 6.21.0
+
+ '@types/react-dom@19.2.7(@types/react@19.2.18)':
+ dependencies:
+ '@types/react': 19.2.18
+
+ '@types/react@19.2.18':
+ dependencies:
+ csstype: 3.2.3
+
+ agent-base@7.1.4: {}
+
+ ansi-colors@4.1.3: {}
+
+ argparse@2.0.1: {}
+
+ balanced-match@1.0.2: {}
+
+ brace-expansion@2.1.4:
+ dependencies:
+ balanced-match: 1.0.2
+
+ caniuse-lite@1.0.30001810: {}
+
+ change-case@5.4.4: {}
+
+ client-only@0.0.1: {}
+
+ colorette@1.4.0: {}
+
+ csstype@3.2.3: {}
+
+ debug@4.4.3(supports-color@10.2.2):
+ dependencies:
+ ms: 2.1.3
+ optionalDependencies:
+ supports-color: 10.2.2
+
+ detect-libc@2.1.2:
+ optional: true
+
+ fast-deep-equal@3.1.3: {}
+
+ https-proxy-agent@7.0.6(supports-color@10.2.2):
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3(supports-color@10.2.2)
+ transitivePeerDependencies:
+ - supports-color
+
+ index-to-position@1.2.0: {}
+
+ js-levenshtein@1.1.6: {}
+
+ js-tokens@4.0.0: {}
+
+ js-yaml@4.3.1:
+ dependencies:
+ argparse: 2.0.1
+
+ json-schema-traverse@1.0.0: {}
+
+ lucide-react@1.46.0(react@19.2.8):
+ dependencies:
+ react: 19.2.8
+
+ minimatch@5.1.9:
+ dependencies:
+ brace-expansion: 2.1.4
+
+ ms@2.1.3: {}
+
+ nanoid@3.3.18: {}
+
+ next@15.5.25(@types/node@22.20.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8):
+ dependencies:
+ '@next/env': 15.5.25
+ '@swc/helpers': 0.5.15
+ caniuse-lite: 1.0.30001810
+ postcss: 8.4.31
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ styled-jsx: 5.1.6(react@19.2.8)
+ optionalDependencies:
+ '@next/swc-darwin-arm64': 15.5.25
+ '@next/swc-darwin-x64': 15.5.25
+ '@next/swc-linux-arm64-gnu': 15.5.25
+ '@next/swc-linux-arm64-musl': 15.5.25
+ '@next/swc-linux-x64-gnu': 15.5.25
+ '@next/swc-linux-x64-musl': 15.5.25
+ '@next/swc-win32-arm64-msvc': 15.5.25
+ '@next/swc-win32-x64-msvc': 15.5.25
+ sharp: 0.35.4(@types/node@22.20.1)
+ transitivePeerDependencies:
+ - '@babel/core'
+ - '@types/node'
+ - babel-plugin-macros
+
+ openapi-typescript@7.13.0(typescript@5.9.3):
+ dependencies:
+ '@redocly/openapi-core': 1.34.19(supports-color@10.2.2)
+ ansi-colors: 4.1.3
+ change-case: 5.4.4
+ parse-json: 8.3.0
+ supports-color: 10.2.2
+ typescript: 5.9.3
+ yargs-parser: 21.1.1
+
+ parse-json@8.3.0:
+ dependencies:
+ '@babel/code-frame': 7.29.7
+ index-to-position: 1.2.0
+ type-fest: 4.41.0
+
+ picocolors@1.1.1: {}
+
+ pluralize@8.0.0: {}
+
+ postcss@8.4.31:
+ dependencies:
+ nanoid: 3.3.18
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ react-dom@19.2.8(react@19.2.8):
+ dependencies:
+ react: 19.2.8
+ scheduler: 0.27.0
+
+ react@19.2.8: {}
+
+ require-from-string@2.0.2: {}
+
+ scheduler@0.27.0: {}
+
+ semver@7.8.5:
+ optional: true
+
+ sharp@0.35.4(@types/node@22.20.1):
+ dependencies:
+ '@img/colour': 1.1.0
+ detect-libc: 2.1.2
+ semver: 7.8.5
+ optionalDependencies:
+ '@img/sharp-darwin-arm64': 0.35.4
+ '@img/sharp-darwin-x64': 0.35.4
+ '@img/sharp-freebsd-wasm32': 0.35.4
+ '@img/sharp-libvips-darwin-arm64': 1.3.3
+ '@img/sharp-libvips-darwin-x64': 1.3.3
+ '@img/sharp-libvips-linux-arm': 1.3.3
+ '@img/sharp-libvips-linux-arm64': 1.3.3
+ '@img/sharp-libvips-linux-ppc64': 1.3.3
+ '@img/sharp-libvips-linux-riscv64': 1.3.3
+ '@img/sharp-libvips-linux-s390x': 1.3.3
+ '@img/sharp-libvips-linux-x64': 1.3.3
+ '@img/sharp-libvips-linuxmusl-arm64': 1.3.3
+ '@img/sharp-libvips-linuxmusl-x64': 1.3.3
+ '@img/sharp-linux-arm': 0.35.4
+ '@img/sharp-linux-arm64': 0.35.4
+ '@img/sharp-linux-ppc64': 0.35.4
+ '@img/sharp-linux-riscv64': 0.35.4
+ '@img/sharp-linux-s390x': 0.35.4
+ '@img/sharp-linux-x64': 0.35.4
+ '@img/sharp-linuxmusl-arm64': 0.35.4
+ '@img/sharp-linuxmusl-x64': 0.35.4
+ '@img/sharp-webcontainers-wasm32': 0.35.4
+ '@img/sharp-win32-arm64': 0.35.4
+ '@img/sharp-win32-ia32': 0.35.4
+ '@img/sharp-win32-x64': 0.35.4
+ '@types/node': 22.20.1
+ optional: true
+
+ source-map-js@1.2.1: {}
+
+ styled-jsx@5.1.6(react@19.2.8):
+ dependencies:
+ client-only: 0.0.1
+ react: 19.2.8
+
+ supports-color@10.2.2: {}
+
+ tslib@2.8.1: {}
+
+ type-fest@4.41.0: {}
+
+ typescript@5.9.3: {}
+
+ undici-types@6.21.0: {}
+
+ uri-js-replace@1.0.1: {}
+
+ yaml-ast-parser@0.0.43: {}
+
+ yargs-parser@21.1.1: {}
diff --git a/frontend/src/app/(app)/agent-workspace/page.tsx b/frontend/src/app/(app)/agent-workspace/page.tsx
new file mode 100644
index 0000000..89dcd5b
--- /dev/null
+++ b/frontend/src/app/(app)/agent-workspace/page.tsx
@@ -0,0 +1,14 @@
+import { Topbar } from "@/components/shell/Topbar";
+import { Crumbs } from "@/components/ui";
+import { RunsPanel } from "@/components/panels";
+
+export default function Page() {
+ return (
+ <>
+ } />
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/app/(app)/avatar/page.tsx b/frontend/src/app/(app)/avatar/page.tsx
new file mode 100644
index 0000000..795d9f4
--- /dev/null
+++ b/frontend/src/app/(app)/avatar/page.tsx
@@ -0,0 +1,14 @@
+import { Topbar } from "@/components/shell/Topbar";
+import { Crumbs } from "@/components/ui";
+import { AvatarPanel } from "@/components/panels";
+
+export default function Page() {
+ return (
+ <>
+ } />
+
+ >
+ );
+}
diff --git a/frontend/src/app/(app)/characters/page.tsx b/frontend/src/app/(app)/characters/page.tsx
new file mode 100644
index 0000000..b17438c
--- /dev/null
+++ b/frontend/src/app/(app)/characters/page.tsx
@@ -0,0 +1,14 @@
+import { Topbar } from "@/components/shell/Topbar";
+import { Crumbs } from "@/components/ui";
+import { CharactersPagePanel } from "@/components/characters/CharactersPagePanel";
+
+export default function Page() {
+ return (
+ <>
+ } />
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/app/(app)/collaboration/page.tsx b/frontend/src/app/(app)/collaboration/page.tsx
new file mode 100644
index 0000000..f933f47
--- /dev/null
+++ b/frontend/src/app/(app)/collaboration/page.tsx
@@ -0,0 +1,14 @@
+import { Topbar } from "@/components/shell/Topbar";
+import { Crumbs } from "@/components/ui";
+import { CollaborationPanel } from "@/components/collaboration/CollaborationPanel";
+
+export default function Page() {
+ return (
+ <>
+ } />
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/app/(app)/community/page.tsx b/frontend/src/app/(app)/community/page.tsx
new file mode 100644
index 0000000..8a4c0d5
--- /dev/null
+++ b/frontend/src/app/(app)/community/page.tsx
@@ -0,0 +1,14 @@
+import { Topbar } from "@/components/shell/Topbar";
+import { Crumbs } from "@/components/ui";
+import { CommunityPanel } from "@/components/community/CommunityPanel";
+
+export default function Page() {
+ return (
+ <>
+ } />
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/app/(app)/conversation/page.tsx b/frontend/src/app/(app)/conversation/page.tsx
new file mode 100644
index 0000000..f397821
--- /dev/null
+++ b/frontend/src/app/(app)/conversation/page.tsx
@@ -0,0 +1,22 @@
+"use client";
+
+import { ThemeButton } from "@/lib/theme";
+import { ConversationPanel } from "@/components/conversation/ConversationPanel";
+
+export default function Page() {
+ return (
+ <>
+
+
+ 镜树
+ /
+ 分支对话
+
+
+ 真实接口 · 联调中
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/app/(app)/devices/page.tsx b/frontend/src/app/(app)/devices/page.tsx
new file mode 100644
index 0000000..f17ba3b
--- /dev/null
+++ b/frontend/src/app/(app)/devices/page.tsx
@@ -0,0 +1,14 @@
+import { Topbar } from "@/components/shell/Topbar";
+import { Crumbs } from "@/components/ui";
+import { DevicesPanel } from "@/components/panels";
+
+export default function Page() {
+ return (
+ <>
+ } />
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/app/(app)/governance/page.tsx b/frontend/src/app/(app)/governance/page.tsx
new file mode 100644
index 0000000..80cf8a3
--- /dev/null
+++ b/frontend/src/app/(app)/governance/page.tsx
@@ -0,0 +1,14 @@
+import { Topbar } from "@/components/shell/Topbar";
+import { Crumbs } from "@/components/ui";
+import { GovernancePanel } from "@/components/panels";
+
+export default function Page() {
+ return (
+ <>
+ } />
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/app/(app)/home/page.tsx b/frontend/src/app/(app)/home/page.tsx
new file mode 100644
index 0000000..bfc3334
--- /dev/null
+++ b/frontend/src/app/(app)/home/page.tsx
@@ -0,0 +1,14 @@
+import { Topbar } from "@/components/shell/Topbar";
+import { Crumbs } from "@/components/ui";
+import { HomePanel } from "@/components/panels";
+
+export default function Page() {
+ return (
+ <>
+ } />
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/app/(app)/layout.tsx b/frontend/src/app/(app)/layout.tsx
new file mode 100644
index 0000000..95b5b8f
--- /dev/null
+++ b/frontend/src/app/(app)/layout.tsx
@@ -0,0 +1,6 @@
+import { AppShell } from "@/components/shell/AppShell";
+
+/** 用户侧 14 域 + 设计说明共用应用外壳 */
+export default function AppLayout({ children }: { children: React.ReactNode }) {
+ return {children} ;
+}
diff --git a/frontend/src/app/(app)/marketplace/page.tsx b/frontend/src/app/(app)/marketplace/page.tsx
new file mode 100644
index 0000000..d672a2f
--- /dev/null
+++ b/frontend/src/app/(app)/marketplace/page.tsx
@@ -0,0 +1,14 @@
+import { Topbar } from "@/components/shell/Topbar";
+import { Crumbs } from "@/components/ui";
+import { MarketplacePanel } from "@/components/marketplace/MarketplacePanel";
+
+export default function Page() {
+ return (
+ <>
+ } />
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/app/(app)/memory/page.tsx b/frontend/src/app/(app)/memory/page.tsx
new file mode 100644
index 0000000..c335efe
--- /dev/null
+++ b/frontend/src/app/(app)/memory/page.tsx
@@ -0,0 +1,14 @@
+import { Topbar } from "@/components/shell/Topbar";
+import { Crumbs } from "@/components/ui";
+import { MemoryPanel } from "@/components/memory/MemoryPanel";
+
+export default function Page() {
+ return (
+ <>
+ } />
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/app/(app)/model-gateway/page.tsx b/frontend/src/app/(app)/model-gateway/page.tsx
new file mode 100644
index 0000000..06d641a
--- /dev/null
+++ b/frontend/src/app/(app)/model-gateway/page.tsx
@@ -0,0 +1,14 @@
+import { Topbar } from "@/components/shell/Topbar";
+import { Crumbs } from "@/components/ui";
+import { GatewayPanel } from "@/components/panels";
+
+export default function Page() {
+ return (
+ <>
+ } />
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/app/(app)/page.tsx b/frontend/src/app/(app)/page.tsx
new file mode 100644
index 0000000..779985b
--- /dev/null
+++ b/frontend/src/app/(app)/page.tsx
@@ -0,0 +1,187 @@
+import { Topbar } from "@/components/shell/Topbar";
+import { Crumbs } from "@/components/ui";
+
+
+export default function Page() {
+ return (
+ <>
+ } />
+
+
+
+
魂镜 原型 v0.2
+
魂镜 Web 界面静态 HTML 原型(v0.1 第一批方向已确认,v0.2 增补第二批共 15 个界面),供审查视觉方向、布局结构与文化语言的界面落地方式。全部确认后按此实现为 React 组件;未在本文档登记的视觉细节以本页令牌为准。
+
+
+
+ 原型页面
+ 第一批(已确认):镜庭、镜生、镜树;第二批:息壤、形仪、天工阁、神识阁、百工坊、雅集、同契台、云市、器谱、司命台、设置,及运营者/自托管使用的管理面与认证页。全站链接已疏通:跨页指向对应页面、页内为锚点;logo 单击回本目录,350ms 内连点 5 次触发「镜启」彩蛋(承接 main 分支旧版设计);侧栏可折叠并记忆偏好。
+
+
+
+
+ 设计立场
+ 四条约束所有页面的决定,审查时优先核对这四条是否成立。
+
+
一 · 双层文字系统 —— 文化名用衬线宋体,直白解释用无衬线小字,两种字体本身就是"文化名 · 直白解释"两层结构的可视化;风险、金额、权限、删除等后果始终以直白语言为主,文化名只作辅助。
+
二 · 朱砂只属于朱批 —— 朱砂色是"用户确认决定"的专用语义色:请命卡的允许按钮、危险动作入口使用它;普通操作一律铜镜青。颜色即权限语言。
+
三 · 墨与月白 —— 深色为墨(青调黑,非纯黑),浅色为月白(青灰白,非暖米);铜镜青为主色,取铜镜铜绿之意,不用蓝紫系默认色。
+
四 · 状态永远三段式 —— 状态徽章固定为「文化状态 · 直白状态 + 稳定工程值」,例如「运转 · 运行中 running 」;错误与交易状态绝不只显示文化名。
+
+
+
+
+ 色彩令牌
+ 右下角可切换主题;以下色板随主题联动。转 React 时这些令牌进入 Tailwind 配置 / CSS 变量。
+
+
+
+
+ 字体规范
+ 不引入外部字体文件,使用系统中文字体栈;转 React 时同栈迁移。
+
+
文化名 · 宋体衬线 镜树 · 分支对话
+
界面正文 · 无衬线 正文 14px / 行高 1.65,辅助文字 12.5px,元信息 11.5px
+
稳定值 · 等宽 running · m_01h8xq… · block_id: blk_7f3a
+
+
+
+
+ 状态徽章全集
+ 对应 docs/product/cultural-language.md 第 10 节;徽章三段:文化状态 · 直白状态 · 稳定值。
+
+
+ 徽章 稳定值 使用场景
+
+ 待启 · 排队中 queued queued Run / 媒体任务排队
+ 运转 · 运行中 running running Run 执行中
+ 请命中 · 等待确认 waiting_approval waiting_approval 等待朱批
+ 已成 · 已完成 completed completed Run / 任务完成
+ 受阻 · 失败 failed failed 失败(附原因与重试)
+ 已止 · 已取消 cancelled cancelled 用户取消
+ 守一 · 安静待命 idle idle 角色无运行
+ 整备 · 正在处理 processing processing 媒体 / 资源处理
+ 待审 · 等待审核 pending_review pending_review 社区 / 市场审核
+ 已刊 · 已发布 published published 社区内容
+
+
+
+
+
+
+ 组件样例
+ 按钮语义:铜镜青为主操作,朱砂仅用于朱批(用户确认决定)与危险操作,描边为次操作。
+
+
+
+ 开始对话
+ 允许(朱批)
+ 查看谱系
+ 取消
+ 删除角色数据
+ 续生
+
+
+ 请命请求你的确认
+ 息壤角色记忆
+ 幻境临时试演 · 不入正史
+
+
+
+
+
python reading_progress.py
+
# 汇总共读接龙进度(示意)
+from pathlib import Path
+
+def summarize (records: list [dict ]) -> dict :
+ done = [r for r in records if r["finished" ]]
+return {'{'}
+ "total" : len (records),
+ "finished" : len (done),
+ "pending" : [r["name" ] for r in records if not r["finished" ]],
+ {'}'}
+
+
+
+
注意力权重 = softmax( QKT / √dk ) · V
+ fallback:注意力权重等于 Q、K 转置乘积经缩放与归一化后对 V 的加权求和
+
+
+
+
+
+ 审查重点
+ 第一批(色彩、字体、镜树/镜生/镜庭结构)已确认;本轮重点核对第二批页面,反馈时按页名 + 位置描述即可。
+
+
1. 息壤:候选审阅的「归真入忆 / 正影纠正 / 忽略」操作,以及大周天来源、重忆提示的呈现是否可理解。
+
2. 天工阁:工筹步骤、预算、分工使、轨迹的信息层级是否符合你对 Run 治理的预期。
+
3. 神识阁 / 百工坊:路由继承链与「升级新增权限需重新确认」的展示是否清晰,朱批语义是否仍然专一。
+
4. 云市:金额、分润、退契规则是否足够直白(文化名没有掩盖交易后果)。
+
5. 器谱 / 司命台 / 设置:直白权限句、双重授权、私箧与删除等高风险项的措辞与视觉重量。
+
6. 雅集 / 同契台:社区帖卡与协作审稿流的信息密度是否合适。
+
+
+
+
+ 旧版传承与候选
+ 已系统挖掘 main 分支旧版 25 页设计原型(铜镜鉴心体系),按「取手法、不照搬旧概念名」原则融入——六爻 / 九枢 / 三魂等概念 1.0 已由小周天、灵台六模块、内驱力等替代,仅传承其可视化手法。
+
+
+ 旧版想法 来源页 状态
+
+ 鎏金发丝线三档令牌 tokens.css ✓ 已融入全站(分组标题 / 金标竖条 / 品牌外环 / 时间线)
+ logo 五连击「镜启」诗签彩蛋 _theme.js ✓ 已融入(修好首次单击即跳转的缺陷)
+ 铜镜登录页(铭文环 / 铜面噪点 / 扫描亮带) copper-mirror-login ✓ 已融入登录 / 注册页
+ 极光铜晕底 + 卡片 hover 光扫 _base.css / rune-engine ✓ 已融入全站(极淡,不干扰阅读)
+ 日晷时辰盘 + 节气 + 相伴日文案 time ✓ 已融入镜庭 hero(巳时 · 白露 · 第 918 日)
+ 朱批卡风险分级(RISK L2) chat-redesign ✓ 已融入镜树请命卡
+ 「呈镜」发送按钮命名(旧版已确立血脉) chat-redesign ✓ 已融入镜树输入区(aria 仍读「发送」)
+ 运行阶段爻象三态(done / active / pending) chat / evolution ✓ 已融入天工阁工筹(active 步骤鎏金光晕)
+ 河图置信度图谱(置信→线宽,假设→朱漆虚线待验) world ✓ 已融入息壤玄络(待验边 + 标注)
+ 息壤浮现(本次回应用了哪些记忆) chat / copper-v2 ✓ 对应 1.0 上下文镜(镜树右栏,同源 ContextSnapshot)
+ 九枢星盘「活的仪器」+ 铜镜四态健康词汇 admin / copper-v2 ✓ 已复刻为灵台六枢星盘(太一居中 + 五枢环绕 + 呼吸刻度 / 慢旋 / 告警闪烁)+ 镜态徽章(管理面)
+ 五行生克健康环(系统健康=关系动力学) wuxing ✓ 已复刻为相生 / 相克指标对告警(管理面星盘卡;环状图 React 阶段评估)
+ 眉批 depth 标尺注入预览 margin-notes 候选 —— 上下文镜深度维度(React 阶段与 Context Manifest 联动)
+ 好奇萤火(亮度=期望值)与探索天平 drive ✓ 萤火已融入内驱力维度(亮度=维度值);探索天平待天工阁预算增强时评估
+ 周天四镜阶段链(评 / 演 / 候 / 藏 四面小铜镜) evolution ✓ 已复刻为大周天四镜:回 / 照 / 议 / 呈(镜生页,ADR-023 四阶段)
+ 会话列表「今日 / 本周 / 更早」分组 + 彩点状态 chat-redesign ✓ 已融入镜树会话列表(活跃铜绿 / 幻境秋香 / 待朱批朱砂)
+ 「深虑 / 博闻」输入控制药丸 + 用户消息鎏金右边线 chat-redesign / copper-v2 ✓ 已融入镜树输入区与用户气泡
+ 起居注时间河「卷」章分割 admin ✓ 已融入管理面审计快照
+ 太极阴阳平衡 gauge(三态) cosmos ✓ 已复刻为朱批治理阴阳平衡(司命台,阳亢 / 平衡 / 阴盛)
+ 角色 card-banner 渐变横幅 character-detail ✓ 已融入镜生详情页
+ 思考块折叠卡(复刻 chat-redesign) chat-redesign ✓ 已融入(用户确认):思考摘要可折叠 + 深虑强度(低/中/高)——契约候选 :正式实现前需在 content-blocks 契约登记 thinking 块类型与流式语义;摘要仅会话内可见,不入长期记录与诊断导出
+
+
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/app/(app)/plugins/page.tsx b/frontend/src/app/(app)/plugins/page.tsx
new file mode 100644
index 0000000..3beb15f
--- /dev/null
+++ b/frontend/src/app/(app)/plugins/page.tsx
@@ -0,0 +1,14 @@
+import { Topbar } from "@/components/shell/Topbar";
+import { Crumbs } from "@/components/ui";
+import { PluginsPanel } from "@/components/plugins/PluginsPanel";
+
+export default function Page() {
+ return (
+ <>
+ } />
+
+ >
+ );
+}
diff --git a/frontend/src/app/(app)/settings/page.tsx b/frontend/src/app/(app)/settings/page.tsx
new file mode 100644
index 0000000..aefd5d7
--- /dev/null
+++ b/frontend/src/app/(app)/settings/page.tsx
@@ -0,0 +1,14 @@
+import { Topbar } from "@/components/shell/Topbar";
+import { Crumbs } from "@/components/ui";
+import { SettingsPanel } from "@/components/panels";
+
+export default function Page() {
+ return (
+ <>
+ } />
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/app/admin/layout.tsx b/frontend/src/app/admin/layout.tsx
new file mode 100644
index 0000000..7499ad2
--- /dev/null
+++ b/frontend/src/app/admin/layout.tsx
@@ -0,0 +1,6 @@
+import { AdminShell } from "@/components/shell/AdminShell";
+
+/** 管理面独立外壳(运营 / 部署 / 观测) */
+export default function AdminLayout({ children }: { children: React.ReactNode }) {
+ return {children} ;
+}
diff --git a/frontend/src/app/admin/page.tsx b/frontend/src/app/admin/page.tsx
new file mode 100644
index 0000000..1b8e3e9
--- /dev/null
+++ b/frontend/src/app/admin/page.tsx
@@ -0,0 +1,42 @@
+"use client";
+
+import { Topbar } from "@/components/shell/Topbar";
+import { Crumbs } from "@/components/ui";
+import { AdminPanel } from "@/components/admin/AdminPanel";
+import { readSession } from "@/lib/api";
+import { useEffect, useState } from "react";
+
+export default function Page() {
+ const [session, setSession] = useState<{ role: string; email: string } | null>(null);
+ useEffect(() => {
+ // 会话身份含 localStorage,须在挂载后读取(避免 SSR/客户端不一致)
+ setSession(readSession());
+ }, []);
+ const isAdmin = session?.role === "admin";
+ return (
+ <>
+ }
+ extra={
+ session === null ? null : isAdmin ? (
+
+
+ 管理员 · {session.email || "dev"} role: admin
+
+ ) : (
+
+
+ 普通用户 role: user · 治理队列需管理员
+
+ )
+ }
+ />
+
+
+ 管理面边界:管理员不可读取私箧与用户明文内容;敏感操作需双人授权并全部写入审计(开发档单管理员,双人授权待运营流程接入)。
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css
new file mode 100644
index 0000000..3a2167d
--- /dev/null
+++ b/frontend/src/app/globals.css
@@ -0,0 +1,1710 @@
+/* ============================================================
+ 魂镜 Hunjing · 界面静态原型 v0.1
+ 设计语言:墨 / 月白双主题 · 铜镜青主色 · 朱砂朱批色 · 双层文字系统
+ ============================================================ */
+
+/* ---------- 设计令牌 ---------- */
+:root {
+ /* 墨色主题(默认) */
+ --bg: #14181c;
+ --bg-sunken: #0f1316;
+ --bg-raised: #1b2126;
+ --bg-overlay: #212930;
+ --line: #2b333b;
+ --line-soft: #232b31;
+ --text-1: #e9e7e2;
+ --text-2: #a7aeb4;
+ --text-3: #6d777f;
+ --accent: #58a99c; /* 铜镜青 */
+ --accent-strong: #74c4b6;
+ --accent-soft: rgba(88, 169, 156, 0.13);
+ --cinnabar: #c4574a; /* 朱砂 · 朱批专用 */
+ --cinnabar-strong: #d26a5d;
+ --cinnabar-soft: rgba(196, 87, 74, 0.14);
+ --ok: #8ab872; /* 已成 */
+ --warn: #c9a227; /* 整备 · 秋香 */
+ --err: #d26a5d; /* 受阻 */
+ --muted: #79838b; /* 守一 / 已止 */
+ --shadow: 0 10px 28px rgba(0, 0, 0, 0.38);
+ --shadow-soft: 0 4px 14px rgba(0, 0, 0, 0.25);
+
+ --font-ui: "PingFang SC", "HarmonyOS Sans SC", "Noto Sans SC", "Microsoft YaHei", system-ui, sans-serif;
+ --font-serif: "Songti SC", "Noto Serif SC", "STSong", "SimSun", serif;
+ --font-mono: "SF Mono", "JetBrains Mono", "Cascadia Code", Menlo, Consolas, monospace;
+
+ --radius: 10px;
+ --radius-sm: 7px;
+ --radius-lg: 14px;
+ --side-w: 224px;
+}
+
+[data-theme="light"] {
+ --bg: #f1f2ef;
+ --bg-sunken: #e6e8e4;
+ --bg-raised: #fafaf8;
+ --bg-overlay: #ffffff;
+ --line: #d6dad3;
+ --line-soft: #e2e5df;
+ --text-1: #262b2e;
+ --text-2: #5c6569;
+ --text-3: #8b9497;
+ --accent: #2f7a6f;
+ --accent-strong: #22615a;
+ --accent-soft: rgba(47, 122, 111, 0.1);
+ --cinnabar: #ac4632;
+ --cinnabar-strong: #963a28;
+ --cinnabar-soft: rgba(172, 70, 50, 0.1);
+ --ok: #4e7e3b;
+ --warn: #8f7414;
+ --err: #ac4632;
+ --muted: #848d90;
+ --shadow: 0 10px 28px rgba(42, 52, 47, 0.12);
+ --shadow-soft: 0 4px 14px rgba(42, 52, 47, 0.08);
+}
+
+/* ---------- 基础 ---------- */
+* { box-sizing: border-box; margin: 0; padding: 0; }
+
+html { height: 100%; }
+
+body {
+ min-height: 100%;
+ background: var(--bg);
+ color: var(--text-1);
+ font-family: var(--font-ui);
+ font-size: 14px;
+ line-height: 1.65;
+ -webkit-font-smoothing: antialiased;
+}
+
+a { color: var(--accent); text-decoration: none; }
+a:hover { color: var(--accent-strong); }
+
+:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+ border-radius: 4px;
+}
+
+::selection { background: var(--accent-soft); }
+
+@media (prefers-reduced-motion: reduce) {
+ *, *::before, *::after { animation: none !important; transition: none !important; }
+}
+
+/* ---------- 双层文字系统(签名) ----------
+ 文化名用衬线宋体;直白解释用无衬线小字。
+ 展示格式:文化名 · 直白解释 */
+.cn { font-family: var(--font-serif); letter-spacing: 0.02em; }
+.cn__plain {
+ font-family: var(--font-ui);
+ font-size: 0.72em;
+ color: var(--text-3);
+ margin-left: 0.4em;
+ letter-spacing: 0;
+}
+
+.mono { font-family: var(--font-mono); font-size: 0.92em; }
+
+/* ---------- 应用外壳 ---------- */
+.app {
+ display: grid;
+ grid-template-columns: var(--side-w) 1fr;
+ min-height: 100vh;
+}
+
+.sidebar {
+ background: var(--bg-sunken);
+ border-right: 1px solid var(--line-soft);
+ display: flex;
+ flex-direction: column;
+ position: sticky;
+ top: 0;
+ height: 100vh;
+ overflow-y: auto;
+}
+
+.brand {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 18px 18px 14px;
+}
+
+.brand__mark {
+ width: 34px;
+ height: 34px;
+ border-radius: 50%;
+ flex: none;
+ background:
+ radial-gradient(circle at 35% 30%, rgba(255, 255, 255, 0.28), transparent 42%),
+ conic-gradient(from 210deg, #3f6e68, #6fae9f 40%, #4a5e63 80%, #3f6e68);
+ border: 1px solid rgba(255, 255, 255, 0.16);
+ box-shadow: inset 0 0 0 3px rgba(0, 0, 0, 0.18);
+}
+
+.brand__name { font-family: var(--font-serif); font-size: 17px; font-weight: 600; line-height: 1.2; }
+.brand__sub { font-size: 11px; color: var(--text-3); }
+
+.side-nav { flex: 1; padding: 4px 10px 16px; }
+
+.nav-group { margin-top: 14px; }
+.nav-group__title {
+ font-size: 11px;
+ color: var(--text-3);
+ padding: 0 10px 5px;
+ letter-spacing: 0.08em;
+}
+
+.nav-item {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 7px 10px;
+ border-radius: var(--radius-sm);
+ color: var(--text-2);
+ position: relative;
+ margin-bottom: 1px;
+}
+
+.nav-item:hover { background: var(--bg-raised); color: var(--text-1); }
+
+.nav-item.is-active {
+ background: var(--accent-soft);
+ color: var(--accent-strong);
+}
+
+.nav-item.is-active::before {
+ content: "";
+ position: absolute;
+ left: -10px;
+ top: 6px;
+ bottom: 6px;
+ width: 3px;
+ border-radius: 2px;
+ background: var(--accent);
+}
+
+.nav-item svg { width: 17px; height: 17px; flex: none; opacity: 0.85; }
+.nav-item .cn { font-size: 14.5px; }
+.nav-item .nav-plain { font-size: 11.5px; color: var(--text-3); margin-left: auto; }
+.nav-item.is-active .nav-plain { color: inherit; opacity: 0.75; }
+
+.side-foot {
+ border-top: 1px solid var(--line-soft);
+ padding: 12px 10px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+/* ---------- 顶栏 ---------- */
+.main { display: flex; flex-direction: column; min-width: 0; }
+
+.topbar {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ padding: 0 22px;
+ height: 54px;
+ border-bottom: 1px solid var(--line-soft);
+ background: var(--bg);
+ position: sticky;
+ top: 0;
+ z-index: 20;
+}
+
+.crumbs { display: flex; align-items: center; gap: 8px; min-width: 0; color: var(--text-2); font-size: 13.5px; }
+.crumbs .cn { color: var(--text-1); font-size: 15px; }
+.crumbs__sep { color: var(--text-3); }
+.crumbs .here { color: var(--text-2); }
+
+.topbar__spacer { flex: 1; }
+
+.prototype-tag {
+ font-size: 11px;
+ color: var(--text-3);
+ border: 1px dashed var(--line);
+ border-radius: 999px;
+ padding: 2px 10px;
+ white-space: nowrap;
+}
+
+.icon-btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ border-radius: var(--radius-sm);
+ border: none;
+ background: transparent;
+ color: var(--text-2);
+ cursor: pointer;
+}
+.icon-btn:hover { background: var(--bg-raised); color: var(--text-1); }
+.icon-btn svg { width: 17px; height: 17px; }
+
+/* ---------- 通用组件 ---------- */
+.content { padding: 26px 30px 48px; max-width: 1160px; width: 100%; margin: 0 auto; }
+.content--wide { max-width: none; }
+
+.card {
+ background: var(--bg-raised);
+ border: 1px solid var(--line-soft);
+ border-radius: var(--radius);
+ padding: 18px 20px;
+}
+
+.card__head {
+ display: flex;
+ align-items: baseline;
+ flex-wrap: wrap;
+ row-gap: 5px;
+ gap: 8px;
+ margin-bottom: 12px;
+}
+.card__head .cn { font-size: 16px; font-weight: 600; }
+
+.btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 7px;
+ font-family: var(--font-ui);
+ font-size: 13.5px;
+ padding: 7px 16px;
+ border-radius: var(--radius-sm);
+ border: 1px solid transparent;
+ cursor: pointer;
+ transition: background 0.15s, border-color 0.15s;
+ white-space: nowrap;
+}
+.btn svg { width: 15px; height: 15px; }
+.btn--primary { background: var(--accent); color: #f4f7f5; }
+.btn--primary:hover { background: var(--accent-strong); color: #fff; }
+.btn--cinnabar { background: var(--cinnabar); color: #fbf3ef; }
+.btn--cinnabar:hover { background: var(--cinnabar-strong); color: #fff; }
+.btn--outline { background: transparent; border-color: var(--line); color: var(--text-1); }
+.btn--outline:hover { border-color: var(--accent); color: var(--accent-strong); }
+.btn--ghost { background: transparent; color: var(--text-2); }
+.btn--ghost:hover { background: var(--bg-raised); color: var(--text-1); }
+.btn--danger { background: transparent; border-color: var(--cinnabar); color: var(--cinnabar); }
+.btn--danger:hover { background: var(--cinnabar-soft); }
+.btn--sm { font-size: 12.5px; padding: 4px 11px; }
+
+/* 状态徽章:文化状态 · 直白状态 · 稳定值 */
+.badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 12px;
+ padding: 2px 10px;
+ border-radius: 999px;
+ border: 1px solid var(--line);
+ color: var(--text-2);
+ white-space: nowrap;
+}
+.badge__dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
+.badge .mono { font-size: 11px; color: var(--text-3); }
+.badge--ok { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 35%, transparent); }
+.badge--ok .badge__dot { background: var(--ok); }
+.badge--info { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 35%, transparent); }
+.badge--info .badge__dot { background: var(--accent); }
+.badge--info .badge__dot { animation: pulse 2.2s ease-in-out infinite; }
+.badge--warn { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 40%, transparent); }
+.badge--warn .badge__dot { background: var(--warn); }
+.badge--err { color: var(--err); border-color: color-mix(in srgb, var(--err) 40%, transparent); }
+.badge--err .badge__dot { background: var(--err); }
+.badge--muted .badge__dot { background: var(--muted); }
+
+@keyframes pulse { 50% { opacity: 0.35; } }
+
+/* 镜像头像:铜镜圆 + 衬线单字 */
+.avatar {
+ width: 38px;
+ height: 38px;
+ border-radius: 50%;
+ flex: none;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-family: var(--font-serif);
+ font-size: 17px;
+ color: #eef4f1;
+ background:
+ radial-gradient(circle at 32% 28%, rgba(255, 255, 255, 0.3), transparent 45%),
+ conic-gradient(from 210deg, #37514d, #5e9a8d 45%, #46565b 85%, #37514d);
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.14), inset 0 0 0 4px rgba(0, 0, 0, 0.16);
+}
+.avatar--user {
+ background:
+ radial-gradient(circle at 32% 28%, rgba(255, 255, 255, 0.3), transparent 45%),
+ conic-gradient(from 210deg, #5a4a44, #9a7b6c 45%, #5f5450 85%, #5a4a44);
+}
+.avatar--sm { width: 28px; height: 28px; font-size: 13px; }
+.avatar--lg { width: 64px; height: 64px; font-size: 28px; }
+
+/* 分段控件(界域切换等) */
+.seg {
+ display: inline-flex;
+ background: var(--bg-sunken);
+ border: 1px solid var(--line-soft);
+ border-radius: var(--radius-sm);
+ padding: 2px;
+ gap: 2px;
+}
+.seg__item {
+ border: none;
+ background: transparent;
+ font-family: var(--font-ui);
+ font-size: 12.5px;
+ color: var(--text-2);
+ padding: 3px 12px;
+ border-radius: 5px;
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+}
+.seg__item .cn { font-size: 13px; }
+.seg__item:hover { color: var(--text-1); }
+.seg__item.is-on { background: var(--bg-raised); color: var(--text-1); box-shadow: var(--shadow-soft); }
+.seg__item--sim.is-on { color: var(--warn); }
+.seg__realm-hint { font-size: 11px; color: var(--text-3); }
+
+/* 表单 */
+.input, .textarea {
+ width: 100%;
+ background: var(--bg-sunken);
+ border: 1px solid var(--line);
+ border-radius: var(--radius-sm);
+ color: var(--text-1);
+ font-family: var(--font-ui);
+ font-size: 13.5px;
+ padding: 8px 12px;
+}
+.input::placeholder, .textarea::placeholder { color: var(--text-3); }
+.input:focus, .textarea:focus { outline: none; border-color: var(--accent); }
+
+/* ---------- 首页(镜庭) ---------- */
+.hero { display: flex; align-items: flex-end; gap: 18px; margin-bottom: 26px; flex-wrap: wrap; }
+.hero h1 { font-family: var(--font-serif); font-size: 26px; font-weight: 600; }
+.hero__stats { display: flex; gap: 10px; margin-left: auto; flex-wrap: wrap; }
+.hero__stat {
+ display: flex;
+ flex-direction: column;
+ gap: 1px;
+ padding: 8px 16px;
+ border-radius: var(--radius);
+ background: var(--bg-raised);
+ border: 1px solid var(--line-soft);
+ min-width: 108px;
+}
+.hero__stat b { font-size: 19px; font-family: var(--font-serif); }
+.hero__stat span { font-size: 11.5px; color: var(--text-3); }
+
+.grid-cards {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
+ gap: 14px;
+}
+
+.char-card { display: flex; flex-direction: column; gap: 10px; padding: 18px; }
+.char-card__top { display: flex; align-items: center; gap: 12px; }
+.char-card__name { font-family: var(--font-serif); font-size: 17px; font-weight: 600; }
+.char-card__line { font-size: 12.5px; color: var(--text-2); line-height: 1.5; }
+.char-card__foot { display: flex; align-items: center; justify-content: space-between; margin-top: 2px; }
+.char-card__foot .badge { flex: none; }
+.char-card__time { font-size: 11.5px; color: var(--text-3); white-space: nowrap; flex: none; }
+
+.home-cols {
+ display: grid;
+ grid-template-columns: 1.5fr 1fr;
+ gap: 16px;
+ margin-top: 26px;
+ align-items: start;
+}
+
+.list-row {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 11px 4px;
+ border-bottom: 1px solid var(--line-soft);
+}
+.list-row:last-child { border-bottom: none; }
+.list-row__body { flex: 1; min-width: 0; }
+.list-row__title { font-size: 13.5px; color: var(--text-1); display: flex; align-items: center; gap: 8px; }
+.list-row__sub { font-size: 12px; color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.list-row__time { font-size: 11.5px; color: var(--text-3); white-space: nowrap; }
+
+.realm-dot {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+ font-size: 11px;
+ color: var(--text-3);
+}
+.realm-dot::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--muted); }
+.realm-dot--world::before { background: var(--accent); }
+.realm-dot--sim::before { background: var(--warn); }
+
+/* 进度条 */
+.progress { height: 5px; border-radius: 3px; background: var(--bg-sunken); overflow: hidden; }
+.progress__bar { height: 100%; border-radius: 3px; background: var(--accent); }
+.progress--warn .progress__bar { background: var(--warn); }
+
+/* ---------- 角色页(镜生) ---------- */
+.char-layout {
+ display: grid;
+ grid-template-columns: 300px 1fr;
+ gap: 20px;
+ align-items: start;
+}
+
+.char-list-item {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 11px 12px;
+ border-radius: var(--radius);
+ border: 1px solid transparent;
+ cursor: pointer;
+}
+.char-list-item:hover { background: var(--bg-raised); }
+.char-list-item.is-active { background: var(--bg-raised); border-color: var(--line); }
+.char-list-item__name { font-family: var(--font-serif); font-size: 15px; }
+.char-list-item__sub { font-size: 11.5px; color: var(--text-3); }
+
+.char-head {
+ display: flex;
+ gap: 18px;
+ align-items: flex-start;
+ margin-bottom: 20px;
+ flex-wrap: wrap;
+}
+.char-head__title { display: flex; flex-direction: column; gap: 6px; }
+.char-head h2 { font-family: var(--font-serif); font-size: 24px; font-weight: 600; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
+.char-head__motto { color: var(--text-2); font-size: 13.5px; }
+.char-head__actions { margin-left: auto; display: flex; gap: 8px; flex-wrap: wrap; }
+
+.section { margin-bottom: 20px; }
+.section__head {
+ display: flex;
+ align-items: baseline;
+ gap: 10px;
+ margin-bottom: 10px;
+}
+.section__head .cn { font-size: 16.5px; font-weight: 600; }
+.section__head .more { margin-left: auto; font-size: 12.5px; }
+
+/* 内驱力维度条 */
+.drive-row { display: grid; grid-template-columns: 72px 1fr 44px; align-items: center; gap: 14px; padding: 7px 0; }
+.drive-row__label { font-size: 13px; color: var(--text-2); }
+.drive-row__val { font-size: 12px; color: var(--text-3); text-align: right; }
+
+/* 谱系时间线 */
+.timeline { position: relative; padding-left: 20px; }
+.timeline::before {
+ content: "";
+ position: absolute;
+ left: 5px;
+ top: 8px;
+ bottom: 8px;
+ width: 1px;
+ background: var(--line);
+}
+.timeline__item { position: relative; padding: 0 0 16px 14px; }
+.timeline__item:last-child { padding-bottom: 0; }
+.timeline__item::before {
+ content: "";
+ position: absolute;
+ left: -20px;
+ top: 7px;
+ width: 11px;
+ height: 11px;
+ border-radius: 50%;
+ background: var(--bg);
+ border: 2px solid var(--accent);
+}
+.timeline__item--muted::before { border-color: var(--muted); }
+.timeline__date { font-size: 11.5px; color: var(--text-3); font-family: var(--font-mono); }
+.timeline__title { font-size: 13.5px; }
+.timeline__sub { font-size: 12.5px; color: var(--text-2); }
+
+/* 危险区 */
+.danger-zone { border-color: color-mix(in srgb, var(--cinnabar) 30%, transparent); }
+.danger-zone .card__head .cn { color: var(--cinnabar); }
+
+/* ---------- 对话页(镜树) ---------- */
+.conv-layout {
+ display: grid;
+ grid-template-columns: 268px 1fr 320px;
+ height: calc(100vh - 54px);
+ min-height: 0;
+}
+
+.conv-side {
+ border-right: 1px solid var(--line-soft);
+ overflow-y: auto;
+ padding: 14px 12px;
+ background: var(--bg);
+}
+
+.conv-side__head { display: flex; align-items: center; gap: 10px; padding: 4px 6px 14px; }
+.conv-side__head .cn { font-size: 15px; font-weight: 600; }
+
+.conv-item {
+ display: block;
+ padding: 10px 12px;
+ border-radius: var(--radius-sm);
+ color: var(--text-2);
+ margin-bottom: 2px;
+}
+.conv-item:hover { background: var(--bg-raised); color: var(--text-1); }
+.conv-item.is-active { background: var(--accent-soft); color: var(--text-1); }
+.conv-item__title { display: flex; align-items: center; gap: 8px; font-size: 13.5px; }
+.conv-item__sub { font-size: 12px; color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-top: 1px; }
+
+.conv-main {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+ overflow: hidden;
+}
+.conv-stream { flex: 1; overflow-y: auto; padding: 26px 0 12px; }
+.conv-stream__inner { max-width: 780px; margin: 0 auto; padding: 0 26px; }
+
+/* 界域提示条 */
+.realm-banner {
+ display: flex;
+ align-items: center;
+ gap: 9px;
+ font-size: 12.5px;
+ color: var(--text-2);
+ background: var(--accent-soft);
+ border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
+ border-radius: var(--radius-sm);
+ padding: 7px 14px;
+ margin-bottom: 22px;
+}
+.realm-banner--sim {
+ color: var(--warn);
+ background: color-mix(in srgb, var(--warn) 9%, transparent);
+ border-color: color-mix(in srgb, var(--warn) 32%, transparent);
+}
+.realm-banner svg { width: 15px; height: 15px; flex: none; }
+.realm-banner::before {
+ content: "";
+ width: 7px; height: 7px;
+ border-radius: 50%;
+ background: currentColor;
+ opacity: 0.75;
+ flex: none;
+}
+
+/* 消息 */
+.msg { margin-bottom: 30px; }
+
+.msg--user { display: flex; flex-direction: column; align-items: flex-end; gap: 5px; }
+.msg__meta { display: flex; align-items: center; gap: 8px; font-size: 11.5px; color: var(--text-3); }
+.msg--user .bubble {
+ background: var(--bg-overlay);
+ border: 1px solid var(--line-soft);
+ border-radius: var(--radius-lg) var(--radius-lg) 4px var(--radius-lg);
+ padding: 11px 16px;
+ max-width: 76%;
+ font-size: 14px;
+ box-shadow: var(--shadow-soft);
+}
+
+.msg--char__head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
+.msg--char__name { font-family: var(--font-serif); font-size: 15.5px; font-weight: 600; }
+.msg--char__meta { display: flex; align-items: center; gap: 8px; margin-left: 2px; }
+
+.msg__blocks { display: flex; flex-direction: column; gap: 12px; padding-left: 48px; }
+
+.msg__actions {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ padding-left: 48px;
+ margin-top: 10px;
+}
+.act-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+ font-size: 12px;
+ color: var(--text-3);
+ background: none;
+ border: none;
+ border-radius: 5px;
+ padding: 4px 9px;
+ cursor: pointer;
+ font-family: var(--font-ui);
+}
+.act-btn:hover { background: var(--bg-raised); color: var(--text-1); }
+.act-btn svg { width: 13.5px; height: 13.5px; }
+
+/* ---------- ContentBlock ---------- */
+.block {
+ background: var(--bg-raised);
+ border: 1px solid var(--line-soft);
+ border-radius: var(--radius);
+ overflow: hidden;
+}
+.block__body { padding: 14px 18px; }
+
+.block--text { background: transparent; border: none; padding: 0; }
+.block--text .block__body { padding: 0; }
+
+.block__head {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 8px 14px;
+ border-bottom: 1px solid var(--line-soft);
+ background: var(--bg-sunken);
+ font-size: 12px;
+ color: var(--text-3);
+}
+.block__head .lang { color: var(--accent); font-family: var(--font-mono); text-transform: lowercase; }
+.block__head .fname { font-family: var(--font-mono); color: var(--text-2); }
+.block__head .acts { margin-left: auto; display: flex; gap: 4px; }
+
+.md p { margin-bottom: 0.8em; }
+.md p:last-child { margin-bottom: 0; }
+.md strong { color: var(--text-1); }
+.md em { color: var(--text-2); }
+.md ul { padding-left: 1.3em; margin-bottom: 0.8em; }
+.md ul:last-child { margin-bottom: 0; }
+.md li { margin-bottom: 0.3em; }
+.md code {
+ font-family: var(--font-mono);
+ font-size: 0.88em;
+ background: var(--bg-sunken);
+ border: 1px solid var(--line-soft);
+ border-radius: 4px;
+ padding: 1px 6px;
+}
+.md a { text-underline-offset: 3px; text-decoration: underline; text-decoration-color: color-mix(in srgb, var(--accent) 45%, transparent); }
+.md .ext::after { content: " ↗"; font-size: 0.82em; color: var(--text-3); }
+
+/* 引文锚 */
+.quote { border-left: 3px solid var(--accent); padding: 2px 0 2px 14px; color: var(--text-2); font-family: var(--font-serif); font-size: 14.5px; }
+.quote__src { display: block; margin-top: 5px; font-family: var(--font-ui); font-size: 11.5px; color: var(--text-3); }
+
+/* 表格 */
+.table-wrap { overflow-x: auto; }
+table.data { border-collapse: collapse; width: 100%; font-size: 13px; }
+table.data th {
+ text-align: left;
+ font-weight: 500;
+ color: var(--text-3);
+ font-size: 12px;
+ border-bottom: 1px solid var(--line);
+ padding: 7px 12px;
+ white-space: nowrap;
+}
+table.data td { padding: 8px 12px; border-bottom: 1px solid var(--line-soft); color: var(--text-2); }
+table.data tr:last-child td { border-bottom: none; }
+table.data td:first-child { color: var(--text-1); font-family: var(--font-serif); }
+
+/* 代码块(手工着色示意) */
+pre.code {
+ font-family: var(--font-mono);
+ font-size: 12.8px;
+ line-height: 1.7;
+ padding: 14px 16px;
+ overflow-x: auto;
+ color: var(--text-2);
+}
+pre.code .k { color: var(--accent); }
+pre.code .s { color: var(--ok); }
+pre.code .c { color: var(--text-3); font-style: italic; }
+pre.code .f { color: var(--cinnabar-strong); }
+
+/* 数学块 */
+.math {
+ font-family: var(--font-serif);
+ font-style: italic;
+ font-size: 16.5px;
+ text-align: center;
+ padding: 16px 12px;
+ letter-spacing: 0.02em;
+}
+.math sub, .math sup { font-size: 0.68em; }
+.math__fallback { display: block; margin-top: 8px; font-family: var(--font-ui); font-style: normal; font-size: 11.5px; color: var(--text-3); text-align: center; }
+
+/* Mermaid 示意(内联 SVG 图) */
+.mermaid-box { padding: 18px 14px; overflow-x: auto; }
+.mermaid-cap { padding: 8px 14px; border-top: 1px solid var(--line-soft); font-size: 11.5px; color: var(--text-3); display: flex; gap: 8px; align-items: center; }
+
+/* 音频块 */
+.audio { display: flex; align-items: center; gap: 14px; padding: 13px 16px; }
+.audio__play {
+ width: 36px; height: 36px; border-radius: 50%;
+ border: 1px solid var(--line);
+ background: var(--bg-sunken);
+ color: var(--accent);
+ display: inline-flex; align-items: center; justify-content: center;
+ cursor: pointer; flex: none;
+}
+.audio__play svg { width: 15px; height: 15px; margin-left: 2px; }
+.wave { display: flex; align-items: center; gap: 2.5px; height: 30px; flex: 1; }
+.wave i {
+ width: 3px;
+ border-radius: 2px;
+ background: var(--accent);
+ opacity: 0.55;
+ display: block;
+}
+.audio__time { font-family: var(--font-mono); font-size: 12px; color: var(--text-3); }
+.audio__meta { flex: none; text-align: right; }
+.audio__meta .t { font-size: 12px; color: var(--text-2); display: block; }
+
+/* 工具调用块 */
+.tool { border-left: 3px solid var(--accent); }
+.tool__head { display: flex; align-items: center; gap: 10px; padding: 10px 16px; }
+.tool__name { font-family: var(--font-mono); font-size: 13px; color: var(--text-1); }
+.tool__purpose { font-size: 12px; color: var(--text-3); }
+.tool__body { padding: 0 16px 12px; font-size: 12.5px; color: var(--text-2); }
+.tool__row { display: flex; gap: 8px; padding: 2px 0; }
+.tool__row .k2 { color: var(--text-3); flex: none; }
+.tool__stat { display: flex; gap: 14px; margin-top: 6px; font-size: 11.5px; color: var(--text-3); }
+
+/* 请命(审批)卡 —— 签名组件 */
+.approval { border: 1px solid color-mix(in srgb, var(--cinnabar) 38%, transparent); }
+.approval__head {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 12px 18px;
+ border-bottom: 1px solid color-mix(in srgb, var(--cinnabar) 22%, transparent);
+ background: var(--cinnabar-soft);
+}
+.approval__seal {
+ width: 30px; height: 30px; flex: none;
+ border-radius: 50%;
+ border: 1.5px solid var(--cinnabar);
+ color: var(--cinnabar);
+ display: inline-flex; align-items: center; justify-content: center;
+ font-family: var(--font-serif); font-size: 13px; font-weight: 600;
+ background: var(--bg-raised);
+}
+.approval__title { font-size: 14px; }
+.approval__title .cn { font-size: 15.5px; font-weight: 600; color: var(--cinnabar); }
+.approval__body { padding: 13px 18px; display: flex; flex-direction: column; gap: 8px; }
+.fact { display: grid; grid-template-columns: 88px 1fr; gap: 10px; font-size: 13px; }
+.fact__k { color: var(--text-3); }
+.fact__v { color: var(--text-1); }
+.fact__v .mono { font-size: 12px; color: var(--text-2); }
+.approval__acts { display: flex; gap: 10px; align-items: center; padding: 0 18px 15px; }
+.approval__note { font-size: 11.5px; color: var(--text-3); margin-left: auto; max-width: 220px; }
+.approval--done { border-color: var(--line); }
+.approval--done .approval__head { background: transparent; border-bottom-color: var(--line-soft); }
+.approval--done .approval__seal { border-color: var(--ok); color: var(--ok); }
+
+/* 候答 */
+.candidates { display: flex; align-items: center; gap: 10px; padding-left: 48px; margin-top: -14px; margin-bottom: 26px; }
+.cand-switch { display: inline-flex; gap: 4px; }
+.cand-tag {
+ border: 1px solid var(--line);
+ background: var(--bg-sunken);
+ color: var(--text-3);
+ font-size: 12px;
+ border-radius: 999px;
+ padding: 3px 12px;
+ cursor: pointer;
+}
+.cand-tag.is-on { border-color: var(--accent); color: var(--accent-strong); background: var(--accent-soft); }
+
+/* 残答折叠 */
+.sealed {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 10px 16px;
+ border: 1px dashed var(--line);
+ border-radius: var(--radius);
+ font-size: 12.5px;
+ color: var(--text-3);
+ padding-left: 48px;
+ border-left: none; border-right: none; border-top: none;
+ margin-left: 0;
+}
+.sealed__body { flex: 1; }
+.sealed__t { color: var(--text-2); }
+
+/* 息壤候选(记忆候选卡) */
+.memory-cand { border-left: 3px solid var(--warn); }
+.memory-cand__head { display: flex; align-items: center; gap: 10px; padding: 11px 16px; }
+.memory-cand__body { padding: 0 16px 13px; font-size: 13px; color: var(--text-2); }
+.memory-cand__body .q { font-family: var(--font-serif); color: var(--text-1); font-size: 14px; }
+.memory-cand__acts { display: flex; gap: 8px; margin-top: 10px; }
+.mem-type {
+ font-size: 11px;
+ border: 1px solid var(--line);
+ border-radius: 4px;
+ padding: 1px 7px;
+ color: var(--text-3);
+}
+
+/* 临时芽体(流式) */
+.sprout { border: 1px dashed color-mix(in srgb, var(--accent) 45%, transparent); background: transparent; }
+.sprout__head { display: flex; align-items: center; gap: 10px; padding: 9px 16px; font-size: 12px; color: var(--accent); border-bottom: 1px dashed var(--line); }
+.sprout__body { padding: 13px 16px; }
+.stream-cursor {
+ display: inline-block;
+ width: 8px; height: 17px;
+ background: var(--accent);
+ vertical-align: -3px;
+ margin-left: 2px;
+ animation: blink 1.05s step-end infinite;
+ border-radius: 1px;
+}
+@keyframes blink { 50% { opacity: 0; } }
+.src-line { font-family: var(--font-mono); font-size: 13px; color: var(--text-2); white-space: pre-wrap; }
+.src-line .unclosed { color: var(--text-3); }
+
+/* 输入区 */
+.composer { border-top: 1px solid var(--line-soft); padding: 14px 26px 18px; background: var(--bg); }
+.composer__inner { max-width: 780px; margin: 0 auto; }
+.composer__box {
+ background: var(--bg-raised);
+ border: 1px solid var(--line);
+ border-radius: var(--radius-lg);
+ padding: 12px 14px;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+.composer__box:focus-within { border-color: var(--accent); }
+.composer__ta {
+ background: none; border: none; resize: none;
+ color: var(--text-1);
+ font-family: var(--font-ui);
+ font-size: 14px;
+ line-height: 1.6;
+ min-height: 44px;
+ outline: none;
+}
+.composer__bar { display: flex; align-items: center; gap: 6px; }
+.composer__bar .spacer { flex: 1; }
+.composer__hint { display: flex; gap: 14px; margin-top: 8px; font-size: 11.5px; color: var(--text-3); flex-wrap: wrap; }
+.composer__hint .k { color: var(--text-2); }
+
+/* ---------- 右栏:镜枝面板 ---------- */
+.conv-ctx {
+ border-left: 1px solid var(--line-soft);
+ overflow-y: auto;
+ padding: 18px 16px 30px;
+ background: var(--bg);
+}
+.ctx-section { margin-bottom: 24px; }
+.ctx-section__head {
+ display: flex; align-items: baseline; gap: 8px;
+ margin-bottom: 10px;
+}
+.ctx-section__head .cn { font-size: 14.5px; font-weight: 600; }
+.ctx-section__head .more { margin-left: auto; font-size: 11.5px; }
+
+/* 分支树 */
+.tree { font-size: 12.5px; }
+.tree ul { list-style: none; padding-left: 18px; position: relative; }
+.tree > ul { padding-left: 2px; }
+.tree ul::before {
+ content: "";
+ position: absolute;
+ left: 6px; top: 4px; bottom: 12px;
+ width: 1px;
+ background: color-mix(in srgb, var(--text-3) 55%, transparent);
+}
+.tree li { position: relative; padding: 3px 0 3px 14px; }
+.tree li::before {
+ content: "";
+ position: absolute;
+ left: 0; top: 13px;
+ width: 12px; height: 1px;
+ background: color-mix(in srgb, var(--text-3) 55%, transparent);
+}
+.tree li:last-child::after {
+ content: "";
+ position: absolute;
+ left: -12px; top: 15px; bottom: 0;
+ width: 1px;
+ background: var(--bg);
+}
+.tree__node { display: flex; align-items: center; gap: 7px; color: var(--text-2); }
+.tree__node .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.tree__node--main .nm { color: var(--text-1); font-weight: 500; }
+.tree__node--now {
+ background: var(--accent-soft);
+ border-radius: 5px;
+ outline: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
+ padding: 2px 6px;
+ margin-left: -6px;
+ color: var(--accent-strong);
+}
+.tree__node--sealed { color: var(--text-3); }
+.tree__tag { font-size: 10.5px; border-radius: 4px; padding: 0 6px; flex: none; border: 1px solid var(--line); color: var(--text-3); }
+.tree__tag--main { border-color: color-mix(in srgb, var(--accent) 40%, transparent); color: var(--accent); }
+.tree__tag--seal { border-color: var(--line); color: var(--text-3); text-decoration: line-through; }
+.tree__tag--bud { border-color: color-mix(in srgb, var(--accent) 40%, transparent); color: var(--accent); animation: pulse 2.2s ease-in-out infinite; }
+
+/* 上下文镜 */
+.ctx-mirror__row { display: flex; gap: 9px; padding: 6px 0; font-size: 12.5px; align-items: baseline; }
+.ctx-mirror__type { flex: none; width: 34px; color: var(--text-3); }
+.ctx-mirror__txt { color: var(--text-2); flex: 1; }
+.ctx-mirror__src { font-size: 11px; color: var(--text-3); white-space: nowrap; }
+.ctx-mirror__omitted { color: var(--text-3); font-size: 12px; padding: 4px 0; }
+.ctx-meter { margin-top: 10px; }
+.ctx-meter__label { display: flex; justify-content: space-between; font-size: 11.5px; color: var(--text-3); margin-bottom: 5px; }
+
+/* ---------- 索引页(设计说明) ---------- */
+.doc { max-width: 980px; }
+.doc-hero { padding: 44px 0 30px; border-bottom: 1px solid var(--line-soft); margin-bottom: 34px; }
+.doc-hero h1 { font-family: var(--font-serif); font-size: 34px; font-weight: 600; margin-bottom: 10px; display: flex; align-items: center; gap: 16px; }
+.doc-hero p { color: var(--text-2); max-width: 640px; }
+.doc-hero .ver { font-family: var(--font-mono); font-size: 13px; color: var(--accent); border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent); border-radius: 999px; padding: 3px 12px; vertical-align: middle; }
+
+.doc-sec { margin-bottom: 40px; }
+.doc-sec > h2 { font-family: var(--font-serif); font-size: 20px; font-weight: 600; margin-bottom: 6px; }
+.doc-sec > .sub { color: var(--text-3); font-size: 13px; margin-bottom: 18px; }
+
+.page-nav { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 12px; }
+.page-nav a.card { display: flex; flex-direction: column; gap: 6px; color: var(--text-1); transition: border-color 0.15s; }
+.page-nav a.card:hover { border-color: var(--accent); }
+.page-nav .cn { font-size: 16px; font-weight: 600; }
+.page-nav .d { font-size: 12.5px; color: var(--text-3); }
+.page-nav .p { font-family: var(--font-mono); font-size: 11px; color: var(--accent); }
+
+.swatches { display: grid; grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); gap: 10px; }
+.swatch { border: 1px solid var(--line-soft); border-radius: var(--radius-sm); overflow: hidden; }
+.swatch__c { height: 52px; }
+.swatch__m { padding: 7px 10px; font-size: 11.5px; color: var(--text-2); background: var(--bg-raised); }
+.swatch__m b { display: block; font-size: 12px; color: var(--text-1); font-weight: 500; }
+.swatch__m .mono { color: var(--text-3); font-size: 10.5px; }
+
+.type-spec { display: flex; flex-direction: column; gap: 14px; }
+.type-spec__row { display: flex; align-items: baseline; gap: 20px; border-bottom: 1px dashed var(--line-soft); padding-bottom: 13px; flex-wrap: wrap; }
+.type-spec__label { width: 130px; flex: none; font-size: 12px; color: var(--text-3); }
+.type-spec__sample { color: var(--text-1); }
+
+.demo-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-bottom: 14px; }
+.demo-block { margin-bottom: 22px; }
+
+.badge-table td:first-child { font-family: var(--font-ui); }
+
+/* ---------- 响应式 ---------- */
+@media (max-width: 1200px) {
+ .conv-layout { grid-template-columns: 240px 1fr; }
+ .conv-ctx { display: none; }
+}
+
+@media (max-width: 960px) {
+ .app { grid-template-columns: 64px 1fr; }
+ .brand__text, .nav-item .cn, .nav-item .nav-plain, .nav-group__title { display: none; }
+ .nav-item { justify-content: center; }
+ .brand { justify-content: center; padding: 16px 0 10px; }
+ .home-cols { grid-template-columns: 1fr; }
+ .char-layout { grid-template-columns: 1fr; }
+ .conv-layout { grid-template-columns: 1fr; }
+ .conv-side { display: none; }
+ .content { padding: 20px 18px 40px; }
+ .prototype-tag, .topbar .realm-dot { display: none; }
+ .crumbs .crumbs__sep, .crumbs .cn { display: none; }
+}
+
+/* ============================================================
+ 第二批页面组件(息壤/形仪/天工阁/百工坊/雅集/同契台/云市/神识阁/器谱/司命台/设置)
+ ============================================================ */
+
+/* 页内选项卡 */
+.tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--line-soft); margin-bottom: 22px; overflow-x: auto; }
+.tab {
+ border: none; background: none; cursor: pointer;
+ font-family: var(--font-ui); font-size: 13.5px; color: var(--text-2);
+ padding: 9px 14px 10px; border-bottom: 2px solid transparent; margin-bottom: -1px;
+ display: inline-flex; align-items: center; gap: 7px; white-space: nowrap;
+}
+.tab .cnt { font-size: 11px; color: var(--text-3); border: 1px solid var(--line); border-radius: 999px; padding: 0 7px; }
+.tab:hover { color: var(--text-1); }
+.tab.is-on { color: var(--accent-strong); border-bottom-color: var(--accent); }
+.tab.is-on .cnt { border-color: color-mix(in srgb, var(--accent) 40%, transparent); color: var(--accent); }
+
+/* chip 筛选 */
+.chips { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; }
+.chip {
+ font-size: 12.5px; color: var(--text-2);
+ border: 1px solid var(--line); border-radius: 999px;
+ padding: 3px 13px; cursor: pointer; background: none; font-family: var(--font-ui);
+ display: inline-flex; align-items: center; gap: 6px;
+}
+.chip:hover { color: var(--text-1); border-color: var(--text-3); }
+.chip.is-on { color: var(--accent-strong); border-color: var(--accent); background: var(--accent-soft); }
+
+/* 搜索条 */
+.searchbar { display: flex; align-items: center; gap: 9px; background: var(--bg-sunken); border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 7px 12px; }
+.searchbar svg { width: 15px; height: 15px; color: var(--text-3); flex: none; }
+.searchbar input { flex: 1; background: none; border: none; outline: none; color: var(--text-1); font-family: var(--font-ui); font-size: 13.5px; min-width: 0; }
+.searchbar input::placeholder { color: var(--text-3); }
+
+/* 开关 */
+.switch { position: relative; width: 36px; height: 20px; flex: none; display: inline-block; }
+.switch input { position: absolute; opacity: 0; inset: 0; cursor: pointer; z-index: 1; margin: 0; }
+.switch i { position: absolute; inset: 0; border-radius: 999px; background: var(--line); transition: background 0.15s; display: block; }
+.switch i::after { content: ""; position: absolute; left: 2px; top: 2px; width: 16px; height: 16px; border-radius: 50%; background: var(--bg-raised); box-shadow: var(--shadow-soft); transition: transform 0.15s; }
+.switch input:checked + i { background: var(--accent); }
+.switch input:checked + i::after { transform: translateX(16px); }
+.switch input:focus-visible + i { outline: 2px solid var(--accent); outline-offset: 2px; }
+
+/* 设置行 */
+.set-row { display: flex; align-items: center; gap: 14px; padding: 13px 0; border-bottom: 1px solid var(--line-soft); }
+.set-row:last-child { border-bottom: none; }
+.set-row__body { flex: 1; min-width: 0; }
+.set-row__t { font-size: 13.5px; }
+.set-row__d { font-size: 12px; color: var(--text-3); margin-top: 2px; line-height: 1.55; }
+
+/* 资产 / 商品网格 */
+.asset-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(196px, 1fr)); gap: 14px; }
+.asset-card { display: flex; flex-direction: column; border: 1px solid var(--line-soft); border-radius: var(--radius); overflow: hidden; background: var(--bg-raised); }
+.asset-thumb { height: 118px; position: relative; display: flex; align-items: center; justify-content: center; }
+.asset-thumb .glyph { font-family: var(--font-serif); font-size: 30px; color: rgba(255, 255, 255, 0.88); text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); }
+.asset-type { position: absolute; top: 8px; left: 8px; font-size: 10.5px; color: #fff; background: rgba(0, 0, 0, 0.45); border-radius: 4px; padding: 1px 7px; }
+.asset-flag { position: absolute; top: 8px; right: 8px; }
+.asset-body { padding: 11px 13px 12px; display: flex; flex-direction: column; gap: 5px; flex: 1; }
+.asset-name { font-size: 13.5px; }
+.asset-sub { font-size: 11.5px; color: var(--text-3); }
+.asset-acts { display: flex; gap: 6px; margin-top: auto; padding-top: 6px; flex-wrap: wrap; }
+
+/* 价格(金额直白优先) */
+.price { font-family: var(--font-serif); font-size: 17px; font-weight: 600; }
+.price small { font-size: 12px; font-weight: 400; color: var(--text-3); }
+.price--free { color: var(--ok); }
+
+/* 天工:工筹步骤 */
+.step { display: flex; gap: 12px; position: relative; padding-bottom: 18px; }
+.step:last-child { padding-bottom: 0; }
+.step::before { content: ""; position: absolute; left: 13px; top: 30px; bottom: 2px; width: 1px; background: var(--line); }
+.step:last-child::before { display: none; }
+.step__dot { width: 27px; height: 27px; border-radius: 50%; flex: none; display: flex; align-items: center; justify-content: center; font-size: 12px; border: 1px solid var(--line); color: var(--text-3); background: var(--bg-raised); font-family: var(--font-serif); }
+.step--done .step__dot { border-color: var(--ok); color: var(--ok); }
+.step--run .step__dot { border-color: var(--accent); color: var(--accent-strong); animation: pulse 2.2s ease-in-out infinite; }
+.step__t { font-size: 13.5px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
+.step__d { font-size: 12px; color: var(--text-3); margin-top: 1px; }
+
+/* 用量条形图 */
+.bars { display: flex; align-items: flex-end; gap: 7px; height: 112px; padding-top: 6px; }
+.bars__col { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px; justify-content: flex-end; height: 100%; }
+.bars__bar { width: 100%; max-width: 34px; border-radius: 4px 4px 0 0; background: var(--accent); opacity: 0.8; }
+.bars__label { font-size: 10.5px; color: var(--text-3); }
+
+/* 授权行 */
+.grant-row { display: grid; grid-template-columns: 122px 1fr auto; gap: 12px; align-items: center; padding: 11px 0; border-bottom: 1px solid var(--line-soft); }
+.grant-row:last-child { border-bottom: none; }
+.grant-row__cap { display: flex; align-items: center; gap: 7px; font-size: 13px; }
+.grant-row__cap svg { width: 15px; height: 15px; color: var(--text-3); flex: none; }
+.grant-row__meta { font-size: 12px; color: var(--text-3); margin-top: 2px; line-height: 1.5; }
+
+/* 帖子卡(雅集) */
+.post { padding: 16px 18px; }
+.post__head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
+.post__author { font-size: 13.5px; }
+.post__time { font-size: 11.5px; color: var(--text-3); }
+.post__body { font-size: 13.5px; color: var(--text-2); display: flex; flex-direction: column; gap: 8px; }
+.post__foot { display: flex; gap: 4px; margin-top: 12px; }
+
+/* 双栏(主内容 + 侧栏) */
+.two-col { display: grid; grid-template-columns: 1fr 320px; gap: 18px; align-items: start; }
+
+/* 玄络图容器 */
+.graph-box { padding: 10px; }
+
+/* 权限矩阵标记 */
+.mark-ok { color: var(--ok); }
+.mark-no { color: var(--text-3); opacity: 0.45; }
+
+/* 治理案件卡 */
+.case-card { border-left: 3px solid var(--warn); }
+
+@media (max-width: 1100px) {
+ .two-col { grid-template-columns: 1fr; }
+}
+
+/* ============================================================
+ 铜镜鉴心传承:鎏金发丝线(承接 main 分支旧版设计体系)
+ ============================================================ */
+:root {
+ --gilt: #c9a24a;
+ --gilt-dim: rgba(201, 162, 74, 0.34);
+ --gilt-faint: rgba(201, 162, 74, 0.14);
+}
+[data-theme="light"] {
+ --gilt: #8f7226;
+ --gilt-dim: rgba(143, 114, 38, 0.4);
+ --gilt-faint: rgba(143, 114, 38, 0.15);
+}
+
+.brand { cursor: pointer; }
+.brand__mark { box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.16), inset 0 0 0 4px rgba(0, 0, 0, 0.16), 0 0 0 1px var(--gilt-dim); }
+.nav-group__title { color: var(--gilt-dim); }
+.topbar { border-bottom: 1px solid var(--gilt-faint); }
+.section__head { position: relative; }
+.section__head > .cn:first-child::before {
+ content: "";
+ display: inline-block;
+ width: 3px;
+ height: 13px;
+ border-radius: 2px;
+ background: var(--gilt);
+ margin-right: 8px;
+ vertical-align: -1px;
+}
+.timeline::before { background: var(--gilt-faint); }
+
+/* logo 彩蛋动效 */
+.brand.tick .brand__mark { transform: scale(0.92); }
+.brand__mark { transition: transform 0.18s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s; }
+.brand.egg-pulse .brand__mark {
+ animation: egg-pulse 1.3s ease-out;
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2), inset 0 0 0 4px rgba(0, 0, 0, 0.16), 0 0 0 1px var(--gilt), 0 0 22px var(--gilt-dim);
+}
+@keyframes egg-pulse {
+ 0% { transform: scale(1); }
+ 30% { transform: scale(1.12) rotate(6deg); }
+ 60% { transform: scale(0.96) rotate(-3deg); }
+ 100% { transform: scale(1); }
+}
+.egg-toast {
+ position: fixed;
+ bottom: 48px;
+ left: 50%;
+ transform: translateX(-50%);
+ padding: 12px 28px;
+ border-radius: 12px;
+ background: var(--accent-soft);
+ color: var(--accent-strong);
+ font-family: var(--font-serif);
+ font-size: 14px;
+ border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
+ z-index: 9999;
+ pointer-events: none;
+ opacity: 0;
+ transition: opacity 0.4s;
+ box-shadow: var(--shadow);
+}
+.egg-toast.is-on { opacity: 1; }
+
+/* ============================================================
+ 侧栏折叠系统(主侧栏 + 对话页左右面板)
+ ============================================================ */
+.side-fold-btn { color: var(--text-3); }
+.side-fold-btn svg { transition: transform 0.18s ease; }
+.side-fold-btn:hover { color: var(--gilt); }
+
+.app--fold { grid-template-columns: 64px 1fr; }
+.app--fold .brand__text,
+.app--fold .nav-item .cn,
+.app--fold .nav-item .nav-plain,
+.app--fold .nav-group__title { display: none; }
+.app--fold .nav-item { justify-content: center; }
+.app--fold .brand { justify-content: center; padding: 16px 0 10px; }
+.app--fold .side-fold-btn svg { transform: rotate(180deg); }
+.app--fold .side-foot { flex-direction: column; gap: 6px; padding: 10px 8px; }
+.app--fold .side-foot > span:not(.avatar) { display: none; }
+
+/* 对话页面板折叠 */
+.conv-layout.no-left { grid-template-columns: 0 1fr 320px; }
+.conv-layout.no-left .conv-side { display: none; }
+.conv-layout.no-right { grid-template-columns: 268px 1fr 0; }
+.conv-layout.no-right .conv-ctx { display: none; }
+.panel-toggle {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
+ border-radius: var(--radius-sm);
+ border: 1px solid var(--line);
+ background: var(--bg-raised);
+ color: var(--text-3);
+ cursor: pointer;
+}
+.panel-toggle:hover { color: var(--gilt); border-color: var(--gilt-dim); }
+.panel-toggle svg { width: 15px; height: 15px; }
+.conv-restore {
+ position: absolute;
+ top: 14px;
+ z-index: 15;
+ width: 32px;
+ height: 32px;
+ border-radius: var(--radius-sm);
+ border: 1px solid var(--line);
+ background: var(--bg-raised);
+ color: var(--text-2);
+ cursor: pointer;
+ display: none;
+ align-items: center;
+ justify-content: center;
+ box-shadow: var(--shadow-soft);
+}
+.conv-restore:hover { color: var(--gilt); border-color: var(--gilt-dim); }
+.conv-restore svg { width: 16px; height: 16px; }
+.conv-restore--left { left: 14px; }
+.conv-restore--right { right: 14px; }
+.conv-layout.no-left .conv-restore--left { display: inline-flex; }
+.conv-layout.no-right .conv-restore--right { display: inline-flex; }
+
+/* ============================================================
+ 登录 / 注册页(StageShell · 铜镜鉴心,承接旧版铜镜登录页)
+ ============================================================ */
+.stage {
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ overflow-x: hidden;
+ background: var(--bg);
+}
+
+.stage__bg { position: fixed; inset: 0; z-index: -1; pointer-events: none; }
+.stage__rings {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+.stage__rings svg { width: min(94vmin, 880px); height: min(94vmin, 880px); opacity: 0.16; }
+[data-theme="light"] .stage__rings svg { opacity: 0.1; }
+.ring-ink { fill: none; stroke: var(--gilt-faint); stroke-width: 0.6; }
+.ring-faint { fill: none; stroke: var(--gilt-faint); stroke-width: 0.35; stroke-dasharray: 2 7; }
+.ring-slow { transform-origin: center; animation: ring-spin 140s linear infinite; }
+@keyframes ring-spin { to { transform: rotate(360deg); } }
+
+.stage__topbar {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 16px clamp(20px, 4vw, 44px);
+}
+.stage__topbar .spacer { flex: 1; }
+
+.stage__main {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 24px 18px 48px;
+}
+
+/* 铜镜卡:唯一的镜面 */
+.mirror-card {
+ width: min(408px, 94vw);
+ background: var(--bg-raised);
+ border: 1px solid var(--gilt-dim);
+ border-radius: 18px;
+ padding: 38px 36px 30px;
+ position: relative;
+ overflow: hidden;
+ box-shadow: var(--shadow), inset 0 1px 0 rgba(255, 255, 255, 0.05);
+}
+.mirror-card::after {
+ /* 扫描亮带:铜镜在烛光下微倾时的漫反射 */
+ content: "";
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ height: 34%;
+ background: linear-gradient(178deg, transparent, rgba(201, 162, 74, 0.05) 45%, rgba(255, 255, 255, 0.045) 55%, transparent);
+ pointer-events: none;
+ animation: mirror-scan 7.5s ease-in-out infinite;
+}
+@keyframes mirror-scan {
+ 0% { transform: translateY(-110%); }
+ 55% { transform: translateY(320%); }
+ 100% { transform: translateY(320%); }
+}
+
+.mirror-card__brand { display: flex; align-items: center; gap: 12px; justify-content: center; margin-bottom: 8px; }
+.mirror-card__brand .brand__mark { width: 40px; height: 40px; }
+.mirror-card__title { text-align: center; }
+.mirror-card__title h1 { font-family: var(--font-serif); font-size: 24px; font-weight: 600; letter-spacing: 0.04em; }
+.mirror-card__sub { text-align: center; font-size: 12.5px; color: var(--text-3); margin: 6px 0 26px; }
+
+.field-group { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
+.field-group label { font-size: 12.5px; color: var(--text-2); }
+.field-group .input { background: var(--bg-sunken); }
+.field-row { display: flex; gap: 10px; }
+.field-row .input { flex: 1; }
+
+.login-alt {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin: 18px 0 14px;
+ color: var(--text-3);
+ font-size: 11.5px;
+}
+.login-alt::before, .login-alt::after { content: ""; flex: 1; height: 1px; background: var(--line-soft); }
+
+.login-note { font-size: 11.5px; color: var(--text-3); line-height: 1.7; text-align: center; margin-top: 16px; }
+.login-foot { text-align: center; font-size: 13px; margin-top: 18px; color: var(--text-2); }
+
+.check-row { display: flex; gap: 10px; align-items: flex-start; margin-bottom: 12px; }
+.check-row input[type="checkbox"] { margin-top: 3px; accent-color: var(--accent); width: 15px; height: 15px; flex: none; }
+.check-row .t { font-size: 12.5px; color: var(--text-2); line-height: 1.6; }
+.check-row .t b { color: var(--text-1); font-weight: 500; }
+
+@media (prefers-reduced-motion: reduce) {
+ .mirror-card::after, .ring-slow { animation: none; }
+}
+
+/* 面板收起后内容区自适应放宽(平滑过渡) */
+.conv-stream__inner, .composer__inner { transition: max-width 0.25s ease; }
+.conv-layout.no-left .conv-stream__inner,
+.conv-layout.no-left .composer__inner,
+.conv-layout.no-right .conv-stream__inner,
+.conv-layout.no-right .composer__inner { max-width: 1000px; }
+.conv-layout.no-left.no-right .conv-stream__inner,
+.conv-layout.no-left.no-right .composer__inner { max-width: 1140px; }
+
+/* 双面板同时收起:不留空轨道,内容占满 */
+.conv-layout.no-left.no-right { grid-template-columns: 0 1fr 0; }
+
+/* 显式网格列位:面板收起(display:none)后主区不因自动放置错位到空轨道 */
+.conv-side { grid-column: 1; }
+.conv-main { grid-column: 2; }
+.conv-ctx { grid-column: 3; }
+
+/* ============================================================
+ 旧版「铜镜鉴心」传承细节(本轮系统挖掘 main 分支 25 页后融入)
+ ============================================================ */
+
+/* 极光铜晕:极淡的三色径向氛围底(承接 _base.css body::before) */
+body::before {
+ content: "";
+ position: fixed;
+ inset: 0;
+ z-index: -1;
+ pointer-events: none;
+ background:
+ radial-gradient(60vmax 60vmax at 12% -10%, rgba(88, 169, 156, 0.05), transparent 60%),
+ radial-gradient(50vmax 50vmax at 105% 15%, rgba(201, 162, 74, 0.035), transparent 60%),
+ radial-gradient(46vmax 46vmax at 50% 115%, rgba(196, 87, 74, 0.022), transparent 62%);
+}
+
+/* 卡片 hover 光扫(承接 --sweep-gradient 手法,克制到接近不可见) */
+.card { position: relative; }
+.card::after {
+ content: "";
+ position: absolute;
+ inset: 0;
+ border-radius: inherit;
+ pointer-events: none;
+ background: linear-gradient(115deg, transparent 30%, rgba(201, 162, 74, 0.03) 48%, rgba(255, 255, 255, 0.035) 52%, transparent 70%);
+ opacity: 0;
+ transition: opacity 0.35s ease;
+}
+.card:hover::after { opacity: 1; }
+
+/* 天工阁:进行中步骤的「动爻」鎏金光晕(爻象手法的 1.0 化:done=铜绿已闭,active=鎏金流转) */
+.step--run .step__dot {
+ background: radial-gradient(circle at 50% 40%, color-mix(in srgb, var(--gilt) 30%, var(--bg-raised)), var(--bg-raised));
+ box-shadow: 0 0 0 3px var(--gilt-faint), 0 0 14px var(--gilt-dim);
+}
+
+/* 镜庭:日晷时辰盘(承接 time-preview 日晷,静态原型固定巳时) */
+.sundial { position: relative; width: 116px; height: 116px; flex: none; }
+.sundial svg { width: 100%; height: 100%; display: block; }
+.sundial__center {
+ position: absolute; inset: 0;
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
+ pointer-events: none;
+}
+.sundial__hour { font-family: var(--font-serif); font-size: 17px; font-weight: 600; color: var(--gilt); }
+.sundial__sub { font-size: 9.5px; color: var(--text-3); margin-top: 1px; }
+.sundial-spin { transform-origin: 58px 58px; animation: sundial-spin 120s linear infinite; }
+@keyframes sundial-spin { to { transform: rotate(360deg); } }
+@media (prefers-reduced-motion: reduce) { .sundial-spin { animation: none; } }
+
+/* 请命卡:风险等级徽章(承接朱批 RISK 分层) */
+.risk-badge {
+ font-family: var(--font-mono);
+ font-size: 10.5px;
+ letter-spacing: 0.04em;
+ border-radius: 4px;
+ padding: 1px 7px;
+ border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent);
+ color: var(--warn);
+}
+.risk-badge--low { border-color: color-mix(in srgb, var(--accent) 40%, transparent); color: var(--accent); }
+
+/* 玄络:待验假设边(承接河图 hypothesis 朱漆虚线呼吸) */
+.tent-edge { stroke-dasharray: 5 5; animation: tent-pulse 2.8s ease-in-out infinite; }
+@keyframes tent-pulse { 50% { opacity: 0.35; } }
+@media (prefers-reduced-motion: reduce) { .tent-edge { animation: none; } }
+
+/* ============================================================
+ main 旧版设计复刻件(概念映射到 1.0:六爻→小周天阶段、九枢→灵台六模块、
+ 周天四镜→大周天四阶段、好奇萤火→内驱力、五行相克→运营指标对)
+ ============================================================ */
+
+/* ---- 灵台六枢星盘(复刻九枢星盘「活的仪器」,映射灵台六模块 ADR-016/017) ---- */
+.astrolabe-wrap { display: flex; gap: 22px; align-items: center; flex-wrap: wrap; }
+.astrolabe { width: 262px; flex: none; }
+.astrolabe svg { width: 100%; height: auto; display: block; }
+.ast-node { cursor: pointer; }
+.ast-node:hover .ast-node__ring { stroke: var(--gilt); }
+.ast-core-text { font-family: var(--font-serif); fill: var(--text-1); }
+.ast-node__name { font-family: var(--font-serif); font-size: 12.5px; fill: var(--text-1); }
+.ast-node__plain { font-size: 8.5px; fill: var(--text-3); }
+.ast-spin { transform-origin: 160px 160px; animation: ast-spin 120s linear infinite; }
+@keyframes ast-spin { to { transform: rotate(360deg); } }
+.ast-breathe { animation: ast-breathe 7s ease-in-out infinite; transform-origin: 160px 160px; }
+@keyframes ast-breathe { 50% { opacity: 0.25; } }
+.ast-alert { animation: ast-alert 1.8s ease-in-out infinite; }
+@keyframes ast-alert { 50% { opacity: 0.2; } }
+@media (prefers-reduced-motion: reduce) {
+ .ast-spin, .ast-breathe, .ast-alert { animation: none; }
+}
+
+/* 铜镜四态健康词汇(复刻 MirrorState) */
+.mirror-state { display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px; }
+.mirror-state i { width: 10px; height: 10px; border-radius: 50%; flex: none; }
+.mirror-state--steady i { background: var(--ok); box-shadow: 0 0 8px color-mix(in srgb, var(--ok) 55%, transparent); }
+.mirror-state--cracked i { background: var(--err); }
+
+/* 相克告警卡(复刻五行相克告警,映射为运营指标对) */
+.clash-row { display: grid; grid-template-columns: 1fr 52px; gap: 10px; align-items: center; padding: 8px 0; border-bottom: 1px dashed var(--line-soft); }
+.clash-row:last-child { border-bottom: none; }
+.clash-row__pair { font-size: 12.5px; color: var(--text-2); }
+.clash-row__pair b { color: var(--text-1); font-weight: 500; }
+.clash-strength { height: 5px; border-radius: 3px; background: var(--bg-sunken); overflow: hidden; }
+.clash-strength i { display: block; height: 100%; background: var(--warn); border-radius: 3px; }
+
+/* ---- 大周天四镜(复刻周天四镜阶段链,映射 ADR-023 四阶段) ---- */
+.cycle-mirrors { display: flex; align-items: center; gap: 6px; margin-bottom: 14px; }
+.cycle-mirror {
+ flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px;
+ padding: 12px 6px 10px; border-radius: var(--radius);
+ background: var(--bg-sunken); border: 1px solid var(--line-soft);
+}
+.cycle-mirror__disc {
+ width: 44px; height: 44px; border-radius: 50%;
+ display: flex; align-items: center; justify-content: center;
+ font-family: var(--font-serif); font-size: 18px;
+ background:
+ radial-gradient(circle at 34% 28%, rgba(255, 255, 255, 0.16), transparent 46%),
+ conic-gradient(from 210deg, #33474a, #4d7d76 45%, #3a4d51 85%, #33474a);
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 0 0 1px var(--line);
+ color: var(--text-2);
+}
+.cycle-mirror.is-done .cycle-mirror__disc { color: var(--ok); box-shadow: inset 0 0 0 1px rgba(255,255,255,.1), 0 0 0 1px color-mix(in srgb, var(--ok) 45%, transparent); }
+.cycle-mirror.is-active {
+ border-color: color-mix(in srgb, var(--gilt) 45%, transparent);
+ background: linear-gradient(180deg, var(--gilt-faint), transparent 70%), var(--bg-sunken);
+}
+.cycle-mirror.is-active .cycle-mirror__disc {
+ color: var(--gilt);
+ background:
+ radial-gradient(circle at 34% 28%, rgba(255, 255, 255, 0.2), transparent 46%),
+ radial-gradient(circle at 50% 45%, color-mix(in srgb, var(--gilt) 32%, #33474a), #33474a);
+ box-shadow: 0 0 0 1px var(--gilt-dim), 0 0 16px var(--gilt-dim);
+}
+.cycle-mirror__name { font-size: 12.5px; color: var(--text-2); }
+.cycle-mirror.is-active .cycle-mirror__name { color: var(--gilt); font-weight: 500; }
+.cycle-mirror__state { font-size: 10.5px; color: var(--text-3); }
+.cycle-mirror__link { width: 14px; height: 1px; background: var(--line); flex: none; }
+
+/* ---- 好奇萤火(复刻 drive 萤火点,亮度=内驱力值) ---- */
+.firefly {
+ width: 9px; height: 9px; border-radius: 50%; flex: none;
+ background: var(--accent);
+ animation: firefly-pulse 2.6s ease-in-out infinite;
+}
+@keyframes firefly-pulse {
+ 0%, 100% { box-shadow: 0 0 3px 1px color-mix(in srgb, var(--accent) 45%, transparent); }
+ 50% { box-shadow: 0 0 9px 3px color-mix(in srgb, var(--accent) 60%, transparent); }
+}
+.firefly--dim { opacity: 0.45; animation-duration: 4.2s; }
+.firefly--bright { background: var(--gilt); }
+.firefly--bright { animation-name: firefly-pulse-gilt; }
+@keyframes firefly-pulse-gilt {
+ 0%, 100% { box-shadow: 0 0 4px 1px var(--gilt-dim); }
+ 50% { box-shadow: 0 0 11px 4px var(--gilt-dim); }
+}
+@media (prefers-reduced-motion: reduce) { .firefly { animation: none; } }
+
+/* ---- 镜树:会话列表分组 + 彩点(复刻 chat-redesign 铭文往来分组) ---- */
+.conv-group__title {
+ font-size: 10.5px; color: var(--text-3); letter-spacing: 0.1em;
+ padding: 12px 8px 5px;
+}
+.conv-group__title:first-child { padding-top: 2px; }
+.conv-item__title { display: flex; align-items: center; gap: 8px; font-size: 13.5px; }
+.conv-item__dot { width: 7px; height: 7px; border-radius: 50%; flex: none; background: var(--muted); }
+.conv-item__dot--active { background: var(--accent); box-shadow: 0 0 6px color-mix(in srgb, var(--accent) 55%, transparent); }
+.conv-item__dot--sim { background: var(--warn); }
+.conv-item__dot--risk { background: var(--cinnabar); }
+
+/* 用户消息鎏金右边线(复刻 copper-v2 用户消息金线) */
+.msg--user .bubble { border-right: 2px solid var(--gilt-dim); }
+
+/* ---- 输入区控制药丸(复刻「深虑 / 博闻」) ---- */
+.pill-btn {
+ display: inline-flex; align-items: center; gap: 5px;
+ font-size: 12px; color: var(--text-3);
+ border: 1px solid var(--line); border-radius: 999px;
+ background: none; cursor: pointer; padding: 3px 11px;
+ font-family: var(--font-ui);
+}
+.pill-btn:hover { color: var(--text-1); }
+.pill-btn.is-on { color: var(--accent-strong); border-color: color-mix(in srgb, var(--accent) 45%, transparent); background: var(--accent-soft); }
+.pill-btn small { font-size: 10px; color: var(--text-3); }
+
+/* ---- 镜生 card-banner(复刻渐变横幅) ---- */
+.char-banner {
+ height: 72px; border-radius: var(--radius); margin-bottom: 16px;
+ background:
+ radial-gradient(120% 190% at 12% 120%, color-mix(in srgb, var(--accent) 16%, transparent), transparent 55%),
+ linear-gradient(115deg, color-mix(in srgb, var(--accent) 8%, transparent), transparent 42%, var(--gilt-faint) 88%);
+ border: 1px solid var(--line-soft);
+}
+
+/* ---- 时间河卷章(复刻起居注「卷」分割) ---- */
+.timeline-volume {
+ display: flex; align-items: center; gap: 10px;
+ margin: 4px 0 12px; padding-left: 14px;
+ font-family: var(--font-serif); font-size: 12px; color: var(--gilt);
+}
+.timeline-volume::before, .timeline-volume::after {
+ content: ""; flex: 1; height: 1px;
+ background: linear-gradient(90deg, transparent, var(--gilt-dim));
+}
+.timeline-volume::after { background: linear-gradient(90deg, var(--gilt-dim), transparent); }
+
+/* ---- 阴阳平衡 gauge(复刻太极 gauge,映射朱批治理) ---- */
+.taiji-gauge { display: flex; align-items: center; gap: 16px; }
+.taiji-gauge svg { width: 128px; height: 76px; flex: none; }
+.taiji-gauge__label { fill: var(--text-3); font-size: 9px; }
+.taiji-gauge__val { font-family: var(--font-serif); font-size: 16px; }
+
+/* ============================================================
+ 思考块(thinking block · 契约候选):模型思考摘要可折叠 + 强度呈现
+ 边界:展示的是模型输出的可读摘要——会话内可见、可丢弃重建;
+ 不写入长期记录、不进入诊断导出(隐藏推理不采集,宪章不变)。
+ ============================================================ */
+.think-block {
+ border: 1px dashed var(--line);
+ border-radius: var(--radius);
+ background: transparent;
+ overflow: hidden;
+}
+.think-block summary {
+ display: flex;
+ align-items: center;
+ gap: 9px;
+ padding: 9px 14px;
+ cursor: pointer;
+ list-style: none;
+ font-size: 12.5px;
+ color: var(--text-2);
+ user-select: none;
+}
+.think-block summary::-webkit-details-marker { display: none; }
+.think-block summary:hover { color: var(--text-1); background: var(--bg-sunken); }
+.think-block summary svg { width: 14px; height: 14px; flex: none; color: var(--text-3); }
+.think-block summary .chev { margin-left: auto; transition: transform 0.18s ease; }
+.think-block[open] summary .chev { transform: rotate(90deg); }
+.think-block summary .think-meta { font-size: 11px; color: var(--text-3); }
+.think-block summary .think-effort {
+ font-size: 10.5px;
+ border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
+ color: var(--accent);
+ border-radius: 4px;
+ padding: 0 6px;
+}
+.think-body {
+ padding: 4px 16px 12px 39px;
+ font-size: 12.5px;
+ color: var(--text-3);
+ line-height: 1.75;
+ border-left: 2px solid var(--line-soft);
+ margin: 0 14px 0 14px;
+}
+.think-body p { margin-bottom: 0.6em; }
+.think-body p:last-child { margin-bottom: 0; }
+.think-note { padding: 8px 16px 12px 39px; font-size: 11px; color: var(--text-3); opacity: 0.85; }
+
+/* 流式思考中:呼吸点 + 实时摘要(底部渐隐,完成后折叠) */
+.think-block.is-thinking { border-color: color-mix(in srgb, var(--accent) 40%, transparent); }
+.think-block.is-thinking summary { color: var(--accent-strong); }
+.think-dot {
+ width: 8px; height: 8px; border-radius: 50%; flex: none;
+ background: var(--accent);
+ animation: think-breathe 1.6s ease-in-out infinite;
+}
+@keyframes think-breathe {
+ 0%, 100% { box-shadow: 0 0 2px 1px color-mix(in srgb, var(--accent) 40%, transparent); opacity: .6; }
+ 50% { box-shadow: 0 0 8px 3px color-mix(in srgb, var(--accent) 60%, transparent); opacity: 1; }
+}
+.think-live {
+ max-height: 108px;
+ overflow: hidden;
+ -webkit-mask-image: linear-gradient(180deg, transparent, #000 22%);
+ mask-image: linear-gradient(180deg, transparent, #000 22%);
+}
+.think-cursor {
+ display: inline-block;
+ width: 7px; height: 13px;
+ background: var(--accent);
+ vertical-align: -2px;
+ margin-left: 2px;
+ border-radius: 1px;
+ animation: blink 1.05s step-end infinite;
+}
+@media (prefers-reduced-motion: reduce) {
+ .think-dot, .think-cursor { animation: none; }
+}
+
+/* 深虑伴随控件:点击深虑 = 开启深度思考;开启后右侧出现力度选项(独立控件,只调档);
+ 再次点击深虑 = 关闭,力度选项随之消失 */
+.effort-seg {
+ display: inline-flex;
+ background: var(--bg-sunken);
+ border: 1px solid var(--line-soft);
+ border-radius: 999px;
+ padding: 2px;
+ gap: 2px;
+ animation: effort-in 0.2s ease;
+}
+.effort-seg[hidden] { display: none; }
+@keyframes effort-in {
+ from { opacity: 0; transform: translateX(-4px); }
+ to { opacity: 1; transform: none; }
+}
+.effort-seg button {
+ border: none;
+ background: none;
+ cursor: pointer;
+ font-family: var(--font-ui);
+ font-size: 11.5px;
+ color: var(--text-3);
+ padding: 2.5px 11px;
+ border-radius: 999px;
+}
+.effort-seg button:hover { color: var(--text-1); }
+.effort-seg button[aria-checked="true"] { background: var(--accent-soft); color: var(--accent-strong); }
+@media (prefers-reduced-motion: reduce) { .effort-seg { animation: none; } }
+
+/* 管理面侧栏底部返回入口 */
+.admin-back { border-top: 1px solid var(--line-soft); padding: 10px 16px; }
+.admin-back a { font-size: 12px; color: var(--text-3); display: flex; align-items: center; gap: 6px; }
+.admin-back a:hover { color: var(--accent-strong); }
+.app--fold .admin-back { display: none; }
diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx
index 3bdf35e..978f8be 100644
--- a/frontend/src/app/layout.tsx
+++ b/frontend/src/app/layout.tsx
@@ -1,14 +1,27 @@
import type { Metadata } from "next";
+import "./globals.css";
+import { ThemeProvider } from "@/lib/theme";
+import { QueryProvider } from "@/lib/query";
export const metadata: Metadata = {
title: "魂镜 Hunjing",
description: "长期 AI 角色、Agent 与创作社区平台",
};
+/** 主题初始化(防闪):与 ThemeProvider 的 localStorage 键一致 */
+const themeInit = `try{var t=localStorage.getItem("hunjing-theme");if(t==="light"||t==="dark")document.documentElement.setAttribute("data-theme",t)}catch(e){}`;
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
-
- {children}
+
+
+
+
+
+
+ {children}
+
+
);
}
diff --git a/frontend/src/app/login/page.tsx b/frontend/src/app/login/page.tsx
new file mode 100644
index 0000000..b6c2bdc
--- /dev/null
+++ b/frontend/src/app/login/page.tsx
@@ -0,0 +1,139 @@
+"use client";
+
+import { Fingerprint, Key } from "lucide-react";
+import { useRouter } from "next/navigation";
+import { useState } from "react";
+import { api, ApiError, storeCsrf, storeSession } from "@/lib/api";
+import { ThemeButton } from "@/lib/theme";
+
+/** 登录页(StageShell 铜镜鉴心)——开发档:后端仅校验邮箱即签发会话 */
+export default function LoginPage() {
+ const router = useRouter();
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [pending, setPending] = useState(false);
+ const [error, setError] = useState(null);
+
+ async function submit(e: React.FormEvent) {
+ e.preventDefault();
+ setPending(true);
+ setError(null);
+ try {
+ const session = await api<{ csrf_token: string; user_id: string; role: string; email: string }>(
+ "/v1/auth/sessions",
+ { method: "POST", json: { email } },
+ );
+ storeCsrf(session.csrf_token);
+ storeSession(session);
+ router.push("/home");
+ } catch (err) {
+ setError(err instanceof ApiError ? err.message : "登录失败,请稍后再试");
+ } finally {
+ setPending(false);
+ }
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 镜中有镜 · 心外无心 · 汝来看此镜时 · 此镜亦照汝 · 铜镜本无字 · 照尽往来人 ·
+
+
+
+
+
+
+
+
+
+
+ 联调中 · 开发档登录
+
+ 原型目录
+
+
+
+
+
+
+
登入魂镜
+
长期 AI 角色、Agent 与创作社区平台
+
+
+
+
其他方式
+
+
+
+ 开发档:输入邮箱即可登入(自动建户)。正式档为 OAuth 2.1 安全会话;凭据只经 Header 传递。
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx
deleted file mode 100644
index 3289917..0000000
--- a/frontend/src/app/page.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-export default function Home() {
- return (
-
- 魂镜 Hunjing
- 工程骨架已建立;界面随 WP-03/WP-10 工作包实现。
-
- );
-}
diff --git a/frontend/src/app/register/page.tsx b/frontend/src/app/register/page.tsx
new file mode 100644
index 0000000..91f1945
--- /dev/null
+++ b/frontend/src/app/register/page.tsx
@@ -0,0 +1,133 @@
+"use client";
+
+import { useRouter } from "next/navigation";
+import { useState } from "react";
+import { api, ApiError, storeCsrf } from "@/lib/api";
+import { ThemeButton } from "@/lib/theme";
+
+/** 注册页(StageShell 铜镜鉴心) */
+export default function RegisterPage() {
+ const router = useRouter();
+ const [pending, setPending] = useState(false);
+ const [error, setError] = useState(null);
+
+ async function submit(e: React.FormEvent) {
+ e.preventDefault();
+ const email = (document.getElementById("reg-email") as HTMLInputElement).value;
+ if (!email) return;
+ setPending(true);
+ setError(null);
+ try {
+ const session = await api<{ csrf_token: string }>("/v1/auth/sessions", { method: "POST", json: { email } });
+ storeCsrf(session.csrf_token);
+ router.push("/home");
+ } catch (err) {
+ setError(err instanceof ApiError ? err.message : "创建失败");
+ } finally {
+ setPending(false);
+ }
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 幻由心生 · 镜随心转 · 一念起 · 万镜生 · 且向镜中看 · 那个是谁 ·
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
创建账户
+
化名与法定身份分离核验;随时可导出、撤权或删除
+
+
+
+
注册即创建你的镜域(个人数据域);角色、记忆与授权都归属镜域治理。
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/admin/AdminPanel.tsx b/frontend/src/components/admin/AdminPanel.tsx
new file mode 100644
index 0000000..d115564
--- /dev/null
+++ b/frontend/src/components/admin/AdminPanel.tsx
@@ -0,0 +1,120 @@
+"use client";
+
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+import { useState } from "react";
+import { api, ApiError, type ToolCall } from "@/lib/api";
+import { PanelState } from "@/lib/queries";
+
+interface ModerationCase {
+ case_id: string;
+ status: string;
+ report: { target_type?: string; target_id?: string; reason?: string } | null;
+ action_taken: string | null;
+ created_at: string;
+}
+
+/** 管理面数据区:治理案件(真实裁决)+ 运行观测 + 支持包 */
+export function AdminPanel() {
+ const qc = useQueryClient();
+ const isAdminDenied = false; // 由下方 PanelState 呈现 403
+ const cases = useQuery({
+ queryKey: ["moderation-cases"],
+ queryFn: () => api<{ data: ModerationCase[] }>("/v1/admin/moderation-cases"),
+ });
+ const metrics = useQuery({
+ queryKey: ["metrics"],
+ queryFn: async () => {
+ const res = await fetch("/api/v1/metrics");
+ if (!res.ok) throw new Error(`metrics ${res.status}`);
+ return res.text();
+ },
+ });
+ const [bundle, setBundle] = useState(null);
+ const resolve = useMutation({
+ mutationFn: ({ id, action }: { id: string; action: string }) =>
+ api(`/v1/admin/moderation-cases/${id}/resolutions`, {
+ method: "POST",
+ json: { action },
+ }),
+ onSuccess: () => qc.invalidateQueries({ queryKey: ["moderation-cases"] }),
+ });
+
+ return (
+
+
+
+ 司议治理案件 · 真实裁决
+
+ cases.refetch()} empty={(cases.data?.data ?? []).length === 0}>
+
+ {(cases.data?.data ?? []).map((c) => (
+
+
+
{c.case_id.slice(-10)} · {c.report?.target_type ?? ""}
+
+ {c.report?.reason ?? ""} · {new Date(c.created_at).toLocaleString("zh-CN", { hour12: false })}
+ {c.action_taken && ` · 已处置:${c.action_taken}`}
+
+
+ {c.status === "pending" ? (
+
+ resolve.mutate({ id: c.case_id, action: "content_removed" })}
+ >
+ 移除内容
+
+ resolve.mutate({ id: c.case_id, action: "dismissed" })}
+ >
+ 驳回
+
+
+ ) : (
+
+ {c.status}
+
+ )}
+
+ ))}
+
+
+
+
+
+
+ 运行观测/v1/metrics
+
+ metrics.refetch()}>
+ {metrics.data}
+
+
+
+
+
+ 支持包脱敏诊断导出
+
+
+ 支持包为脱敏导出:不包含消息明文、私箧、隐藏推理、密钥与签名 URL。
+
+ {
+ try {
+ const r = await api<{ id?: string }>("/v1/support/bundles", { method: "POST", json: {} });
+ setBundle(`已生成支持包 ${r.id ?? ""}`);
+ } catch (err) {
+ setBundle(err instanceof ApiError ? err.message : "生成失败");
+ }
+ }}
+ >
+ 生成支持包
+
+ {bundle && {bundle}
}
+
+
+ );
+}
diff --git a/frontend/src/components/characters/CharacterListPanel.tsx b/frontend/src/components/characters/CharacterListPanel.tsx
new file mode 100644
index 0000000..2b9722e
--- /dev/null
+++ b/frontend/src/components/characters/CharacterListPanel.tsx
@@ -0,0 +1,141 @@
+"use client";
+
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+import { useState } from "react";
+import { api, ApiError, type Character, type CursorPage } from "@/lib/api";
+
+/** 角色状态 → 徽章(稳定值来自后端,文化名前端本地化) */
+const STATUS_LABEL: Record = {
+ draft: { cn: "灵胚", plain: "草稿", tone: "warn" },
+ active: { cn: "临照", plain: "运行中", tone: "info" },
+ resting: { cn: "静修", plain: "停止运行", tone: "muted" },
+ archived: { cn: "归藏", plain: "已隐藏", tone: "muted" },
+ deceased: { cn: "死亡", plain: "永久闭合", tone: "muted" },
+};
+
+function StatusBadge({ status }: { status: string }) {
+ const label = STATUS_LABEL[status] ?? { cn: status, plain: "未知状态", tone: "muted" as const };
+ return (
+
+
+ {label.cn} · {label.plain}
+
+ );
+}
+
+/** 镜生左列:真实角色列表(未登入 / 空 / 有数据三态)+ 创建 */
+export function CharacterListPanel() {
+ const qc = useQueryClient();
+ const [name, setName] = useState("");
+ const [creating, setCreating] = useState(false);
+ const [error, setError] = useState(null);
+
+ const list = useQuery({
+ queryKey: ["characters"],
+ queryFn: () => api>("/v1/characters?limit=20"),
+ });
+
+ const create = useMutation({
+ mutationFn: (newName: string) =>
+ api("/v1/characters", { method: "POST", json: { name: newName, summary: "" } }),
+ onSuccess: () => {
+ setName("");
+ setCreating(false);
+ setError(null);
+ qc.invalidateQueries({ queryKey: ["characters"] });
+ },
+ onError: (err) => setError(err instanceof ApiError ? err.message : "创建失败"),
+ });
+
+ if (list.isError) {
+ const status = list.error instanceof ApiError ? list.error.status : 0;
+ if (status === 401 || status === 403) {
+ return (
+
+ );
+ }
+ return (
+
+
读取失败
+
+ {list.error instanceof ApiError ? list.error.message : "请稍后重试"}
+
+
list.refetch()}>重试
+
+ );
+ }
+
+ if (list.isPending) {
+ return (
+
+ );
+ }
+
+ const items = list.data?.data ?? [];
+
+ return (
+
+
+
+ 镜生{items.length} 位角色
+
+ setCreating((v) => !v)}>创建
+
+
+ {items.length === 0 && (
+
+
镜域里还没有角色。
+
从一位灵胚(角色草稿)开始——可删除,不等于角色死亡。
+
+ )}
+
+ {items.map((c) => (
+
+
{c.name.slice(0, 1)}
+
+
{c.name}
+
+ {STATUS_LABEL[c.status]?.cn === "灵胚" ? "灵胚 · 未启镜" : `道体 ${c.current_version_id ? "有版本" : "待定"}`} ·{" "}
+ {new Date(c.created_at).toLocaleDateString("zh-CN")}
+
+
+
+
+ ))}
+
+ {creating && (
+
+ )}
+ {error &&
{error}
}
+
+
+
+ 随缘 · 生成匿名灵胚候选(待接入)
+
+
+
+ );
+}
diff --git a/frontend/src/components/characters/CharactersPagePanel.tsx b/frontend/src/components/characters/CharactersPagePanel.tsx
new file mode 100644
index 0000000..9cb5ed8
--- /dev/null
+++ b/frontend/src/components/characters/CharactersPagePanel.tsx
@@ -0,0 +1,218 @@
+"use client";
+
+import { useQuery } from "@tanstack/react-query";
+import { useState } from "react";
+import { api, ApiError, type Character, type CursorPage } from "@/lib/api";
+import { PanelState } from "@/lib/queries";
+
+/** 角色状态 → 徽章(稳定值来自后端,文化名前端本地化) */
+const STATUS_LABEL: Record = {
+ draft: { cn: "灵胚", plain: "草稿", tone: "warn" },
+ active: { cn: "临照", plain: "运行中", tone: "info" },
+ resting: { cn: "静修", plain: "停止运行", tone: "muted" },
+ archived: { cn: "归藏", plain: "已隐藏", tone: "muted" },
+ deceased: { cn: "死亡", plain: "永久闭合", tone: "muted" },
+};
+
+function StatusBadge({ status }: { status: string }) {
+ const label = STATUS_LABEL[status] ?? { cn: status, plain: "未知状态", tone: "muted" as const };
+ return (
+
+
+ {label.cn} · {label.plain}
+
+ );
+}
+
+/** 镜生页:左列真实列表 + 右列真实详情(选中联动) */
+export function CharactersPagePanel() {
+ const qc_client = { key: "characters" };
+ const [selectedId, setSelectedId] = useState(null);
+ const [name, setName] = useState("");
+ const [creating, setCreating] = useState(false);
+ const [error, setError] = useState(null);
+
+ const list = useQuery({
+ queryKey: [qc_client.key],
+ queryFn: () => api>("/v1/characters?limit=20"),
+ });
+ const detail = useQuery({
+ queryKey: ["character", selectedId],
+ enabled: !!selectedId,
+ queryFn: () => api(`/v1/characters/${selectedId}`),
+ });
+
+ async function create(e: React.FormEvent) {
+ e.preventDefault();
+ if (!name.trim()) return;
+ setError(null);
+ try {
+ const c = await api("/v1/characters", { method: "POST", json: { name: name.trim(), summary: "" } });
+ setName("");
+ setCreating(false);
+ list.refetch();
+ setSelectedId(c.id);
+ } catch (err) {
+ setError(err instanceof ApiError ? err.message : "创建失败");
+ }
+ }
+
+ async function activate(id: string) {
+ try {
+ await api(`/v1/characters/${id}/activations`, { method: "POST", json: {} });
+ list.refetch();
+ detail.refetch();
+ } catch (err) {
+ setError(err instanceof ApiError ? err.message : "启镜失败");
+ }
+ }
+
+ const items = list.data?.data ?? [];
+
+ return (
+
+
+
+
+
+ 镜生{items.length} 位角色
+
+ setCreating((v) => !v)}>创建
+
+
+ {list.isPending &&
正在读取…
}
+ {list.isError && (
+
+ {(list.error as ApiError).status === 401 || (list.error as ApiError).status === 403 ? (
+ <>
+
尚未登入,无法读取你的镜域。
+
去登入
+ >
+ ) : (
+ <>
+
读取失败
+
list.refetch()}>重试
+ >
+ )}
+
+ )}
+
+ {items.length === 0 && !list.isPending && !list.isError && (
+
+
镜域里还没有角色。
+
从一位灵胚(角色草稿)开始——可删除,不等于角色死亡。
+
+ )}
+
+ {items.map((c) => (
+
setSelectedId(c.id)}
+ role="button"
+ tabIndex={0}
+ onKeyDown={(e) => { if (e.key === "Enter") setSelectedId(c.id); }}
+ aria-current={selectedId === c.id ? "true" : undefined}
+ >
+
{c.name.slice(0, 1)}
+
+
{c.name}
+
+ {c.status === "draft" ? "灵胚 · 未启镜" : c.status} · {new Date(c.created_at).toLocaleDateString("zh-CN")}
+
+
+
+
+ ))}
+
+ {creating && (
+
+ )}
+ {error &&
{error}
}
+
+
+
+
+ {!selectedId && (
+
+
+ 镜生选择一位角色查看详情
+
+
+ 详情包含道体、内驱力(可编辑)、大周天与生命周期操作——全部来自真实接口。
+
+
+ )}
+ {selectedId && (
+
detail.refetch()}>
+ {(() => {
+ const c = detail.data as Character;
+ if (!c) return null;
+ return (
+
+
+
+
{c.name.slice(0, 1)}
+
+
+ {c.name}
+
+
{c.id}
+
+
+ {c.status === "draft" && (
+
activate(c.id)}>启镜 · 正式创建
+ )}
+
进入镜树
+
+
+
+
+
+ 道体稳定身份基线
+
+
+ {Object.keys(c.identity_baseline ?? {}).length === 0
+ ? "道体尚未设定——启镜并完成初照自陈后形成首个版本。"
+ : JSON.stringify(c.identity_baseline, null, 2)}
+
+
+
+
+
+ 内驱力动机维度 · 可编辑(PATCH drives)
+
+
+ {(c.drives ?? []).length === 0 ? "尚未声明内驱力维度。" : JSON.stringify(c.drives, null, 2)}
+
+
+
+
+
+ 生命周期与数据删除分离
+
+
+ {["静修 · 停止运行", "归藏 · 隐藏可恢复", "复照 · 从旧版本重开"].map((op) => (
+ {op}
+ ))}
+ {["铸魂 · 重大重塑(强确认)", "死亡 · 永久闭合(冷静期)"].map((op) => (
+ {op}
+ ))}
+
+
+ 铸魂与死亡走强确认与谱系留痕;后端 lifecycle 端点(POST /v1/characters/{"{id}"}/{"{action}"})已具备,交互流程待接入。
+
+
+
+ );
+ })()}
+
+ )}
+
+
+ );
+}
diff --git a/frontend/src/components/collaboration/CollaborationPanel.tsx b/frontend/src/components/collaboration/CollaborationPanel.tsx
new file mode 100644
index 0000000..45f566f
--- /dev/null
+++ b/frontend/src/components/collaboration/CollaborationPanel.tsx
@@ -0,0 +1,57 @@
+"use client";
+
+import { useState } from "react";
+import { useCreateWorkspace } from "@/lib/queries";
+import type { Workspace } from "@/lib/api";
+
+export function CollaborationPanel() {
+ const create = useCreateWorkspace();
+ const [name, setName] = useState("");
+ const [created, setCreated] = useState(null);
+
+ return (
+
+
+ 成员通过同一明确契约协作;编辑、发布、市场上架与财务权限分别授予。
+
+
+
+ 同契台创建协作空间
+
+
+ {create.isError && (
+
{(create.error as { message?: string }).message}
+ )}
+ {created && (
+
+
+
契
+
+
{created.name}
+
{created.id}
+
+
已创建
+
+
+ )}
+
+ 成员、项目分支与审稿接口(邀请 / 版本 / 合卷)已具备;空间列表端点待补齐后在此展示全部空间。
+
+
+
+ );
+}
diff --git a/frontend/src/components/community/CommunityPanel.tsx b/frontend/src/components/community/CommunityPanel.tsx
new file mode 100644
index 0000000..7931ed6
--- /dev/null
+++ b/frontend/src/components/community/CommunityPanel.tsx
@@ -0,0 +1,67 @@
+"use client";
+
+import { useState } from "react";
+import { PanelState, useContents, useCreateContent } from "@/lib/queries";
+import type { CommunityContent } from "@/lib/api";
+
+export function CommunityPanel() {
+ const contents = useContents();
+ const create = useCreateContent();
+ const [title, setTitle] = useState("");
+ const list = contents.data?.data ?? [];
+
+ return (
+
+
+
发布动态 · 公开内容
+
+ {create.isError && (
+
{(create.error as { message?: string }).message}
+ )}
+
+
+
contents.refetch()} empty={list.length === 0}>
+ {list.map((c: CommunityContent) => (
+
+
+
镜
+
+
镜域 {c.author_domain_id.slice(-6)}
+
{new Date(c.created_at).toLocaleString("zh-CN", { hour12: false })}
+
+
+ 已刊 · {c.status}
+
+
+
+
+ 评论
+ 收藏
+ 鸣冤
+
+
+ ))}
+
+
+ );
+}
diff --git a/frontend/src/components/conversation/BlockRenderer.tsx b/frontend/src/components/conversation/BlockRenderer.tsx
new file mode 100644
index 0000000..ff71ff2
--- /dev/null
+++ b/frontend/src/components/conversation/BlockRenderer.tsx
@@ -0,0 +1,170 @@
+import type { ContentBlockLike } from "@/lib/api";
+
+/**
+ * ContentBlock 判别联合渲染器(静态阶段)。
+ * 契约要求:穷尽判别、未知类型走兼容占位不崩溃;Markdown 未闭合结构保持源码态。
+ * 完整的分级实时渲染(Shiki/KaTeX/Mermaid Worker)按 frontend-content-rendering.md 在后续接入。
+ */
+
+function src(b: ContentBlockLike, ...keys: string[]): string {
+ for (const k of keys) {
+ const v = b[k];
+ if (typeof v === "string" && v) return v;
+ }
+ return "";
+}
+
+function TextBlock({ b }: { b: ContentBlockLike }) {
+ const text = src(b, "text", "source");
+ if (b.status === "streaming") {
+ return {text}
;
+ }
+ return {text}
;
+}
+
+function CodeBlock({ b }: { b: ContentBlockLike }) {
+ return (
+
+
{src(b, "language") || "code"} {src(b, "filename") && {src(b, "filename")} }
+
{src(b, "source", "code", "text")}
+
+ );
+}
+
+function QuoteBlock({ b }: { b: ContentBlockLike }) {
+ return (
+
+ {src(b, "text", "content")}
+ {src(b, "source") && 引文锚 · {src(b, "source")} }
+
+ );
+}
+
+function TableBlock({ b }: { b: ContentBlockLike }) {
+ const columns = Array.isArray(b.columns) ? (b.columns as string[]) : [];
+ const rows = Array.isArray(b.rows) ? (b.rows as unknown[][]) : [];
+ return (
+
+
{src(b, "caption") || "表格"}
+
+
+ {columns.length > 0 && (
+ {columns.map((c, i) => {c} )}
+ )}
+
+ {rows.map((row, i) => (
+ {row.map((cell, j) => {String(cell ?? "")} )}
+ ))}
+
+
+
+
+ );
+}
+
+function CalloutBlock({ b }: { b: ContentBlockLike }) {
+ const kind = src(b, "kind") || "info";
+ const tone = kind === "danger" ? "var(--err)" : kind === "warning" || kind === "success" ? "var(--warn)" : "var(--accent)";
+ return (
+
+
+ {src(b, "title") &&
{src(b, "title")} }
+
{src(b, "content")}
+
+
+ );
+}
+
+function MathBlock({ b }: { b: ContentBlockLike }) {
+ return (
+
+
+ {src(b, "source")}
+ {src(b, "fallback_text") && fallback:{src(b, "fallback_text")} }
+
+
+ );
+}
+
+function MermaidBlock({ b }: { b: ContentBlockLike }) {
+ return (
+
+
mermaid 完成后以 strict mode 隔离渲染
+
{src(b, "source")}
+
+ );
+}
+
+function MediaBlock({ b }: { b: ContentBlockLike }) {
+ const label = src(b, "file_id") ? `文件 ${src(b, "file_id").slice(-8)}` : "媒体";
+ return (
+
+
{b.type}{src(b, "filename") && {src(b, "filename")} }
+
{label} · 资源就绪后加载(短期授权 URL 换取待接入)
+
+ );
+}
+
+function StatusBlock({ b }: { b: ContentBlockLike }) {
+ const label: Record = {
+ tool_call: "工具调用", tool_result: "工具结果", approval_request: "请命",
+ agent_status: "Agent 状态", artifact: "产物", citation: "引文", error: "错误",
+ };
+ return (
+
+
+ {label[b.type] ?? b.type}
+ {src(b, "tool_name", "name", "title")}
+ {src(b, "status") && {src(b, "status")} }
+
+
+
内容 {JSON.stringify(b).slice(0, 160)}
+
+
+ );
+}
+
+export function BlockRenderer({ block }: { block: ContentBlockLike }) {
+ switch (block.type) {
+ case "text":
+ case "markdown":
+ return ;
+ case "code":
+ return ;
+ case "quote":
+ return ;
+ case "table":
+ return ;
+ case "callout":
+ return ;
+ case "math":
+ return ;
+ case "mermaid":
+ return ;
+ case "image":
+ case "video":
+ case "audio":
+ case "file":
+ case "gallery":
+ case "avatar":
+ return ;
+ case "tool_call":
+ case "tool_result":
+ case "approval_request":
+ case "agent_status":
+ case "artifact":
+ case "citation":
+ case "error":
+ return ;
+ default:
+ return (
+
+
+
+ 当前客户端暂不支持「{block.type}」块——已按兼容占位处理,内容不丢失。
+
+
+
+ );
+ }
+}
diff --git a/frontend/src/components/conversation/ConvLayout.tsx b/frontend/src/components/conversation/ConvLayout.tsx
new file mode 100644
index 0000000..b285e04
--- /dev/null
+++ b/frontend/src/components/conversation/ConvLayout.tsx
@@ -0,0 +1,86 @@
+"use client";
+
+import { ChevronsLeft } from "lucide-react";
+import { useState } from "react";
+
+/**
+ * 镜树三栏布局:左右面板可折叠,内容区自适应放宽(773 → 1000 → 1140)。
+ * 折叠时浮动恢复按钮出现在消息流顶角。
+ */
+export function ConvLayout({
+ side,
+ main,
+ ctx,
+}: {
+ side: React.ReactNode;
+ main: React.ReactNode;
+ ctx: React.ReactNode;
+}) {
+ const [noLeft, setNoLeft] = useState(false);
+ const [noRight, setNoRight] = useState(false);
+ const cls = ["conv-layout", noLeft ? "no-left" : "", noRight ? "no-right" : ""]
+ .filter(Boolean)
+ .join(" ");
+
+ return (
+
+
+
+
梧
+
+
setNoLeft(true)}
+ aria-label="收起会话列表"
+ title="收起会话列表"
+ style={{ marginLeft: 4 }}
+ >
+
+
+
+ {side}
+
+
+ setNoLeft(false)}
+ aria-label="展开会话列表"
+ title="展开会话列表"
+ >
+
+
+ setNoRight(false)}
+ aria-label="展开分支面板"
+ title="展开分支与上下文面板"
+ >
+
+
+ {main}
+
+
+
+ );
+}
diff --git a/frontend/src/components/conversation/ConversationPanel.tsx b/frontend/src/components/conversation/ConversationPanel.tsx
new file mode 100644
index 0000000..840b139
--- /dev/null
+++ b/frontend/src/components/conversation/ConversationPanel.tsx
@@ -0,0 +1,382 @@
+"use client";
+
+import { useQueryClient } from "@tanstack/react-query";
+import { Send } from "lucide-react";
+import { useEffect, useState } from "react";
+import { api } from "@/lib/api";
+import { BlockRenderer } from "@/components/conversation/BlockRenderer";
+import { EffortControl, SearchPill } from "@/components/conversation/EffortControl";
+import { ConvLayout } from "@/components/conversation/ConvLayout";
+import {
+ useConversation,
+ useCreateConversation,
+ useCreateToolCall,
+ useMessages,
+ useRunEvents,
+ useSendMessage,
+} from "@/lib/queries";
+import type { Character, CursorPage, Message } from "@/lib/api";
+
+function MessageRow({ msg }: { msg: Message }) {
+ if (msg.role === "user") {
+ return (
+
+
+ 问节
+ {msg.id.slice(-6)}
+ {new Date(msg.created_at).toLocaleString("zh-CN", { hour12: false })}
+
+
+ {msg.content.map((b, i) => (
+
+ ))}
+
+
+ );
+ }
+ return (
+
+
+ 镜
+ {msg.role === "assistant" ? "角色" : msg.role}
+ {msg.canonical_scope && (
+
+
+ 归真 · {msg.canonical_scope}
+
+ )}
+
+
+
+
{msg.content.map((b, i) => )}
+
+
+
+ );
+}
+
+export function ConversationPanel() {
+ const qc = useQueryClient();
+ const [convId, setConvId] = useState(null);
+ const [draft, setDraft] = useState("");
+ const [activeRunId, setActiveRunId] = useState(null);
+ const [title, setTitle] = useState("");
+ const [characterId, setCharacterId] = useState("");
+ const [effortHint, setEffortHint] = useState("深虑 · 中");
+ const [error, setError] = useState(null);
+
+ // URL ?c= 携带当前会话
+ useEffect(() => {
+ const c = new URLSearchParams(window.location.search).get("c");
+ if (c) setConvId(c);
+ }, []);
+
+ const characters = useQuerySafe("/v1/characters?limit=20");
+ const conversation = useConversation(convId);
+ const messages = useMessages(convId);
+ const send = useSendMessage(convId);
+ const create = useCreateConversation();
+
+ function openConversation(id: string) {
+ setConvId(id);
+ window.history.replaceState(null, "", `/conversation?c=${id}`);
+ }
+
+ async function submit(e: React.FormEvent) {
+ e.preventDefault();
+ if (!draft.trim() || !convId) return;
+ setError(null);
+ try {
+ const result = await send.mutateAsync(draft.trim());
+ setDraft("");
+ if (result?.run?.id) setActiveRunId(result.run.id);
+ } catch (err) {
+ setError((err as { message?: string }).message ?? "发送失败");
+ }
+ }
+
+ const items = (characters.data as Character[] | undefined) ?? [];
+
+ useEffect(() => {
+ if (!characterId && items.length > 0) setCharacterId(items[0].id);
+ }, [items, characterId]);
+
+ return (
+
+
+ {convId && (
+
+
+
+ {String((conversation.data as { title?: string } | undefined)?.title ?? "当前对话")}
+
+ 会话列表端点待 WP-03 补齐,当前按会话直达
+
+ )}
+ >
+ }
+ main={
+ <>
+
+
+ {!convId && (
+
+ 从左侧选择角色并开始一棵新的镜树——分支、候答与界域切换随消息数据启用。
+
+ )}
+ {convId && messages.isPending &&
正在读取消息…
}
+ {convId && messages.isError && (
+
+ 读取失败:{(messages.error as { message?: string }).message ?? "请重试"}
+
+ )}
+ {convId &&
+ ((messages.data?.data.length ?? 0) === 0) &&
+ !messages.isPending && (
+
+ 这棵镜树还没有消息——在下方呈镜第一句。
+
+ )}
+ {messages.data?.data.map((m) =>
)}
+
{ messages.refetch(); if (finalStatus !== "failed") setActiveRunId(null); }} />
+
+
+
+
+
+ {error &&
{error}
}
+
+ 模型 会话临时(网关路由待 WP-05 暴露选择端点)
+ 思考 {effortHint} (点深虑开关,右侧调力度)
+
+
+
+ >
+ }
+ ctx={
+ <>
+
+
+ 镜枝分支结构
+
+ {convId ? (
+
+ ) : (
+ 开始会话后显示真实分支。
+ )}
+
+
+
+ 上下文镜ContextSnapshot
+
+
+ 召回证据与省略项随 WP-04 ContextSnapshot 暴露后接入;当前仅呈现结构占位。
+
+
+ >
+ }
+ />
+ );
+}
+
+/** 分支树:真实 branches 数据(主脉/当前分支) */
+function BranchTree({ convId }: { convId: string }) {
+ const branches = useQuerySafe<{ id: string; is_current?: boolean; [k: string]: unknown }>(
+ `/v1/conversations/${convId}/branches`,
+ );
+ if (branches.pending) return 读取分支…
;
+ if (branches.error) return 分支读取失败
;
+ const list = branches.data ?? [];
+ if (list.length === 0)
+ return 尚无分支——重生成会产生候答分支。
;
+ return (
+
+ );
+}
+
+/** 轻量查询(面板内局部使用) */
+import { useQuery } from "@tanstack/react-query";
+function useQuerySafe(path: string) {
+ const q = useQuery({ queryKey: ["panel", path], queryFn: () => api | { data: T[] }>(path) });
+ const data = (q.data as CursorPage | undefined)?.data ?? ((q.data as { data?: T[] } | undefined)?.data as T[] | undefined);
+ return { pending: q.isPending, error: q.error, data, refetch: q.refetch };
+}
+
+
+/** Run 流式区:临时芽体(delta 累积)+ 失败卡 + 完成后由父级对账权威快照 */
+function RunStream({ runId, onDone }: { runId: string | null; onDone: (finalStatus: string | null) => void }) {
+ const stream = useRunEvents(runId, onDone);
+ if (!runId) return null;
+ return (
+
+
+ 镜
+ 临时芽体
+
+
+ {stream.done
+ ? stream.finalStatus === "failed" ? `受阻 · ${stream.runStatus}` : `已成 · ${stream.runStatus}`
+ : `运转 · ${stream.runStatus}`}
+ {stream.runStatus}
+
+
+
+ {stream.blocks.map((b) => (
+
+ {b.status === "streaming" && (
+
+ 临时芽体
+ 流式生成中 · 完成后才固化
+ {b.type}
+
+ )}
+
+ {b.status === "failed" ? (
+
+ 受阻 · 失败:{b.error}
+ {b.error === "no_endpoint_available" && (
+
+ 没有可用的模型端点——在神识阁配置端点(WP-05)后重试;流中断不伪装完成。
+
+ )}
+
+ ) : (
+
+ {b.text}
+ {b.status === "streaming" && }
+
+ )}
+
+
+ ))}
+ {stream.blocks.length === 0 && !stream.done && (
+
正在连接事件流…
+ )}
+ {stream.done && stream.finalStatus && (
+
+ message.completed(final_status: {stream.finalStatus} )——已按权威快照对账。
+
+ )}
+
+
+ );
+}
+
+/** 工具调用发起(挂在当前会话最新 Run 或新 Run 上) */
+export function ToolCallForm({ runId }: { runId: string | null }) {
+ const create = useCreateToolCall(runId);
+ const [input, setInput] = useState('{"expression":"1+2*3"}');
+ if (!runId) return null;
+ return (
+
+ );
+}
diff --git a/frontend/src/components/conversation/EffortControl.tsx b/frontend/src/components/conversation/EffortControl.tsx
new file mode 100644
index 0000000..19a68ed
--- /dev/null
+++ b/frontend/src/components/conversation/EffortControl.tsx
@@ -0,0 +1,82 @@
+"use client";
+
+import { useState } from "react";
+
+/**
+ * 深虑控制:点击深虑 = 开启深度思考;开启后右侧伴随出现力度选项(独立控件,只调档);
+ * 再次点击 = 关闭,力度选项随之消失;重开保留上次档位。
+ */
+export function EffortControl({
+ effort,
+ onEffortChange,
+ onToggle,
+}: {
+ effort: string;
+ onEffortChange: (v: "低" | "中" | "高") => void;
+ onToggle: (on: boolean) => void;
+}) {
+ const [on, setOn] = useState(true);
+ const [val, setVal] = useState<"低" | "中" | "高">(effort as "低" | "中" | "高");
+
+ const toggle = () => {
+ const next = !on;
+ setOn(next);
+ onToggle(next);
+ };
+
+ return (
+ <>
+
+ 深虑
+
+
+ {(["低", "中", "高"] as const).map((level) => (
+ {
+ e.stopPropagation();
+ setVal(level);
+ onEffortChange(level);
+ }}
+ >
+ {level}
+
+ ))}
+
+
+
+
+ >
+ );
+}
+
+/** 博闻:独立开关(联网检索,走百工坊白名单) */
+export function SearchPill() {
+ const [on, setOn] = useState(false);
+ return (
+ setOn((v) => !v)}
+ title="博闻 · 本条消息启用联网检索(走百工坊白名单,点击开关)"
+ >
+ 博闻{on ? "开" : "关"}
+
+ );
+}
diff --git a/frontend/src/components/marketplace/MarketplacePanel.tsx b/frontend/src/components/marketplace/MarketplacePanel.tsx
new file mode 100644
index 0000000..9d52ca5
--- /dev/null
+++ b/frontend/src/components/marketplace/MarketplacePanel.tsx
@@ -0,0 +1,88 @@
+"use client";
+
+import { useState } from "react";
+import { PanelState, useCreateListing, useFinanceSummary, useListings } from "@/lib/queries";
+import type { Listing } from "@/lib/api";
+
+function yuan(minor: number | null, currency: string | null) {
+ if (minor == null) return "免费";
+ return `¥${(minor / 100).toFixed(2)}${currency ? ` ${currency.toUpperCase()}` : ""}`;
+}
+
+export function MarketplacePanel() {
+ const listings = useListings();
+ const finance = useFinanceSummary();
+ const create = useCreateListing();
+ const [title, setTitle] = useState("");
+ const [price, setPrice] = useState("");
+ const list = listings.data?.data ?? [];
+
+ return (
+
+
+ 购买的是版本与许可(市契),不是人格所有权。金额、分润与退契规则始终直白展示。
+
+
+
+
+
+
+ 云市商品
+
+ listings.refetch()} empty={list.length === 0}>
+
+ {list.map((l: Listing) => (
+
+
+ {l.title.slice(0, 1)}
+
+
+
{l.title}
+
{l.status === "published" ? "已发布" : l.status}
+
+ {l.price_minor ? yuan(l.price_minor, l.currency) : 免费 }
+
+
+
+ ))}
+
+
+
+
+
+ 结算创作者收益
+
+
+ {Object.entries(finance.data?.totals_minor ?? {}).map(([cur, minor]) => (
+
+
{cur.toUpperCase()} 余额
+
{yuan(minor, cur)}
+
+ ))}
+
四账平衡 · {String(finance.data?.balanced ?? false)}
+
+
+ Ledger 不可变,修正写反向分录;每日自动对账(支付 / 订单 / Ledger / 结算)。
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/memory/MemoryPanel.tsx b/frontend/src/components/memory/MemoryPanel.tsx
new file mode 100644
index 0000000..8882c73
--- /dev/null
+++ b/frontend/src/components/memory/MemoryPanel.tsx
@@ -0,0 +1,135 @@
+"use client";
+
+import { useState } from "react";
+import { api } from "@/lib/api";
+import { PanelState, useDecideCandidate, useMemories, useMemoryCandidates } from "@/lib/queries";
+import type { Memory, MemoryCandidate } from "@/lib/api";
+
+const KIND_LABEL: Record = {
+ fact: "事实", preference: "偏好", event: "事件", relationship: "关系",
+};
+
+function candidateText(c: MemoryCandidate) {
+ const p = c.payload as { text?: string; summary?: string; content?: string };
+ return p.text ?? p.summary ?? p.content ?? JSON.stringify(c.payload);
+}
+
+export function MemoryPanel() {
+ const candidates = useMemoryCandidates();
+ const memories = useMemories();
+ const decide = useDecideCandidate();
+ const [tab, setTab] = useState<"candidates" | "memories">("candidates");
+
+ const candList = candidates.data?.data ?? [];
+ const memList = memories.data?.data ?? [];
+
+ return (
+
+
+ setTab("candidates")}>
+ 候选审阅 {candList.length}
+
+ setTab("memories")}>
+ 明忆 {memList.length}
+
+
+
+
+ 归真的约束:候选经你确认才会影响未来上下文;境界内容只成世界知识候选,幻境内容须手动入忆。
+
+
+ {tab === "candidates" && (
+
candidates.refetch()}>
+ {candList.length === 0 ? (
+
+
当前没有待审候选。
+
+ 候选来自镜树对话与大周天评估——发一段对话后回到这里,或使用下方手动添加。
+
+
+
+ ) : (
+ candList.map((c) => (
+
+
+ {KIND_LABEL[c.kind] ?? c.kind}
+
+ 来源 · {c.source} · 敏感度 {c.sensitivity}
+
+ {c.id.slice(-8)}
+
+
{candidateText(c)}
+
+ decide.mutate({ id: c.id, approve: true })}>
+ 归真入忆
+
+ decide.mutate({ id: c.id, approve: false })}>
+ 忽略
+
+
+
+ ))
+ )}
+
+ )}
+
+ {tab === "memories" && (
+
memories.refetch()} empty={memList.length === 0}>
+
+ {memList.map((m: Memory) => (
+
+
{KIND_LABEL[m.kind] ?? m.kind}
+
+
{candidateText(m as unknown as MemoryCandidate)}
+
{m.source} · {new Date(m.created_at).toLocaleString("zh-CN", { hour12: false })}
+
+
已归真
+
+ ))}
+
+
+ )}
+
+ );
+}
+
+/** 手动添加候选(走同一审核流,用于联调验证闭环) */
+function ManualCandidate() {
+ const [text, setText] = useState("");
+ const [kind, setKind] = useState("preference");
+ const [state, setState] = useState<{ pending: boolean; error: string | null; ok: boolean }>({ pending: false, error: null, ok: false });
+
+ return (
+
+ );
+}
diff --git a/frontend/src/components/panels/index.tsx b/frontend/src/components/panels/index.tsx
new file mode 100644
index 0000000..93cb0af
--- /dev/null
+++ b/frontend/src/components/panels/index.tsx
@@ -0,0 +1,364 @@
+"use client";
+
+import { useQuery } from "@tanstack/react-query";
+import { api, clearCsrf, type Character, type CursorPage, type ToolCall } from "@/lib/api";
+import { PanelState, useApproveToolCall, useLogout, useMemoryCandidates, useToolCalls } from "@/lib/queries";
+
+/* ---------- 镜庭:聚合真实状态 ---------- */
+export function HomePanel() {
+ const characters = useQuery({
+ queryKey: ["characters"],
+ queryFn: () => api>("/v1/characters?limit=10"),
+ });
+ const candidates = useMemoryCandidates();
+ const toolCalls = useToolCalls();
+
+ const chars = characters.data?.data ?? [];
+ const candCount = candidates.data?.data.length ?? 0;
+ const calls = toolCalls.data?.data ?? [];
+
+ return (
+
+
+
+
镜庭
+
+ 镜庭只聚合状态,各域才是数据事实源
+
+
+
+
+
+
+
+ characters.refetch()} empty={chars.length === 0}>
+
+
+
+
+
+
+ 待你决定审批与候选
+
+
+
息壤候选 ×{candCount}
+
+
{candCount > 0 ? "有候选等待你的归真决定" : "暂无待审候选"}
+
归真后才会影响未来上下文
+
+
去审阅
+
+
+
+ );
+}
+
+/* ---------- 形仪:真实资产列表 ---------- */
+export function AvatarPanel() {
+ const avatars = useQuery({
+ queryKey: ["avatars"],
+ queryFn: () => api<{ data: { id: string; kind?: string; status?: string; [k: string]: unknown }[] }>("/v1/avatars"),
+ });
+ const list = avatars.data?.data ?? [];
+ return (
+
+
+ 资产库图像 / 声音 / Live2D / 3D
+
+ avatars.refetch()} empty={list.length === 0}>
+
+ {list.map((a) => (
+
+
+ {String(a.kind ?? "形").slice(0, 1)}
+
+
+ {a.id.slice(-10)}
+ {String(a.kind ?? "avatar")} · {String(a.status ?? "")}
+
+
+ ))}
+
+
+
+ );
+}
+
+/* ---------- 天工阁:能力与调用 ---------- */
+export function RunsPanel() {
+ const tools = useQuery({
+ queryKey: ["tools"],
+ queryFn: () => api<{ data: { name: string; description: string; risk_level: string; requires_approval: boolean }[] }>("/v1/tools"),
+ });
+ const calls = useToolCalls();
+ const toolList = tools.data?.data ?? [];
+ const callList = calls.data?.data ?? [];
+ return (
+
+
+ 天工是功能执行者,不冒充角色身份;工具结果只是观察,采用前需验证。
+
+
+ 工筹可用工具
+ tools.refetch()} empty={toolList.length === 0}>
+
+ {toolList.map((t) => (
+
+
+
{t.name}
+
{t.description}
+
+
+ {t.requires_approval ? "须请命" : "只读"}
+
+
+ ))}
+
+
+
+
+ 轨迹工具调用记录
+ calls.refetch()} empty={callList.length === 0}>
+
+ {callList.map((c: ToolCall) => (
+
+
+
{String(c.tool_name ?? c.id.slice(-8))}
+
{String(c.status ?? "")}
+
+
+ ))}
+
+
+
+
+ );
+}
+
+/* ---------- 神识阁:观测(模型路由选择端点待 WP-05) ---------- */
+export function GatewayPanel() {
+ const metrics = useQuery({
+ queryKey: ["metrics"],
+ queryFn: async () => {
+ const res = await fetch("/api/v1/metrics");
+ if (!res.ok) throw new Error(`metrics ${res.status}`);
+ return res.text();
+ },
+ });
+ return (
+
+
+ 神识是可替换推理能力,不等于角色身份。首个可见输出后不切换 Provider。
+
+
+ 运行观测/v1/metrics
+ metrics.refetch()}>
+ {metrics.data}
+
+
+
+ 逻辑模型与路由
+
+ 逻辑模型目录与路由继承链的读取端点属 WP-05(模型网关与运行观测),暴露后在此呈现五层继承与 BYOK 管理。
+
+
+
+ );
+}
+
+/* ---------- 司命台:审批与治理(真实数据 + 边界) ---------- */
+export function GovernancePanel() {
+ const calls = useToolCalls();
+ const approve = useApproveToolCall();
+ const list = calls.data?.data ?? [];
+ const history = useQuery({
+ queryKey: ["tool-calls-history"],
+ queryFn: async () => {
+ const [ok, denied] = await Promise.all([
+ api<{ data: ToolCall[] }>("/v1/tool-calls?status_filter=succeeded"),
+ api<{ data: ToolCall[] }>("/v1/tool-calls?status_filter=denied"),
+ ]);
+ return [...(ok.data ?? []), ...(denied.data ?? [])];
+ },
+ });
+ return (
+
+
+ 治理不支配角色或用户数据;隐藏推理、密钥与凭据永不进入诊断与审计展示。
+
+
+ 请命等待你确认的工具调用
+ calls.refetch()} empty={list.length === 0}>
+
+ {list.map((c: ToolCall) => {
+ const pending = String(c.status ?? "") === "pending_approval";
+ return (
+
+
+
{String(c.tool_name ?? c.id.slice(-8))}
+
+ {String(JSON.stringify(c.input ?? {}))}{c.output ? ` → ${String(c.output).slice(0, 60)}` : ""}
+
+
+ {pending ? (
+ <>
+
approve.mutate({ id: String(c.id), approve: true })}
+ >
+ 允许(朱批)
+
+
approve.mutate({ id: String(c.id), approve: false })}
+ >
+ 拒绝
+
+ >
+ ) : (
+
+ {String(c.status)}
+
+ )}
+
+ );
+ })}
+
+
+
+
+ 朱批留痕已处理的调用
+
+
+ {(history.data ?? []).map((c: ToolCall) => (
+
+
+
{String(c.tool_name)}
+
{String(JSON.stringify(c.output ?? c.input ?? {})).slice(0, 90)}
+
+
+ {String(c.status)}
+
+
+ ))}
+
+
+
+
+ 诊断授权双重授权
+
+ 读取 Run 详细诊断需普通认证之外叠加短期诊断令牌:绑定单个 Run、单次使用、默认 15 分钟。
+ 需诊断时从镜树/天工阁的具体 Run 发起(POST /v1/runs/{"{run_id}"}/diagnostic-grants)。
+
+
+
+ );
+}
+
+/* ---------- 设置:登出 + 财务(真实)+ 其余空态 ---------- */
+export function SettingsPanel() {
+ const logout = useLogout();
+ const finance = useQuery({
+ queryKey: ["finance-summary"],
+ queryFn: () => api<{ totals_minor: Record; balanced: boolean }>("/v1/finance/summary"),
+ });
+ return (
+
+
+ 会话
+
+
+
退出当前会话
+
登出并撤销 Session Cookie(DELETE /v1/auth/sessions/current)
+
+
{
+ await logout.mutateAsync().catch(() => undefined);
+ clearCsrf();
+ window.location.href = "/login";
+ }}
+ >
+ {logout.isPending ? "登出中…" : "登出"}
+
+
+
+
+ 结算财务摘要
+
+
+ {Object.entries(finance.data?.totals_minor ?? {}).map(([cur, minor]) => (
+
+
{cur.toUpperCase()}
+
{`¥${(minor / 100).toFixed(2)}`}
+
+ ))}
+
账平 · {String(finance.data?.balanced ?? false)}
+
+
+
+
+ 数据权与私箧
+
+ 导出、撤权、删除与私箧管理端点属后续工作包(WP-01 私箧 / WP-02 可携导出),暴露后在此接入。
+
+
+
+ );
+}
+
+/* ---------- 器谱:真实空态(设备 API 属 WP-10) ---------- */
+export function DevicesPanel() {
+ return (
+
+
+ 设备能力同时受系统权限与授契约束;采集有持续指示,可即时撤销。
+
+
+
形器登记设备
+
+ 设备登记与 DeviceGrant 端点属 WP-10(Web、桌面、移动与感知),尚未暴露。
+
+
+ 接入后此处显示:形器列表、授契(闻声 / 观屏 / 问路 · 范围与期限)、通感进行中、直白权限请求。
+
+
+
+ );
+}
diff --git a/frontend/src/components/plugins/PluginsPanel.tsx b/frontend/src/components/plugins/PluginsPanel.tsx
new file mode 100644
index 0000000..1ad75fa
--- /dev/null
+++ b/frontend/src/components/plugins/PluginsPanel.tsx
@@ -0,0 +1,77 @@
+"use client";
+
+import { useState } from "react";
+import { PanelState, useInstallPlugin, usePlugins, useTools } from "@/lib/queries";
+import type { PluginMeta, ToolMeta } from "@/lib/api";
+
+export function PluginsPanel() {
+ const plugins = usePlugins();
+ const tools = useTools();
+ const install = useInstallPlugin();
+ const [installed, setInstalled] = useState([]);
+ const pluginList = plugins.data?.data ?? [];
+ const toolList = tools.data?.data ?? [];
+
+ return (
+
+
+ 购买不等于安装,安装不等于启用,启用不等于授予新增权限。安全内核不可插件化。
+
+
+
+
+ 百工可用能力集合 · 能力可发现不等于已授权
+
+ tools.refetch()} empty={toolList.length === 0}>
+
+ {toolList.map((t: ToolMeta) => (
+
+
+
{t.name}
+
{t.description}
+
+
+ {t.requires_approval ? "须请命" : t.risk_level === "read_only" ? "只读" : t.risk_level}
+
+
+ {t.enabled ? "已启用" : "未启用"}
+
+
+ ))}
+
+
+
+
+
+
+ 插件注册表
+
+ plugins.refetch()} empty={pluginList.length === 0}>
+
+ {pluginList.map((p: PluginMeta) => (
+
+
+
{p.name}
+
{String(p.description ?? p.kind ?? "plugin")}
+
+
{
+ await install.mutateAsync(p.name);
+ setInstalled((prev) => [...prev, p.name]);
+ }}
+ >
+ {installed.includes(p.name) ? "已安装" : "安装"}
+
+
+ ))}
+
+
+ 插件注册表为空时表示本部署尚无已注册插件;安装不自动启用,启用不授予新增权限。
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/shell/AdminShell.tsx b/frontend/src/components/shell/AdminShell.tsx
new file mode 100644
index 0000000..e26c150
--- /dev/null
+++ b/frontend/src/components/shell/AdminShell.tsx
@@ -0,0 +1,55 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import { ADMIN_NAV } from "@/lib/nav";
+import { readSession } from "@/lib/api";
+import { Sidebar, useBrandEgg } from "@/components/shell/Sidebar";
+import { ThemeButton } from "@/lib/theme";
+import { RequireAuth } from "./RequireAuth";
+
+/** 管理面外壳:独立侧栏(锚点导航)+ 管理员身份 + 双人授权提示 */
+export function AdminShell({ children }: { children: React.ReactNode }) {
+ const [folded, setFolded] = useState(false);
+ const [foot, setFoot] = useState<{ char: string; name: string }>({ char: "司", name: "加载中…" });
+ useEffect(() => {
+ const s = readSession();
+ setFoot({ char: "司", name: s.role === "admin" ? `${s.email.split("@")[0] || "dev"} · 管理员` : "普通用户" });
+ }, []);
+ const { onBrandClick, brandState, toast } = useBrandEgg();
+
+ useEffect(() => {
+ try {
+ const saved = localStorage.getItem("hunjing-sidebar");
+ if (saved === "collapsed") setFolded(true);
+ } catch {
+ /* 忽略 */
+ }
+ }, []);
+
+ return (
+
+ {toast &&
{toast}
}
+
setFolded((v) => !v)}
+ onBrandClick={onBrandClick}
+ brandState={brandState}
+ brandSub="司命台 · 管理面"
+ foot={foot}
+ below={
+
+ }
+ />
+
+ {children}
+
+
+ );
+}
+
+export function AdminThemeButton() {
+ return ;
+}
diff --git a/frontend/src/components/shell/AppShell.tsx b/frontend/src/components/shell/AppShell.tsx
new file mode 100644
index 0000000..9e81aca
--- /dev/null
+++ b/frontend/src/components/shell/AppShell.tsx
@@ -0,0 +1,52 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import { usePathname } from "next/navigation";
+import { NAV_GROUPS } from "@/lib/nav";
+import { Sidebar, useBrandEgg } from "./Sidebar";
+import { RequireAuth } from "./RequireAuth";
+
+/** 应用外壳:主侧栏(可折叠,localStorage 记忆)+ 主区 */
+export function AppShell({ children }: { children: React.ReactNode }) {
+ const pathname = usePathname();
+ const [folded, setFolded] = useState(false);
+ const { onBrandClick, brandState, toast } = useBrandEgg();
+
+ useEffect(() => {
+ try {
+ const saved = localStorage.getItem("hunjing-sidebar");
+ if (saved === "collapsed") setFolded(true);
+ } catch {
+ /* 忽略 */
+ }
+ }, []);
+
+ const toggleFold = () => {
+ setFolded((prev) => {
+ const next = !prev;
+ try {
+ localStorage.setItem("hunjing-sidebar", next ? "collapsed" : "expanded");
+ } catch {
+ /* 忽略 */
+ }
+ return next;
+ });
+ };
+
+ return (
+
+ {toast &&
{toast}
}
+
+
+ {children}
+
+
+ );
+}
diff --git a/frontend/src/components/shell/RequireAuth.tsx b/frontend/src/components/shell/RequireAuth.tsx
new file mode 100644
index 0000000..fec20e4
--- /dev/null
+++ b/frontend/src/components/shell/RequireAuth.tsx
@@ -0,0 +1,46 @@
+"use client";
+
+import { useQuery } from "@tanstack/react-query";
+import { useRouter } from "next/navigation";
+import { api, ApiError, type CursorPage } from "@/lib/api";
+
+/**
+ * 登录守卫:进入应用外壳时校验会话(GET /v1/characters),
+ * 未登录(401/403)跳转登录页。校验期间显示轻量占位,不闪烁内容。
+ */
+export function RequireAuth({ children }: { children: React.ReactNode }) {
+ const router = useRouter();
+ const check = useQuery({
+ queryKey: ["auth-check"],
+ queryFn: () => api>("/v1/characters?limit=1"),
+ retry: false,
+ staleTime: 30_000,
+ });
+
+ if (check.isError) {
+ const status = check.error instanceof ApiError ? check.error.status : 0;
+ if (status === 401 || status === 403) {
+ router.replace("/login");
+ return (
+
+ );
+ }
+ return (
+
+ );
+ }
+
+ if (check.isPending) {
+ return (
+
+ );
+ }
+
+ return <>{children}>;
+}
diff --git a/frontend/src/components/shell/Sidebar.tsx b/frontend/src/components/shell/Sidebar.tsx
new file mode 100644
index 0000000..a9835c5
--- /dev/null
+++ b/frontend/src/components/shell/Sidebar.tsx
@@ -0,0 +1,136 @@
+"use client";
+
+import { ChevronsLeft } from "lucide-react";
+import { useRouter } from "next/navigation";
+import { useEffect, useRef, useState } from "react";
+import type { NavGroup } from "@/lib/nav";
+import { ThemeButton } from "@/lib/theme";
+
+/** 镜启彩蛋诗签(承接 main 分支旧版设计) */
+const EGG_VERSES = [
+ "镜中有镜,心外无心。",
+ "汝来看此镜时,此镜亦照汝。",
+ "一念起,万镜生。",
+ "镜非鉴物,乃鉴心也。",
+ "幻由心生,镜随心转。",
+ "铜镜本无字,照尽往来人。",
+ "且向镜中看,那个是谁?",
+];
+
+interface SidebarProps {
+ groups: NavGroup[];
+ activeHref?: string;
+ folded: boolean;
+ onToggleFold: () => void;
+ onBrandClick: (e: React.MouseEvent) => void;
+ brandState: { tick: boolean; pulse: boolean };
+ brandSub?: string;
+ foot?: { char: string; name: string };
+ below?: React.ReactNode;
+}
+
+export function Sidebar({ groups, activeHref, folded, onToggleFold, onBrandClick, brandState, brandSub = "Hunjing", foot, below }: SidebarProps) {
+ return (
+
+ );
+}
+
+/** 品牌彩蛋逻辑:单击延迟回目录;首击 + 4 次连点 = 镜启 */
+export function useBrandEgg() {
+ const router = useRouter();
+ const seq = useRef(0);
+ const last = useRef(0);
+ const timer = useRef | null>(null);
+ const [toast, setToast] = useState(null);
+ const [pulse, setPulse] = useState(false);
+ const [tick, setTick] = useState(false);
+
+ useEffect(() => () => { if (timer.current) clearTimeout(timer.current); }, []);
+
+ const onBrandClick = (e: React.MouseEvent) => {
+ e.preventDefault();
+ if (timer.current) clearTimeout(timer.current);
+ const now = Date.now();
+ const fast = now - last.current < 380;
+ last.current = now;
+ setTick(true);
+ setTimeout(() => setTick(false), 300);
+ if (fast) {
+ seq.current += 1;
+ if (seq.current >= 4) {
+ seq.current = 0;
+ setPulse(true);
+ setTimeout(() => setPulse(false), 1400);
+ const verse = EGG_VERSES[Math.floor(Math.random() * EGG_VERSES.length)];
+ setToast(`镜启 · ${verse}`);
+ setTimeout(() => setToast(null), 2500);
+ }
+ return;
+ }
+ seq.current = 0;
+ timer.current = setTimeout(() => router.push("/"), 380);
+ };
+
+ return { onBrandClick, brandState: { tick, pulse }, toast };
+}
diff --git a/frontend/src/components/shell/Topbar.tsx b/frontend/src/components/shell/Topbar.tsx
new file mode 100644
index 0000000..7dd4c55
--- /dev/null
+++ b/frontend/src/components/shell/Topbar.tsx
@@ -0,0 +1,19 @@
+import { ThemeButton } from "@/lib/theme";
+
+interface TopbarProps {
+ crumbs: React.ReactNode;
+ extra?: React.ReactNode;
+}
+
+/** 顶栏:面包屑 + 原型标记 + 主题切换 */
+export function Topbar({ crumbs, extra }: TopbarProps) {
+ return (
+
+ {crumbs}
+ {extra}
+
+ 静态原型 · 待联调
+
+
+ );
+}
diff --git a/frontend/src/components/ui/index.tsx b/frontend/src/components/ui/index.tsx
new file mode 100644
index 0000000..26d609a
--- /dev/null
+++ b/frontend/src/components/ui/index.tsx
@@ -0,0 +1,39 @@
+import type { ReactNode } from "react";
+
+/* ---------- 面包屑 ---------- */
+export function Crumbs({ cn, here }: { cn: string; here?: string }) {
+ return (
+ <>
+ {cn}
+ {here && (
+ <>
+ /
+ {here}
+ >
+ )}
+ >
+ );
+}
+
+/* ---------- 状态徽章:文化状态 · 直白状态 · 稳定值 ---------- */
+type BadgeTone = "ok" | "info" | "warn" | "err" | "muted";
+
+export function Badge({
+ tone = "muted",
+ children,
+ stable,
+ style,
+}: {
+ tone?: BadgeTone;
+ children: ReactNode;
+ stable?: string;
+ style?: React.CSSProperties;
+}) {
+ return (
+
+
+ {children}
+ {stable && {stable} }
+
+ );
+}
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts
new file mode 100644
index 0000000..fc1297f
--- /dev/null
+++ b/frontend/src/lib/api.ts
@@ -0,0 +1,228 @@
+/**
+ * API 客户端(开发档联调):
+ * - 同源 /api 前缀经 Next rewrites 代理至后端,Session Cookie 自动携带;
+ * - 写请求带 Hunjing-CSRF-Token(双提交,契约头名);
+ * - 错误统一映射为 ApiError(status + code + message),不匹配错误字符串。
+ */
+
+const BASE = "/api";
+
+export class ApiError extends Error {
+ constructor(
+ public status: number,
+ public code: string | null,
+ message: string,
+ ) {
+ super(message);
+ this.name = "ApiError";
+ }
+}
+
+function csrfToken(): string | null {
+ try {
+ return localStorage.getItem("hunjing-csrf");
+ } catch {
+ return null;
+ }
+}
+
+export function storeCsrf(token: string) {
+ try {
+ localStorage.setItem("hunjing-csrf", token);
+ } catch {
+ /* 忽略 */
+ }
+}
+
+export function storeSession(session: { role?: string; email?: string }) {
+ try {
+ localStorage.setItem("hunjing-role", session.role ?? "user");
+ localStorage.setItem("hunjing-email", session.email ?? "");
+ } catch {
+ /* 忽略 */
+ }
+}
+
+export function readSession(): { role: string; email: string } {
+ try {
+ return {
+ role: localStorage.getItem("hunjing-role") ?? "user",
+ email: localStorage.getItem("hunjing-email") ?? "",
+ };
+ } catch {
+ return { role: "user", email: "" };
+ }
+}
+
+export function clearCsrf() {
+ try {
+ localStorage.removeItem("hunjing-csrf");
+ localStorage.removeItem("hunjing-role");
+ localStorage.removeItem("hunjing-email");
+ } catch {
+ /* 忽略 */
+ }
+}
+
+export async function api(
+ path: string,
+ init: RequestInit & { json?: unknown } = {},
+): Promise {
+ const headers = new Headers(init.headers);
+ if (init.json !== undefined) {
+ headers.set("Content-Type", "application/json");
+ }
+ const method = (init.method ?? "GET").toUpperCase();
+ if (method !== "GET" && method !== "HEAD") {
+ const csrf = csrfToken();
+ if (csrf) headers.set("Hunjing-CSRF-Token", csrf);
+ }
+ const res = await fetch(`${BASE}${path}`, {
+ ...init,
+ headers,
+ body: init.json !== undefined ? JSON.stringify(init.json) : init.body,
+ credentials: "same-origin",
+ });
+ if (res.status === 204) return undefined as T;
+ const payload = await res.json().catch(() => null);
+ if (!res.ok) {
+ const detail = (payload as { detail?: string } | null)?.detail;
+ const code =
+ (payload as { error?: { code?: string } } | null)?.error?.code ?? null;
+ throw new ApiError(res.status, code, detail ?? `请求失败(${res.status})`);
+ }
+ return payload as T;
+}
+
+/* ---------- 联调用的领域类型(源自运行时 OpenAPI;正式契约冻结后切 openapi-typescript 生成) ---------- */
+
+export interface SessionInfo {
+ csrf_token: string;
+ user_id: string;
+ role: string;
+ email: string;
+}
+
+export interface Character {
+ id: string;
+ name: string;
+ status: string;
+ origin: string;
+ based_on_real_person: boolean;
+ identity_baseline: Record;
+ drives: unknown[];
+ current_version_id: string | null;
+ created_at: string;
+}
+
+export interface CursorPage {
+ data: T[];
+ next_cursor: string | null;
+ has_more: boolean;
+}
+
+/* ---------- 联调领域类型(按后端实测形状;正式契约冻结后切生成类型) ---------- */
+
+export interface Memory {
+ id: string;
+ character_id: string | null;
+ kind: string;
+ sensitivity: string;
+ realm: string | null;
+ payload: Record;
+ source: string;
+ status: string;
+ created_at: string;
+}
+
+export interface MemoryCandidate {
+ id: string;
+ character_id: string | null;
+ kind: string;
+ sensitivity: string;
+ realm: string | null;
+ payload: Record;
+ source: string;
+ status: string;
+ created_at: string;
+}
+
+export interface ContentBlockLike {
+ type: string;
+ [k: string]: unknown;
+}
+
+export interface Message {
+ id: string;
+ conversation_id: string;
+ branch_id: string | null;
+ role: string;
+ content: ContentBlockLike[];
+ parser_version: string | null;
+ created_at: string;
+ canonical_scope?: string | null;
+}
+
+export interface Conversation {
+ id: string;
+ title: string;
+ [k: string]: unknown;
+}
+
+export interface Branch {
+ id: string;
+ [k: string]: unknown;
+}
+
+export interface PluginMeta {
+ name: string;
+ [k: string]: unknown;
+}
+
+export interface CommunityContent {
+ id: string;
+ kind: string;
+ title: string | null;
+ body: Record;
+ visibility: string;
+ status: string;
+ author_domain_id: string;
+ created_at: string;
+}
+
+export interface Listing {
+ id: string;
+ title: string;
+ price_minor: number | null;
+ currency: string | null;
+ status: string;
+}
+
+export interface Workspace {
+ id: string;
+ name: string;
+ owner_domain_id: string;
+}
+
+export interface ToolCall {
+ id: string;
+ [k: string]: unknown;
+}
+
+export interface AvatarAsset {
+ id: string;
+ [k: string]: unknown;
+}
+
+export interface FinanceSummary {
+ totals_minor: Record;
+ balanced: boolean;
+}
+
+export interface ToolMeta {
+ name: string;
+ description: string;
+ risk_level: string;
+ requires_approval: boolean;
+ enabled: boolean;
+}
diff --git a/frontend/src/lib/nav.tsx b/frontend/src/lib/nav.tsx
new file mode 100644
index 0000000..41d602c
--- /dev/null
+++ b/frontend/src/lib/nav.tsx
@@ -0,0 +1,101 @@
+import {
+ Activity,
+ BookOpen,
+ Database,
+ FileText,
+ GitBranch,
+ Globe,
+ Home,
+ Layers,
+ Lock,
+ Monitor,
+ Package,
+ Route,
+ Server,
+ Shield,
+ ShoppingBag,
+ SlidersHorizontal,
+ SquareKanban,
+ User,
+ Users,
+} from "lucide-react";
+
+export interface NavItem {
+ href: string;
+ label: string;
+ plain: string;
+ icon: React.ComponentType;
+}
+
+export interface NavGroup {
+ title: string;
+ items: NavItem[];
+}
+
+export const NAV_GROUPS: NavGroup[] = [
+ {
+ title: "常用",
+ items: [
+ { href: "/home", label: "镜庭", plain: "首页", icon: Home },
+ { href: "/conversation", label: "镜树", plain: "对话", icon: GitBranch },
+ { href: "/characters", label: "镜生", plain: "角色", icon: User },
+ { href: "/memory", label: "息壤", plain: "记忆", icon: BookOpen },
+ { href: "/avatar", label: "形仪", plain: "形象", icon: Layers },
+ ],
+ },
+ {
+ title: "创造",
+ items: [
+ { href: "/agent-workspace", label: "天工阁", plain: "任务", icon: SquareKanban },
+ { href: "/plugins", label: "百工坊", plain: "插件", icon: Package },
+ { href: "/collaboration", label: "同契台", plain: "协作", icon: Users },
+ { href: "/model-gateway", label: "神识阁", plain: "模型", icon: Route },
+ ],
+ },
+ {
+ title: "世界",
+ items: [
+ { href: "/community", label: "雅集", plain: "社区", icon: Globe },
+ { href: "/marketplace", label: "云市", plain: "市场", icon: ShoppingBag },
+ ],
+ },
+ {
+ title: "治理与账户",
+ items: [
+ { href: "/governance", label: "司命台", plain: "治理", icon: Shield },
+ { href: "/devices", label: "器谱", plain: "设备", icon: Monitor },
+ { href: "/settings", label: "设置", plain: "账户", icon: SlidersHorizontal },
+ ],
+ },
+];
+
+export const ADMIN_NAV: NavGroup[] = [
+ {
+ title: "运营",
+ items: [
+ { href: "/admin#queue", label: "治理队列", plain: "", icon: SquareKanban },
+ { href: "/admin#tickets", label: "支持工单", plain: "3", icon: FileText },
+ { href: "/admin#users", label: "用户", plain: "", icon: Users },
+ { href: "/admin#perms", label: "权限管理", plain: "", icon: Shield },
+ ],
+ },
+ {
+ title: "部署",
+ items: [
+ { href: "/admin#health", label: "服务健康", plain: "", icon: Server },
+ { href: "/admin#backup", label: "迁移与备份", plain: "", icon: Database },
+ { href: "/admin#queue", label: "插件治理", plain: "", icon: Package },
+ ],
+ },
+ {
+ title: "观测",
+ items: [
+ { href: "/admin#slo", label: "SLO 与指标", plain: "", icon: Activity },
+ { href: "/admin#audit", label: "审计日志", plain: "", icon: Shield },
+ ],
+ },
+ {
+ title: "边界",
+ items: [{ href: "/governance", label: "治理边界说明", plain: "", icon: Lock }],
+ },
+];
diff --git a/frontend/src/lib/queries.tsx b/frontend/src/lib/queries.tsx
new file mode 100644
index 0000000..6b924aa
--- /dev/null
+++ b/frontend/src/lib/queries.tsx
@@ -0,0 +1,373 @@
+"use client";
+
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+import { useEffect, useRef, useState } from "react";
+import { api, type CursorPage } from "./api";
+
+/* ---------- 通用四态外壳:加载 / 未登入 / 失败 / 内容 ---------- */
+export function PanelState({
+ pending,
+ error,
+ onRetry,
+ children,
+ empty,
+}: {
+ pending: boolean;
+ error: unknown;
+ onRetry?: () => void;
+ children: React.ReactNode;
+ empty?: boolean;
+}) {
+ if (pending) {
+ return 正在读取…
;
+ }
+ if (error) {
+ const status = (error as { status?: number }).status ?? 0;
+ if (status === 401 || status === 403) {
+ return (
+
+
尚未登入,无法读取你的镜域数据。
+
去登入
+
+ );
+ }
+ return (
+
+
读取失败
+
+ {(error as { message?: string }).message ?? "请稍后重试"}
+
+ {onRetry &&
重试 }
+
+ );
+ }
+ if (empty) {
+ return (
+
+ 暂无数据——这里将显示你的真实内容。
+
+ );
+ }
+ return <>{children}>;
+}
+
+export function useUnauthorized(v: unknown) {
+ return (v as { status?: number })?.status === 401;
+}
+
+/* ---------- 会话(镜树) ---------- */
+export function useCreateConversation() {
+ const qc = useQueryClient();
+ return useMutation({
+ mutationFn: (body: { character_id?: string; title: string }) =>
+ api<{ id: string; title: string }>("/v1/conversations", { method: "POST", json: body }),
+ onSuccess: () => qc.invalidateQueries({ queryKey: ["conversation"] }),
+ });
+}
+
+export function useConversation(id: string | null) {
+ return useQuery({
+ queryKey: ["conversation", id],
+ enabled: !!id,
+ queryFn: () => api>(`/v1/conversations/${id}`),
+ });
+}
+
+export function useMessages(id: string | null) {
+ return useQuery({
+ queryKey: ["messages", id],
+ enabled: !!id,
+ queryFn: () => api>(`/v1/conversations/${id}/messages?limit=50`),
+ });
+}
+
+export interface SendMessageResult {
+ message: { id: string };
+ run: { id: string } | null;
+}
+
+export function useSendMessage(conversationId: string | null) {
+ const qc = useQueryClient();
+ return useMutation({
+ mutationFn: (text: string) =>
+ api(`/v1/conversations/${conversationId}/messages`, {
+ method: "POST",
+ json: { content: [{ type: "text", text }], start_run: true },
+ headers: { "Idempotency-Key": crypto.randomUUID() },
+ }),
+ onSuccess: () => qc.invalidateQueries({ queryKey: ["messages", conversationId] }),
+ });
+}
+
+/* ---------- Run SSE:块级事件消费(重放持久化流,sequence 单调) ---------- */
+export interface StreamBlock {
+ id: string;
+ type: string;
+ status: "streaming" | "completed" | "failed" | "cancelled";
+ text: string;
+ error?: string;
+}
+
+export interface RunStreamState {
+ runStatus: string;
+ blocks: StreamBlock[];
+ finalStatus: string | null;
+ done: boolean;
+}
+
+export function useRunEvents(runId: string | null, onDone?: (finalStatus: string | null) => void) {
+ const [state, setState] = useState({ runStatus: "connecting", blocks: [], finalStatus: null, done: false });
+ // onDone 经 ref 稳定化:避免内联回调导致事件流反复重连重置
+ const onDoneRef = useRef(onDone);
+ onDoneRef.current = onDone;
+
+ useEffect(() => {
+ if (!runId) return;
+ const ac = new AbortController();
+ setState({ runStatus: "connecting", blocks: [], finalStatus: null, done: false });
+
+ /** 契约事件归约(第 5 节):块级 started/delta/completed/failed + 终态 */
+ const applyEvent = (e: { type: string; data: Record }) => {
+ setState((prev) => {
+ const next = { ...prev, blocks: [...prev.blocks] };
+ switch (e.type) {
+ case "run.status.changed":
+ next.runStatus = String(e.data.status ?? "");
+ break;
+ case "content.block.started": {
+ const b = (e.data.block ?? {}) as { id?: string; type?: string };
+ next.blocks.push({ id: String(b.id ?? crypto.randomUUID()), type: String(b.type ?? "text"), status: "streaming", text: "" });
+ break;
+ }
+ case "content.block.delta": {
+ const d = e.data as { block_id?: string; delta?: { text?: string } };
+ const idx = next.blocks.findIndex((x) => x.id === d.block_id);
+ const chunk = String(d.delta?.text ?? "");
+ if (idx >= 0) next.blocks[idx] = { ...next.blocks[idx], text: next.blocks[idx].text + chunk };
+ break;
+ }
+ case "content.block.completed": {
+ const b = (e.data.block ?? {}) as { id?: string };
+ const idx = next.blocks.findIndex((x) => x.id === b.id);
+ if (idx >= 0) next.blocks[idx] = { ...next.blocks[idx], status: "completed" };
+ break;
+ }
+ case "content.block.failed": {
+ const b = (e.data.block ?? {}) as { id?: string; type?: string };
+ const err = String(e.data.error ?? "unknown");
+ const idx = next.blocks.findIndex((x) => x.id === b.id);
+ const failed = { id: String(b.id ?? crypto.randomUUID()), type: String(b.type ?? "text"), status: "failed" as const, text: "", error: err };
+ if (idx >= 0) next.blocks[idx] = failed;
+ else next.blocks.push(failed);
+ break;
+ }
+ case "message.completed":
+ next.finalStatus = String(e.data.final_status ?? "");
+ break;
+ default:
+ break;
+ }
+ if (
+ e.type === "message.completed" ||
+ (e.type === "run.status.changed" && ["completed", "failed", "cancelled"].includes(next.runStatus))
+ ) {
+ next.done = true;
+ }
+ return next;
+ });
+ };
+
+ const API_ORIGIN = process.env.NEXT_PUBLIC_API_ORIGIN ?? "http://127.0.0.1:8000";
+ const xhr = new XMLHttpRequest();
+ xhr.open("GET", `${API_ORIGIN}/v1/runs/${runId}/events`);
+ xhr.withCredentials = true;
+ xhr.setRequestHeader("Accept", "text/event-stream");
+ let processed = 0;
+ xhr.onprogress = () => {
+ // XHR 增量流式:responseText 为累积文本,按已处理游标切帧
+ const text = xhr.responseText;
+ const fresh = text.slice(processed);
+ processed = text.length;
+ const frames = fresh.split("\n\n");
+ for (const frame of frames) {
+ const line = frame.split("\n").find((l) => l.startsWith("data: "));
+ if (!line) continue;
+ try {
+ applyEvent(JSON.parse(line.slice(6)));
+ } catch {
+ /* 忽略无法解析的帧 */
+ }
+ }
+ };
+ xhr.onload = () => setState((prev) => (prev.done ? prev : { ...prev, done: true }));
+ xhr.onerror = () => {
+ setState((prev) => ({ ...prev, done: true, runStatus: prev.runStatus === "connecting" ? "recovering" : prev.runStatus }));
+ };
+ ac.signal.addEventListener("abort", () => xhr.abort());
+ xhr.send();
+
+ const doneCheck = setInterval(() => {
+ setState((prev) => {
+ if (prev.done) {
+ clearInterval(doneCheck);
+ xhr.abort();
+ onDoneRef.current?.(prev.finalStatus);
+ return prev;
+ }
+ return prev;
+ });
+ }, 250);
+
+ return () => {
+ ac.abort();
+ clearInterval(doneCheck);
+ };
+ }, [runId]);
+
+ return state;
+}
+
+/* ---------- 工具调用:发起与审批 ---------- */
+export function useCreateToolCall(runId: string | null) {
+ const qc = useQueryClient();
+ return useMutation({
+ mutationFn: ({ tool, input }: { tool: string; input: Record }) =>
+ api(`/v1/runs/${runId}/tool-calls`, {
+ method: "POST",
+ json: { tool_name: tool, input },
+ headers: { "Idempotency-Key": crypto.randomUUID() },
+ }),
+ onSuccess: () => qc.invalidateQueries({ queryKey: ["tool-calls"] }),
+ });
+}
+
+export function useApproveToolCall() {
+ const qc = useQueryClient();
+ return useMutation({
+ mutationFn: ({ id, approve }: { id: string; approve: boolean }) =>
+ api(`/v1/tool-calls/${id}/approvals`, { method: "POST", json: { approve } }),
+ onSuccess: () => qc.invalidateQueries({ queryKey: ["tool-calls"] }),
+ });
+}
+
+/* ---------- 记忆(息壤) ---------- */
+export function useMemories() {
+ return useQuery({
+ queryKey: ["memories"],
+ queryFn: () => api>("/v1/memories?limit=50"),
+ });
+}
+
+export function useMemoryCandidates() {
+ return useQuery({
+ queryKey: ["memory-candidates"],
+ queryFn: () => api<{ data: import("./api").MemoryCandidate[] }>("/v1/memories/candidates"),
+ });
+}
+
+export function useDecideCandidate() {
+ const qc = useQueryClient();
+ return useMutation({
+ mutationFn: ({ id, approve }: { id: string; approve: boolean }) =>
+ api(`/v1/memories/candidates/${id}/decisions`, { method: "POST", json: { approve } }),
+ onSuccess: () => {
+ qc.invalidateQueries({ queryKey: ["memory-candidates"] });
+ qc.invalidateQueries({ queryKey: ["memories"] });
+ },
+ });
+}
+
+/* ---------- 社区(雅集) ---------- */
+export function useContents() {
+ return useQuery({
+ queryKey: ["community-contents"],
+ queryFn: () => api>("/v1/community/contents?limit=20"),
+ });
+}
+
+export function useCreateContent() {
+ const qc = useQueryClient();
+ return useMutation({
+ mutationFn: (body: { kind: string; title: string }) =>
+ api("/v1/community/contents", { method: "POST", json: body }),
+ onSuccess: () => qc.invalidateQueries({ queryKey: ["community-contents"] }),
+ });
+}
+
+/* ---------- 市场(云市) ---------- */
+export function useListings() {
+ return useQuery({
+ queryKey: ["listings"],
+ queryFn: () => api<{ data: import("./api").Listing[] }>("/v1/marketplace/listings"),
+ });
+}
+
+export function useCreateListing() {
+ const qc = useQueryClient();
+ return useMutation({
+ mutationFn: (body: { title: string; price_minor: number; currency: string }) =>
+ api("/v1/marketplace/listings", { method: "POST", json: body }),
+ onSuccess: () => qc.invalidateQueries({ queryKey: ["listings"] }),
+ });
+}
+
+/* ---------- 协作(同契台) ---------- */
+export function useCreateWorkspace() {
+ return useMutation({
+ mutationFn: (body: { name: string }) =>
+ api("/v1/workspaces", { method: "POST", json: body }),
+ });
+}
+
+/* ---------- 插件(百工坊) ---------- */
+export function usePlugins() {
+ return useQuery({
+ queryKey: ["plugins"],
+ queryFn: () => api<{ data: import("./api").PluginMeta[] }>("/v1/plugins"),
+ });
+}
+
+export function useInstallPlugin() {
+ return useMutation({
+ mutationFn: (name: string) =>
+ api(`/v1/plugins/${name}/installations`, { method: "POST", json: {} }),
+ });
+}
+
+/* ---------- 工具与审批(天工阁 / 司命台) ---------- */
+export function useToolCalls() {
+ return useQuery({
+ queryKey: ["tool-calls"],
+ queryFn: () => api>("/v1/tool-calls?limit=30"),
+ });
+}
+
+export function useTools() {
+ return useQuery({
+ queryKey: ["tools"],
+ queryFn: () => api<{ data: import("./api").ToolMeta[] }>("/v1/tools"),
+ });
+}
+
+/* ---------- 形仪 ---------- */
+export function useAvatars() {
+ return useQuery({
+ queryKey: ["avatars"],
+ queryFn: () => api<{ data: import("./api").AvatarAsset[] }>("/v1/avatars"),
+ });
+}
+
+/* ---------- 财务(设置 / 云市) ---------- */
+export function useFinanceSummary() {
+ return useQuery({
+ queryKey: ["finance-summary"],
+ queryFn: () => api("/v1/finance/summary"),
+ });
+}
+
+/* ---------- 登出 ---------- */
+export function useLogout() {
+ return useMutation({
+ mutationFn: () => api("/v1/auth/sessions/current", { method: "DELETE" }),
+ });
+}
diff --git a/frontend/src/lib/query.tsx b/frontend/src/lib/query.tsx
new file mode 100644
index 0000000..447177b
--- /dev/null
+++ b/frontend/src/lib/query.tsx
@@ -0,0 +1,16 @@
+"use client";
+
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { useState } from "react";
+
+export function QueryProvider({ children }: { children: React.ReactNode }) {
+ const [client] = useState(
+ () =>
+ new QueryClient({
+ defaultOptions: {
+ queries: { retry: 1, refetchOnWindowFocus: false, staleTime: 15_000 },
+ },
+ }),
+ );
+ return {children} ;
+}
diff --git a/frontend/src/lib/theme.tsx b/frontend/src/lib/theme.tsx
new file mode 100644
index 0000000..5e17932
--- /dev/null
+++ b/frontend/src/lib/theme.tsx
@@ -0,0 +1,56 @@
+"use client";
+
+import { Moon } from "lucide-react";
+import { createContext, useCallback, useContext, useEffect, useState } from "react";
+
+type Theme = "dark" | "light";
+
+const ThemeContext = createContext<{ theme: Theme; toggle: () => void }>({
+ theme: "dark",
+ toggle: () => {},
+});
+
+export function ThemeProvider({ children }: { children: React.ReactNode }) {
+ const [theme, setTheme] = useState("dark");
+
+ useEffect(() => {
+ const saved = localStorage.getItem("hunjing-theme");
+ if (saved === "light" || saved === "dark") {
+ setTheme(saved);
+ document.documentElement.setAttribute("data-theme", saved);
+ }
+ }, []);
+
+ const toggle = useCallback(() => {
+ setTheme((prev) => {
+ const next: Theme = prev === "dark" ? "light" : "dark";
+ document.documentElement.setAttribute("data-theme", next);
+ try {
+ localStorage.setItem("hunjing-theme", next);
+ } catch {
+ /* 忽略 */
+ }
+ return next;
+ });
+ }, []);
+
+ return {children} ;
+}
+
+export function useTheme() {
+ return useContext(ThemeContext);
+}
+
+export function ThemeButton() {
+ const { theme, toggle } = useTheme();
+ return (
+
+
+
+ );
+}
diff --git a/frontend/src/types/css.d.ts b/frontend/src/types/css.d.ts
new file mode 100644
index 0000000..bb5580b
--- /dev/null
+++ b/frontend/src/types/css.d.ts
@@ -0,0 +1,8 @@
+import type {} from "react";
+
+declare module "react" {
+ interface CSSProperties {
+ /** CSS 自定义属性(原型内联样式中大量使用 var(--token)) */
+ [key: `--${string}`]: string | number | undefined;
+ }
+}