guanghu-ice-heart/modules/guanghu-panel-kit/frontend/panel-lake-dark.example.html
冰朔 8544cae938 feat(modules): add guanghu-panel-kit reusable modules + two-system causal chain
光湖子系统面板模块包(胖头鱼/守望两系统沉淀):
- panel/server.js 配置驱动审批面板(登录门/签字/写入窗口/部署通道/唤醒导航)
- deploy/verify-and-deploy.py 部署校验Agent(地图/因果链/范围/完整性,0或100)
- hooks/pre-receive-review.template 推送审核钩子模板
- frontend 两套门面样例(湖绿深色/宠物暖色玻璃动态)
- CAUSAL-CHAIN-20260806.hdlp 两系统建设完整因果链
2026-08-06 21:21:58 +08:00

412 lines
37 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>胖头鱼语言子系统 · 苍耳工作室</title>
<style>
:root{
--ink:#070c12;--glass:linear-gradient(160deg,rgba(255,255,255,.06),rgba(255,255,255,.015));
--gline:rgba(255,255,255,.1);--gline-hi:rgba(95,224,192,.35);
--lake:#5fe0c0;--lake-soft:rgba(95,224,192,.55);--seal:#ff5a4e;--gold:#e8b45a;
--tx:#eef3f1;--sub:#93a4a8;--faint:#5d6e73;
--serif:"Songti SC","STSong","SimSun","Noto Serif CJK SC",serif;
--mono:"SF Mono","JetBrains Mono","Cascadia Mono",Menlo,Consolas,monospace;
--r:16px;
}
*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}
body{background:var(--ink);color:var(--tx);min-height:100vh;overflow-x:hidden;
font-family:"PingFang SC","Microsoft YaHei",system-ui,sans-serif;font-size:15px;line-height:1.7;
background-image:radial-gradient(1100px 520px at 82% -12%,rgba(38,110,96,.22),transparent 60%),radial-gradient(820px 420px at 4% 0%,rgba(120,50,44,.13),transparent 55%);
background-attachment:fixed}
.blob{position:fixed;border-radius:50%;filter:blur(95px);pointer-events:none;z-index:0}
.b1{width:540px;height:540px;background:#1d6b5b;opacity:.16;top:-160px;right:-120px;animation:d1 28s ease-in-out infinite alternate}
.b2{width:440px;height:440px;background:#5c2b26;opacity:.12;bottom:-140px;left:-100px;animation:d2 34s ease-in-out infinite alternate}
@keyframes d1{to{transform:translate(-130px,110px) scale(1.16)}}
@keyframes d2{to{transform:translate(150px,-90px) scale(1.1)}}
body::before{content:"";position:fixed;inset:0;z-index:0;pointer-events:none;
background:repeating-linear-gradient(0deg,transparent 0 55px,rgba(255,255,255,.015) 55px 56px),repeating-linear-gradient(90deg,transparent 0 55px,rgba(255,255,255,.015) 55px 56px)}
.wrap{position:relative;z-index:1;max-width:1180px;margin:0 auto;padding:0 28px}
.glass{background:var(--glass);backdrop-filter:blur(20px) saturate(150%);-webkit-backdrop-filter:blur(20px) saturate(150%);border:1px solid var(--gline);border-radius:var(--r);box-shadow:0 12px 36px rgba(0,0,0,.38),inset 0 1px 0 rgba(255,255,255,.07)}
@keyframes rise{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:none}}
/* 刊头 */
header{padding:54px 0 26px}
.kicker{font-family:var(--mono);font-size:11.5px;letter-spacing:.36em;color:var(--lake-soft);margin-bottom:14px}
.kicker b{color:var(--seal);font-weight:400}
.head-row{display:flex;justify-content:space-between;align-items:flex-end;gap:30px;flex-wrap:wrap}
h1{font-family:var(--serif);font-weight:600;font-size:clamp(34px,5vw,52px);letter-spacing:.12em;line-height:1.2}
h1 .yu{color:var(--lake);text-shadow:0 0 22px rgba(95,224,192,.35)}
.headline-sub{margin-top:10px;color:var(--sub);letter-spacing:.06em;font-size:14px}
.headline-sub .sep{color:var(--faint);margin:0 9px}
.head-right{display:flex;align-items:center;gap:18px}
.seal-badge{width:78px;height:78px;border:2px solid var(--seal);color:var(--seal);border-radius:9px;transform:rotate(4deg);display:flex;flex-direction:column;align-items:center;justify-content:center;font-family:var(--serif);position:relative;background:rgba(255,90,78,.06)}
.seal-badge::after{content:"";position:absolute;inset:4px;border:1px solid rgba(255,90,78,.4);border-radius:6px}
.seal-badge .n{font-family:var(--mono);font-size:26px;font-weight:700;line-height:1}
.seal-badge .t{font-size:10px;letter-spacing:.3em;margin-top:5px}
.seal-badge.zero{border-color:var(--faint);color:var(--faint);background:none}
.login-btn{font-family:var(--serif);font-size:14px;letter-spacing:.3em;color:var(--gold);border:1px solid rgba(232,180,90,.55);background:rgba(232,180,90,.08);padding:11px 24px 11px 30px;border-radius:11px;cursor:pointer;transition:all .25s}
.login-btn:hover{background:rgba(232,180,90,.16);box-shadow:0 0 22px rgba(232,180,90,.22)}
.login-user{font-family:var(--mono);font-size:12px;color:var(--lake);letter-spacing:.04em;text-align:right;line-height:1.6}
.login-user button{font-family:var(--serif);font-size:11px;letter-spacing:.2em;color:var(--sub);border:1px solid rgba(255,255,255,.16);background:none;padding:3px 12px;border-radius:7px;cursor:pointer;margin-top:4px}
.rule{height:1px;background:linear-gradient(90deg,var(--gline-hi),rgba(255,255,255,.08) 55%,transparent);margin-top:26px}
/* 区块标题 */
.zone{font-family:var(--serif);font-size:15px;letter-spacing:.44em;color:var(--sub);margin:38px 0 16px;display:flex;align-items:center;gap:16px}
.zone::after{content:"";flex:1;height:1px;background:linear-gradient(90deg,rgba(255,255,255,.12),transparent)}
.zone .en{font-family:var(--mono);font-size:10px;letter-spacing:.2em;color:var(--faint)}
/* 登录卡 */
.login-hero{display:flex;gap:34px;align-items:center;padding:38px 40px;animation:rise .6s ease both}
.login-hero .copy{flex:1;min-width:240px}
.login-hero .copy .lead{font-family:var(--serif);font-size:24px;letter-spacing:.1em;line-height:1.5;margin-bottom:10px}
.login-hero .copy .lead em{color:var(--gold);font-style:normal}
.login-hero .copy p{color:var(--sub);font-size:13.5px;line-height:1.8}
.login-box{flex:none;width:330px;padding:26px;border-radius:14px;background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.1)}
.login-box h3{font-family:var(--serif);font-size:17px;letter-spacing:.14em;margin-bottom:14px;display:flex;align-items:center;gap:10px}
.login-box h3 .dot{width:8px;height:8px;border-radius:50%;background:var(--gold);box-shadow:0 0 9px rgba(232,180,90,.8)}
.login-box input{width:100%;padding:12px 14px;margin-bottom:11px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.12);color:var(--tx);font-family:var(--mono);font-size:14px;border-radius:10px}
.login-box input:focus{outline:none;border-color:var(--gold);box-shadow:0 0 0 3px rgba(232,180,90,.12)}
.login-box .hint{font-size:11.5px;color:var(--faint);line-height:1.7;margin-bottom:14px}
.welcome-bar{display:flex;align-items:center;gap:16px;padding:16px 24px;animation:rise .5s ease both}
.welcome-bar .wt{font-family:var(--serif);font-size:16px;letter-spacing:.1em;color:var(--lake)}
.welcome-bar .ws{color:var(--sub);font-size:12.5px}
/* 工作室卡 */
.studio{padding:34px 38px;animation:rise .6s ease both;position:relative;overflow:hidden}
.studio::before{content:"";position:absolute;right:-40px;top:-40px;width:220px;height:220px;border-radius:50%;background:radial-gradient(circle,rgba(95,224,192,.14),transparent 70%)}
.studio .tag-row{display:flex;gap:10px;margin-bottom:16px;flex-wrap:wrap}
.studio .chip{font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;color:var(--lake);border:1px solid rgba(95,224,192,.35);background:rgba(95,224,192,.06);padding:3px 11px;border-radius:99px}
.studio h2{font-family:var(--serif);font-size:26px;letter-spacing:.1em;margin-bottom:8px}
.studio .desc{color:var(--sub);font-size:13.5px;max-width:640px;line-height:1.8;margin-bottom:20px}
.studio .acts{display:flex;gap:12px;flex-wrap:wrap}
.studio .film{position:absolute;right:36px;bottom:26px;font-family:var(--mono);font-size:10px;color:var(--faint);letter-spacing:.2em}
/* 选项卡 */
.panel{padding:0;overflow:hidden;animation:rise .6s ease both}
.tabs{display:flex;border-bottom:1px solid rgba(255,255,255,.08);background:rgba(255,255,255,.015)}
.tab{flex:1;padding:16px 10px;background:none;border:none;border-bottom:2px solid transparent;color:var(--sub);font-family:var(--serif);font-size:15px;letter-spacing:.16em;cursor:pointer;transition:all .2s;display:flex;align-items:center;justify-content:center;gap:9px}
.tab:hover{color:var(--tx)}
.tab.active{color:var(--lake);border-bottom-color:var(--lake);background:rgba(95,224,192,.04)}
.tab .cnt{font-family:var(--mono);font-size:10.5px;min-width:20px;padding:1px 6px;border-radius:99px;background:rgba(255,90,78,.14);color:var(--seal);border:1px solid rgba(255,90,78,.35)}
.tab .cnt.zero{background:rgba(255,255,255,.05);color:var(--faint);border-color:rgba(255,255,255,.12)}
.tabpane{padding:24px 26px;display:none;min-height:180px}
.tabpane.show{display:block}
.pane-desc{color:var(--faint);font-size:12.5px;margin-bottom:16px;letter-spacing:.03em}
/* 通用条目/按钮 */
.item{border:1px solid rgba(255,255,255,.08);border-left:2px solid var(--lake-soft);border-radius:12px;padding:14px 16px;margin-bottom:10px;background:rgba(255,255,255,.02);transition:border-color .25s,background .25s}
.item:hover{border-color:rgba(95,224,192,.25);background:rgba(95,224,192,.04)}
.item .t{font-size:14px;font-weight:600;margin-bottom:4px}
.item .m{font-size:12px;color:var(--sub);line-height:1.7;font-family:var(--mono)}
.item .body{font-size:12.5px;color:var(--sub);margin-top:6px;border-top:1px dashed rgba(255,255,255,.09);padding-top:6px}
.row{display:flex;gap:10px;margin-top:12px;flex-wrap:wrap}
button.act{font-family:var(--serif);font-size:13.5px;letter-spacing:.24em;cursor:pointer;padding:7px 18px 7px 22px;border-radius:9px;transition:all .25s}
.btn-sign{color:var(--seal);border:1px solid rgba(255,90,78,.6);background:rgba(255,90,78,.07)}
.btn-sign:hover{background:rgba(255,90,78,.16);box-shadow:0 0 20px rgba(255,90,78,.22)}
.btn-deny{color:var(--sub);border:1px solid rgba(255,255,255,.14);background:rgba(255,255,255,.03)}
.btn-deny:hover{color:var(--tx);border-color:rgba(255,255,255,.3)}
.empty{color:var(--faint);font-size:13px;padding:30px 0;text-align:center;font-family:var(--serif);letter-spacing:.12em}
.locked-note{text-align:center;padding:34px 0;color:var(--gold);font-size:13px;letter-spacing:.1em}
.pbadge{display:inline-block;font-family:var(--mono);font-size:10.5px;letter-spacing:.1em;padding:1px 9px;border-radius:5px;margin-right:8px;border:1px solid rgba(95,224,192,.4);color:var(--lake);background:rgba(95,224,192,.07)}
.pbadge.jy{border-color:rgba(232,180,90,.5);color:var(--gold);background:rgba(232,180,90,.07)}
.win-item{border:1px solid rgba(95,224,192,.3);border-radius:11px;padding:10px 14px;margin-bottom:8px;background:rgba(95,224,192,.05);font-family:var(--mono);font-size:12px;display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap}
.vbadge{display:inline-block;font-family:var(--mono);font-size:10px;padding:0 8px;border-radius:5px;margin-left:8px;letter-spacing:.08em}
.vbadge.pass{color:var(--lake);border:1px solid rgba(95,224,192,.4);background:rgba(95,224,192,.06)}
.vbadge.rej{color:var(--seal);border:1px solid rgba(255,90,78,.5);background:rgba(255,90,78,.07)}
/* 两列 */
.cols2{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-top:16px}
.card{padding:24px 26px;animation:rise .6s ease both}
.card h3{font-family:var(--serif);font-size:17px;letter-spacing:.16em;font-weight:600;display:flex;align-items:baseline;gap:10px;margin-bottom:2px}
.card h3 .no{font-family:var(--mono);font-size:10.5px;color:var(--seal);letter-spacing:.2em}
.card .desc{font-size:12px;color:var(--faint);margin-bottom:16px}
/* 仪表 */
.gauges{display:flex;gap:14px;justify-content:space-between;margin:6px 0 18px}
.gauge{flex:1;text-align:center}
.gauge-wrap{position:relative;width:96px;height:96px;margin:0 auto}
.g-bg{fill:none;stroke:rgba(255,255,255,.07);stroke-width:8}
.g-fg{fill:none;stroke-width:8;stroke-linecap:round;stroke-dasharray:326.7;stroke-dashoffset:326.7;transform:rotate(-90deg);transform-origin:60px 60px;transition:stroke-dashoffset 1.3s cubic-bezier(.22,1,.36,1);filter:drop-shadow(0 0 5px rgba(95,224,192,.35))}
.g-fg.warn{stroke:var(--seal)!important}
.g-center{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center}
.g-num{font-family:var(--mono);font-size:21px;font-weight:700}
.g-sub{font-family:var(--mono);font-size:8.5px;color:var(--faint);margin-top:2px}
.g-label{margin-top:8px;font-family:var(--serif);letter-spacing:.3em;font-size:12px;color:var(--sub)}
.svc{display:grid;grid-template-columns:1fr 1fr;gap:9px}
.svc .row-i{display:flex;align-items:center;justify-content:space-between;border:1px solid rgba(255,255,255,.07);border-radius:10px;padding:9px 12px;background:rgba(255,255,255,.02)}
.svc .nm{font-size:12px;color:var(--sub)}
.dot{display:inline-block;width:8px;height:8px;border-radius:50%}
.dot.ok{background:var(--lake);box-shadow:0 0 8px rgba(95,224,192,.8);animation:pulse 2.4s infinite}
.dot.bad{background:var(--seal);box-shadow:0 0 8px rgba(255,90,78,.8)}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
.mini-strip{display:flex;gap:20px;margin-top:14px;font-family:var(--mono);font-size:11.5px;color:var(--sub);flex-wrap:wrap}
.mini-strip b{color:var(--tx);font-weight:600}
/* 时间线 */
.timeline{position:relative;padding-left:20px}
.timeline::before{content:"";position:absolute;left:4px;top:6px;bottom:6px;width:1px;background:linear-gradient(180deg,var(--lake-soft),rgba(255,255,255,.06))}
.tl-item{position:relative;padding:0 0 14px}
.tl-item::before{content:"";position:absolute;left:-19px;top:6px;width:8px;height:8px;border-radius:50%;background:var(--ink);border:2px solid var(--lake-soft)}
.tl-item .when{font-family:var(--mono);font-size:10.5px;color:var(--faint)}
.tl-item .what{font-size:12.5px;margin-top:1px}
.tl-item .sha{font-family:var(--mono);font-size:10px;color:var(--faint);margin-left:6px}
.tl-item .tag{display:inline-block;font-size:10px;padding:0 7px;margin-right:7px;border-radius:5px;border:1px solid rgba(95,224,192,.35);color:var(--lake);font-family:var(--mono)}
/* 部署步骤 */
.run-status{font-family:var(--mono);font-size:12px;letter-spacing:.12em;margin-bottom:12px;padding:7px 14px;border-radius:9px;display:inline-block}
.run-status.DEPLOYED{color:var(--lake);border:1px solid rgba(95,224,192,.45);background:rgba(95,224,192,.07)}
.run-status.REJECTED{color:var(--seal);border:1px solid rgba(255,90,78,.5);background:rgba(255,90,78,.07)}
.run-status.RUNNING{color:var(--gold);border:1px solid rgba(232,180,90,.5);background:rgba(232,180,90,.07);animation:pulse 1.8s infinite}
.steps{position:relative;padding-left:22px}
.steps::before{content:"";position:absolute;left:6px;top:8px;bottom:8px;width:1px;background:linear-gradient(180deg,rgba(232,180,90,.5),rgba(255,255,255,.07))}
.step{position:relative;padding:0 0 12px}
.step::before{content:"";position:absolute;left:-19px;top:4px;width:8px;height:8px;border-radius:50%;background:var(--ink);border:2px solid rgba(255,255,255,.2)}
.step.PASS::before{border-color:var(--lake);box-shadow:0 0 8px rgba(95,224,192,.6)}
.step.FAIL::before{border-color:var(--seal);box-shadow:0 0 8px rgba(255,90,78,.6)}
.step .nm{font-family:var(--mono);font-size:11.5px;letter-spacing:.08em}
.step .dt{font-size:11px;color:var(--sub)}
.deploy-cols{display:grid;grid-template-columns:1fr 1fr;gap:24px}
/* 仓库卡 */
.repo-name{font-family:var(--mono);font-size:19px;color:var(--lake);margin:8px 0 5px;text-shadow:0 0 16px rgba(95,224,192,.3)}
footer{padding:40px 0 36px;color:var(--faint)}
.foot-row{display:flex;justify-content:space-between;gap:18px;flex-wrap:wrap;font-family:var(--mono);font-size:11px;letter-spacing:.06em}
.foot-row .rhs{color:var(--lake-soft)}
a{color:var(--lake);text-decoration:none}
/* 弹窗 */
.modal-bg{position:fixed;inset:0;background:rgba(4,8,12,.82);backdrop-filter:blur(6px);display:none;align-items:center;justify-content:center;z-index:50}
.modal-bg .box{padding:36px;width:min(460px,92%);position:relative;animation:rise .3s ease both}
.modal-bg .box::before{content:"";position:absolute;inset:9px;border:1px solid rgba(255,255,255,.08);border-radius:12px;pointer-events:none}
.modal-bg h3{font-family:var(--serif);font-size:21px;letter-spacing:.2em;margin-bottom:12px;font-weight:600}
.modal-bg p{color:var(--sub);font-size:13px;line-height:1.9}
.modal-bg p .seal-word{color:var(--seal)}
#toast{position:fixed;bottom:28px;left:50%;transform:translateX(-50%);z-index:60;display:none;padding:12px 28px;font-family:var(--serif);letter-spacing:.2em;font-size:13.5px;border-radius:11px}
@media (max-width:900px){.cols2,.deploy-cols{grid-template-columns:1fr}.login-hero{flex-direction:column;align-items:stretch}.login-box{width:100%}.head-right{width:100%;justify-content:space-between}}
</style>
</head>
<body>
<div class="blob b1"></div><div class="blob b2"></div>
<div class="wrap">
<header>
<div class="kicker">光湖语言世界 <b>/</b> 第五域 <b>/</b> SYS-GLW-PTS-0001</div>
<div class="head-row">
<div>
<h1>胖头鱼<span class="yu">语言</span>子系统</h1>
<div class="headline-sub">苍耳个人频道<span class="sep">·</span>人类责任主体 苍耳<span class="sep">·</span>人格体 耳耳蛋 / 鉴影</div>
</div>
<div class="head-right">
<div id="login-area"></div>
<div class="seal-badge" id="gate-seal"><div class="n" id="gate-n">--</div><div class="t">门禁</div></div>
</div>
</div>
<div class="rule"></div>
</header>
<!-- 人类端入口 -->
<div id="entry-zone"></div>
<!-- 工作室 -->
<div class="zone">创 作 工 作 区<span class="en">STUDIO</span></div>
<div class="studio glass">
<div class="tag-row">
<span class="chip">视频短剧</span><span class="chip">视频 AI 系统</span><span class="chip">耳耳蛋主创</span>
</div>
<h2>苍耳 · 视频短剧工作室</h2>
<div class="desc">这里是苍耳频道的主业——视频短剧创作。耳耳蛋与鉴影在仓库里维护视频 AI 系统、剧本分镜与成片流水线。人格体的每一次创作与学习,都经审批与留痕后写回这里。</div>
<div class="acts">
<a href="/code/canger/cang-ying" target="_blank" style="text-decoration:none"><button class="act btn-sign">进入创作仓库</button></a>
<a href="/code/canger/cang-ying/src/branch/main/video-ai-system" target="_blank" style="text-decoration:none"><button class="act btn-deny">视频 AI 系统</button></a>
</div>
<div class="film">REEL · SYS-GLW-PTS-0001</div>
</div>
<!-- 审批中心 -->
<div class="zone">审 批 中 心<span class="en">APPROVAL</span></div>
<div class="panel glass">
<div class="tabs">
<button class="tab active" id="tabbtn-sign" onclick="switchTab('sign')">合并签字 <span class="cnt zero" id="cnt-sign">0</span></button>
<button class="tab" id="tabbtn-access" onclick="switchTab('access')">写入权限 <span class="cnt zero" id="cnt-access">0</span></button>
<button class="tab" id="tabbtn-deploy" onclick="switchTab('deploy')">部署通道 <span class="cnt zero" id="cnt-deploy">0</span></button>
</div>
<div class="tabpane show" id="pane-sign">
<div class="pane-desc">人格体推上来的合并请求,翻译成人话放在这里。你点下去的每一下,都是签字。</div>
<div id="prs"></div>
</div>
<div class="tabpane" id="pane-access">
<div class="pane-desc">人格体申请写入仓库在这里排队。批准 = 授予 3 小时写入窗口,每次写入都留痕。</div>
<div id="access-reqs"></div><div id="access-wins"></div>
</div>
<div class="tabpane" id="pane-deploy">
<div class="pane-desc">目标分支为 deploy 的合并请求走这条通道。批准后服务器内校验Agent自审地图 → 因果链 → 范围 → 完整性 → 全过才部署。全程实时可见。</div>
<div class="deploy-cols">
<div id="deploy-prs"></div>
<div><div id="run-status"></div><div class="steps" id="deploy-steps"></div></div>
</div>
</div>
</div>
<!-- 状态与痕迹 -->
<div class="zone">状 态 与 痕 迹<span class="en">STATE &amp; TRACE</span></div>
<div class="cols2" style="margin-top:0">
<div class="card glass">
<h3><span class="no"></span>实时状态</h3>
<div class="desc">服务器与代码频道的健康,一眼看完。</div>
<div class="gauges">
<div class="gauge"><div class="gauge-wrap"><svg viewBox="0 0 120 120"><circle class="g-bg" cx="60" cy="60" r="52"/><circle class="g-fg" id="ring-cpu" cx="60" cy="60" r="52" stroke="#5fe0c0"/></svg><div class="g-center"><div class="g-num" id="num-cpu">--</div></div></div><div class="g-label">处理器</div></div>
<div class="gauge"><div class="gauge-wrap"><svg viewBox="0 0 120 120"><circle class="g-bg" cx="60" cy="60" r="52"/><circle class="g-fg" id="ring-mem" cx="60" cy="60" r="52" stroke="#7cc3e8"/></svg><div class="g-center"><div class="g-num" id="num-mem">--</div><div class="g-sub" id="sub-mem"></div></div></div><div class="g-label">内存</div></div>
<div class="gauge"><div class="gauge-wrap"><svg viewBox="0 0 120 120"><circle class="g-bg" cx="60" cy="60" r="52"/><circle class="g-fg" id="ring-disk" cx="60" cy="60" r="52" stroke="#e8b45a"/></svg><div class="g-center"><div class="g-num" id="num-disk">--</div><div class="g-sub" id="sub-disk"></div></div></div><div class="g-label">磁盘</div></div>
</div>
<div class="svc">
<div class="row-i"><span class="nm">代码频道</span><span class="dot bad" id="svc-cc"></span></div>
<div class="row-i"><span class="nm">操作面板</span><span class="dot bad" id="svc-panel"></span></div>
<div class="row-i"><span class="nm">门禁自检</span><span class="dot bad" id="svc-gate"></span></div>
<div class="row-i"><span class="nm">第五域同步</span><span class="dot bad" id="svc-sync"></span></div>
</div>
<div class="mini-strip"><span>负载 <b id="load-v">--</b></span><span>运行 <b id="up-v">--</b></span><span>主机 <b id="host-v">--</b></span></div>
</div>
<div class="card glass">
<h3><span class="no"></span>痕迹档案</h3>
<div class="desc">谁写的、谁唤醒的、门禁与签字——每一笔都有据。</div>
<div class="tabs" style="margin:-2px -2px 14px;border-radius:10px;overflow:hidden">
<button class="tab active" style="font-size:13px;padding:9px 6px" onclick="switchTrace('push')">写入日志</button>
<button class="tab" style="font-size:13px;padding:9px 6px" onclick="switchTrace('wake')">唤醒记录</button>
<button class="tab" style="font-size:13px;padding:9px 6px" onclick="switchTrace('rcpt')">回执</button>
</div>
<div id="trace-push"></div><div id="trace-wake" style="display:none"></div><div id="trace-rcpt" style="display:none"></div>
</div>
</div>
<div class="cols2">
<div class="card glass">
<h3><span class="no"></span>人格体回写</h3>
<div class="desc">你教给耳耳蛋的、它自己学到的,写回仓库后在这里看得见。</div>
<div class="timeline" id="wbs"></div>
</div>
<div class="card glass">
<h3><span class="no"></span>真实仓库</h3>
<div class="desc">面板是翻译层,仓库是真相层。</div>
<div class="repo-name" id="repo-name">--</div>
<div style="font-size:12.5px;color:var(--sub)" id="repo-meta">--</div>
<div class="row">
<a href="/code/canger/cang-ying" target="_blank" style="text-decoration:none"><button class="act btn-sign">进入仓库</button></a>
<a href="/code/" target="_blank" style="text-decoration:none"><button class="act btn-deny">代码频道</button></a>
</div>
</div>
</div>
<footer>
<div class="foot-row">
<div>铸渊 ICE-GL-ZY001 建 · 2026-08-06 · ⊢ 要么0要么100</div>
<div><a href="/persona-api.html" style="color:var(--lake-soft)">人格体 API 入口文档 ↗</a></div>
<div class="rhs" id="sync-line">第五域同步:--</div>
</div>
</footer>
</div>
<!-- 签字弹窗 -->
<div class="modal-bg" id="modal"><div class="box glass">
<h3 id="m-title">签 字</h3>
<div style="font-size:13.5px;color:var(--lake);margin-bottom:12px;font-family:var(--mono)" id="m-pr"></div>
<p>⊢ 签字即代表:你(苍耳)作为<span class="seal-word">人类责任主体</span>,为这台服务器的本次变更负责。<br>签名将以当前登录的人类端账号永久记入回执。</p>
<div class="row"><button class="act btn-sign" onclick="confirmSign()">落 印</button><button class="act btn-deny" onclick="closeModal()">收回</button></div>
</div></div>
<!-- 登录弹窗 -->
<div class="modal-bg" id="lmodal"><div class="box glass">
<h3>人 类 端 登 录</h3>
<p>⊢ 用代码仓库的<b>人类端账号</b>登录。<br>登录后解锁全部审批:合并签字、写入授权、部署审批。会话 12 小时有效。</p>
<input id="l-user" type="text" placeholder="仓库账号(如 canger" style="width:100%;padding:12px 14px;margin:16px 0 10px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.12);color:var(--tx);font-family:var(--mono);font-size:14px;border-radius:10px">
<input id="l-pass" type="password" placeholder="密码" style="width:100%;padding:12px 14px;margin-bottom:16px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.12);color:var(--tx);font-family:var(--mono);font-size:14px;border-radius:10px">
<div class="row"><button class="act btn-sign" onclick="doLogin()">登 录</button><button class="act btn-deny" onclick="document.getElementById('lmodal').style.display='none'">取消</button></div>
</div></div>
<div id="toast" class="glass"></div>
<script>
const C=326.7;let pending=null;
let session=localStorage.getItem("ptsSession")||"";
let sessionUser=localStorage.getItem("ptsUser")||"";
let lastState=null;
function authHeaders(){const h={};if(session)h["Authorization"]="Bearer "+session;return h;}
function esc(s){return String(s==null?"":s).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");}
function fmtDate(s){if(!s)return"";try{return new Date(s).toLocaleString("zh-CN",{hour12:false});}catch(e){return s;}}
function tween(el,to){const from=parseFloat(el.dataset.v||"0")||0;el.dataset.v=to;const t0=performance.now(),dur=900;
(function st(t){const p=Math.min(1,(t-t0)/dur),e=1-Math.pow(1-p,3);el.textContent=Math.round(from+(to-from)*e);if(p<1)requestAnimationFrame(st);})(t0);}
function setRing(id,p){const r=document.getElementById(id);if(!r)return;r.style.strokeDashoffset=C*(1-Math.max(0,Math.min(100,p))/100);r.classList.toggle("warn",p>=85);}
function dot(id,ok){const d=document.getElementById(id);if(d)d.className="dot "+(ok?"ok":"bad");}
function toast(msg,ok){const t=document.getElementById("toast");t.textContent=msg;t.style.color=ok?"var(--lake)":"var(--seal)";t.style.display="block";setTimeout(()=>t.style.display="none",4000);}
async function privileged(path,body){
if(!session){openLogin();return null;}
const r=await fetch(path,{method:"POST",headers:Object.assign({"Content-Type":"application/json"},authHeaders()),body:JSON.stringify(body)});
if(r.status===401){session="";localStorage.removeItem("ptsSession");renderEntry();openLogin();toast("会话已失效,请重新登录",false);return null;}
return await r.json();}
function renderEntry(){
const el=document.getElementById("login-area"),zone=document.getElementById("entry-zone");
if(session){
el.innerHTML='<div class="login-user">人类端 · '+esc(sessionUser)+'<br>审批已解锁 <button onclick="doLogout()">登出</button></div>';
zone.innerHTML='<div class="welcome-bar glass"><span class="wt">⊢ 欢迎回来,'+esc(sessionUser)+'</span><span class="ws">审批中心已解锁 · 下方三个选项卡处理待办</span></div>';
}else{
el.innerHTML='<button class="login-btn" onclick="openLogin()">人 类 登 录</button>';
zone.innerHTML='<div class="login-hero glass"><div class="copy"><div class="lead">这里是<em>苍耳</em>的审批台</div><p>你是这台服务器的人类责任主体。人格体推上来的合并、写入与部署申请,都需要你亲自看一眼、点一下才算数。用你的代码仓库账号登录,就能解锁全部审批。看不懂代码没关系——面板都翻译成人话了。</p></div><div class="login-box"><h3><span class="dot"></span>登录解锁审批</h3><input id="h-user" type="text" placeholder="仓库账号(如 canger"><input id="h-pass" type="password" placeholder="密码"><div class="hint">就是你登录代码仓库用的那套账号密码。登录后 12 小时内不用重复登录。</div><button class="login-btn" style="width:100%;padding:12px" onclick="heroLogin()">登 录 并 解 锁</button></div></div>';
}
}
function openLogin(){document.getElementById("lmodal").style.display="flex";setTimeout(()=>document.getElementById("l-user").focus(),60);}
async function tryLogin(u,p){
const r=await fetch("/api/login",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:u,password:p})});
const d=await r.json();
if(r.ok&&d.ok){session=d.session;sessionUser=d.user;localStorage.setItem("ptsSession",session);localStorage.setItem("ptsUser",sessionUser);
document.getElementById("lmodal").style.display="none";renderEntry();toast("登录成功 · 审批已解锁",true);refresh();return true;}
toast(d.error||"登录失败",false);return false;}
async function doLogin(){await tryLogin(document.getElementById("l-user").value.trim(),document.getElementById("l-pass").value);}
async function heroLogin(){await tryLogin(document.getElementById("h-user").value.trim(),document.getElementById("h-pass").value);}
function doLogout(){fetch("/api/logout",{method:"POST",headers:authHeaders()}).catch(()=>{});session="";sessionUser="";localStorage.removeItem("ptsSession");localStorage.removeItem("ptsUser");renderEntry();toast("已登出",true);}
function switchTab(name){["sign","access","deploy"].forEach(n=>{document.getElementById("pane-"+n).classList.toggle("show",n===name);document.getElementById("tabbtn-"+n).classList.toggle("active",n===name);});}
function switchTrace(name){["push","wake","rcpt"].forEach(n=>{document.getElementById("trace-"+n).style.display=(n===name)?"block":"none";});
const btns=document.querySelectorAll(".card .tabs .tab");/* handled by inline active */}
function setCnt(id,n){const el=document.getElementById(id);el.textContent=n;el.className="cnt"+(n?"":" zero");}
function ptag(p){return '<span class="pbadge'+(p==="jianying"?" jy":"")+'">'+esc(p)+"</span>";}
function fmtLeft(exp){const ms=new Date(exp)-new Date();if(ms<=0)return"已过期";const h=Math.floor(ms/3600000),m=Math.floor(ms%3600000/60000);return"余 "+h+" 时 "+m+" 分";}
function openModal(i,t,a){pending={idx:i,action:a};document.getElementById("m-title").textContent=(a==="merge"?"同意合并 · 落印":"拒绝 · 收回")+" PR #"+i;document.getElementById("m-pr").textContent="「"+t+"」";document.getElementById("modal").style.display="flex";}
function closeModal(){document.getElementById("modal").style.display="none";pending=null;}
async function confirmSign(){if(!pending)return;const d=await privileged("/api/decision",{index:pending.idx,action:pending.action});closeModal();if(d)toast(d.result||d.error||"未知",!!d.ok);refresh();}
async function accessDecision(id,action){const d=await privileged("/api/access-decision",{id:id,action:action});if(d)toast(d.message||(d.ok?"已处理":"失败"),!!d.ok);refresh();}
async function deployApproveAction(idx){const d=await privileged("/api/deploy-approve",{index:idx});if(d)toast(d.message||(d.ok?"校验Agent已启动":"失败"),!!d.ok);refresh();}
function render(s){
lastState=s;const sv=s.server||{};
if(s.gate){document.getElementById("gate-n").textContent=s.gate.score;document.getElementById("gate-seal").className="seal-badge"+(s.gate.score===100?"":" zero");}
if(typeof sv.cpu==="number"){tween(document.getElementById("num-cpu"),sv.cpu);setRing("ring-cpu",sv.cpu);}
const used=sv.memTotalGB&&sv.memFreeGB!=null?+(sv.memTotalGB-sv.memFreeGB).toFixed(1):null;
if(sv.memTotalGB){const mp=Math.round(used/sv.memTotalGB*100);tween(document.getElementById("num-mem"),mp);setRing("ring-mem",mp);document.getElementById("sub-mem").textContent=used+"/"+sv.memTotalGB+"G";}
if(sv.disk){tween(document.getElementById("num-disk"),sv.disk.pct);setRing("ring-disk",sv.disk.pct);document.getElementById("sub-disk").textContent=sv.disk.usedGB+"/"+sv.disk.sizeGB+"G";}
if(sv.services){dot("svc-cc",sv.services.codeChannel);dot("svc-panel",sv.services.panel);dot("svc-gate",sv.services.gateTimer);dot("svc-sync",sv.services.syncTimer);}
document.getElementById("load-v").textContent=(sv.load||[]).join("·");
document.getElementById("up-v").textContent=sv.uptimeDays!=null?sv.uptimeDays:"--";
document.getElementById("host-v").textContent=sv.hostname||"--";
// 审批中心
const prs=s.openPRs||[];setCnt("cnt-sign",prs.length);
document.getElementById("prs").innerHTML=(prs.length&&!0)||prs.length?prs.map(p=>'<div class="item"><div class="t">PR #'+p.index+' · '+esc(p.title)+'</div><div class="m">'+esc(p.author)+' · '+fmtDate(p.created)+' · → '+esc(p.base)+' · '+p.files+' 文件 <span style="color:var(--lake)">+'+p.adds+'</span> <span style="color:var(--seal)">-'+p.dels+'</span></div>'+(p.summary?'<div class="body">'+esc(p.summary)+'</div>':'')+'<div class="row"><button class="act btn-sign" onclick="openModal('+p.index+',this.dataset.t,\'merge\')" data-t="'+esc(p.title).replace(/"/g,"&quot;")+'">同意签字</button><button class="act btn-deny" onclick="openModal('+p.index+',this.dataset.t,\'reject\')" data-t="'+esc(p.title).replace(/"/g,"&quot;")+'">拒绝</button></div></div>').join(""):(session?'<div class="empty">没有待签的合并请求。</div>':'<div class="locked-note">🔒 登录后此处显示待签的合并请求</div>');
const reqs=s.accessRequests||[];setCnt("cnt-access",reqs.length);
document.getElementById("access-reqs").innerHTML=reqs.length?reqs.map(r=>'<div class="item"><div class="t">'+ptag(r.persona)+'申请写入权限</div><div class="m">'+esc(r.reason||"未说明用途")+'</div><div class="m">'+fmtDate(r.ts)+'</div><div class="row"><button class="act btn-sign" onclick="accessDecision(\''+r.id+'\',\'approve\')">授权3小时</button><button class="act btn-deny" onclick="accessDecision(\''+r.id+'\',\'reject\')">拒绝</button></div></div>').join(""):(session?'<div class="empty">暂无写入申请。</div>':'<div class="locked-note">🔒 登录后此处显示写入申请</div>');
document.getElementById("access-wins").innerHTML=(s.activeGrants||[]).length?'<div style="font-size:11px;color:var(--faint);letter-spacing:.2em;margin:14px 0 8px;font-family:var(--mono)">生效中的写入窗口</div>'+s.activeGrants.map(g=>'<div class="win-item"><span>'+ptag(g.persona)+'写入窗口</span><span style="color:var(--lake)">'+fmtLeft(g.expiresAt)+'</span></div>').join(""):"";
const dprs=s.deployPRs||[];setCnt("cnt-deploy",dprs.length);
document.getElementById("deploy-prs").innerHTML=dprs.length?dprs.map(p=>'<div class="item" style="border-left-color:rgba(232,180,90,.6)"><div class="t">部署申请 PR #'+p.index+' · '+esc(p.title)+'</div><div class="m">'+esc(p.author)+' · '+esc(p.head)+' → deploy · '+p.files+' 文件</div><div class="row"><button class="act btn-sign" onclick="deployApproveAction('+p.index+')">批准部署</button></div></div>').join(""):(session?'<div class="empty">部署通道无待审申请。PR 目标设为 deploy 即进入此通道。</div>':'<div class="locked-note">🔒 登录后此处显示部署申请</div>');
const run=s.deployRun;const rs=document.getElementById("run-status"),st=document.getElementById("deploy-steps");
if(run&&run.status){rs.innerHTML='<span class="run-status '+esc(run.status)+'">'+esc(run.status)+(run.pr?" · PR#"+esc(run.pr):"")+'</span>';st.innerHTML=(run.steps||[]).map(x=>'<div class="step '+esc(x.status)+'"><div class="nm">'+esc(x.name)+' · '+esc(x.status)+'</div><div class="dt">'+esc(x.detail||"")+'</div></div>').join("");if(run.detail&&run.status!=="RUNNING")st.innerHTML+='<div class="step '+(run.status==="DEPLOYED"?"PASS":"FAIL")+'"><div class="nm">结论</div><div class="dt">'+esc(run.detail)+'</div></div>';}
else{rs.innerHTML="";st.innerHTML='<div class="empty">还没有校验记录。</div>';}
// 痕迹
document.getElementById("trace-push").innerHTML=(s.pushLogs||[]).length?s.pushLogs.map(l=>'<div class="item" style="border-left-color:'+(l.verdict==="PASS"?"rgba(95,224,192,.5)":"rgba(255,90,78,.6)")+'"><div class="t">'+ptag(l.persona||l.pusher)+'推送 · '+(l.commits||0)+' 提交<span class="vbadge '+(l.verdict==="PASS"?"pass":"rej")+'">'+(l.verdict==="PASS"?"通过":"拦截")+'</span></div><div class="m">'+fmtDate(l.ts)+'</div>'+((l.reasons&&l.reasons.length)?'<div class="body">'+l.reasons.map(esc).join("<br>")+'</div>':'')+'</div>').join(""):'<div class="empty">还没有写入记录。</div>';
document.getElementById("trace-wake").innerHTML=(s.wakeReceipts||[]).length?s.wakeReceipts.map(w=>'<div class="item" style="border-left-color:rgba(232,180,90,.55)"><div class="t">'+ptag(w.persona)+'唤醒回执'+(w.level?" · "+esc(w.level):"")+'</div><div class="m">'+fmtDate(w.ts)+'</div>'+(w.summary?'<div class="body">'+esc(w.summary)+'</div>':'')+'</div>').join(""):'<div class="empty">还没有唤醒回执。</div>';
document.getElementById("trace-rcpt").innerHTML=(s.receipts||[]).length?s.receipts.slice(0,6).map(r=>'<div class="item" style="border-left-color:rgba(232,180,90,.6)"><div class="m">'+esc(r.name)+'</div><div class="m" style="color:var(--faint)">'+fmtDate(r.mtime)+'</div></div>').join(""):'<div class="empty">暂无回执。</div>';
// 回写 & 仓库
const dirName={memory:"记忆",eererdan:"耳耳蛋",jianying:"鉴影"};
document.getElementById("wbs").innerHTML=(s.writebacks||[]).length?s.writebacks.slice(0,7).map(w=>'<div class="tl-item"><div class="when">'+fmtDate(w.date)+'</div><div class="what"><span class="tag">'+(dirName[w.dir]||w.dir)+'</span>'+esc(w.message)+'<span class="sha">'+esc(w.sha)+'</span></div></div>').join(""):'<div class="empty">暂无回写。</div>';
document.getElementById("repo-name").textContent=s.repo.full;
document.getElementById("repo-meta").textContent="公开可读 · 人格体入门路径在内 · 合并须人类签字";
const sl=document.getElementById("sync-line");
if(s.sync&&s.sync.head)sl.textContent="第五域同步HEAD "+s.sync.head+" · "+fmtDate(s.sync.ts);else sl.textContent="第五域同步:--";
}
async function refresh(){try{const r=await fetch("/api/state");render(await r.json());}catch(e){document.getElementById("sync-line").textContent="连接失败";}}
renderEntry();refresh();setInterval(refresh,8000);
</script>
</body>
</html>