guanghu-ice-heart/modules/guanghu-panel-kit/frontend/panel-pet-warm.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

398 lines
38 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:#12100e;--coal:#1a1714;
--glass:linear-gradient(160deg,rgba(255,240,228,.065),rgba(255,240,228,.018));
--gline:rgba(255,224,200,.11);--gline-hi:rgba(255,157,138,.4);
--peach:#ff9d8a;--peach-soft:rgba(255,157,138,.55);--cream:#f7e8dc;--mint:#9fd8c3;--mint-deep:#4d9b80;--seal:#e05d55;--gold:#e8b45a;
--tx:#f3ece6;--sub:#b3a69b;--faint:#7d7168;
--serif:"Songti SC","STSong","Kaiti SC","SimSun",serif;
--mono:"SF Mono","JetBrains Mono","Cascadia Mono",Menlo,Consolas,monospace;
--r:18px;
}
*{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(255,157,138,.13),transparent 60%),radial-gradient(820px 460px at 4% 2%,rgba(159,216,195,.1),transparent 55%);
background-attachment:fixed}
.blob{position:fixed;border-radius:50%;filter:blur(100px);pointer-events:none;z-index:0}
.b1{width:520px;height:520px;background:#ff9d8a;opacity:.12;top:-150px;right:-110px;animation:d1 26s ease-in-out infinite alternate}
.b2{width:430px;height:430px;background:#9fd8c3;opacity:.1;bottom:-130px;left:-90px;animation:d2 32s ease-in-out infinite alternate}
.b3{width:300px;height:300px;background:#e8b45a;opacity:.07;top:44%;left:58%;animation:d3 38s ease-in-out infinite alternate}
@keyframes d1{to{transform:translate(-120px,110px) scale(1.15)}}
@keyframes d2{to{transform:translate(140px,-80px) scale(1.1)}}
@keyframes d3{to{transform:translate(-160px,60px) scale(.92)}}
/* 爪印飘浮 */
.paw{position:fixed;z-index:0;opacity:0;pointer-events:none;font-size:15px;color:var(--peach);animation:floatUp linear infinite}
@keyframes floatUp{0%{transform:translateY(12vh) rotate(-8deg);opacity:0}12%{opacity:.16}80%{opacity:.1}100%{transform:translateY(-105vh) rotate(10deg);opacity:0}}
body::before{content:"";position:fixed;inset:0;z-index:0;pointer-events:none;
background:repeating-linear-gradient(0deg,transparent 0 55px,rgba(255,224,200,.013) 55px 56px),repeating-linear-gradient(90deg,transparent 0 55px,rgba(255,224,200,.013) 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,240,228,.07)}
@keyframes rise{from{opacity:0;transform:translateY(18px)}to{opacity:1;transform:none}}
header{padding:54px 0 26px}
.kicker{font-family:var(--mono);font-size:11.5px;letter-spacing:.36em;color:var(--mint);margin-bottom:14px}
.kicker b{color:var(--peach);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:.1em;line-height:1.2}
h1 .hl{color:var(--peach);text-shadow:0 0 24px rgba(255,157,138,.4)}
.headline-sub{margin-top:10px;color:var(--sub);letter-spacing:.05em;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:12px;transform:rotate(4deg);display:flex;flex-direction:column;align-items:center;justify-content:center;font-family:var(--serif);position:relative;background:rgba(224,93,85,.06);animation:breathe 3.6s ease-in-out infinite}
@keyframes breathe{0%,100%{box-shadow:0 0 0 rgba(224,93,85,0)}50%{box-shadow:0 0 22px rgba(224,93,85,.25)}}
.seal-badge::after{content:"";position:absolute;inset:4px;border:1px solid rgba(224,93,85,.4);border-radius:8px}
.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;animation:none}
.login-btn{font-family:var(--serif);font-size:14px;letter-spacing:.28em;color:var(--peach);border:1px solid rgba(255,157,138,.55);background:rgba(255,157,138,.08);padding:11px 24px 11px 30px;border-radius:12px;cursor:pointer;transition:all .25s}
.login-btn:hover{background:rgba(255,157,138,.16);box-shadow:0 0 24px rgba(255,157,138,.25)}
.login-user{font-family:var(--mono);font-size:12px;color:var(--mint);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:.08em;line-height:1.5;margin-bottom:10px}
.login-hero .copy .lead em{color:var(--peach);font-style:normal}
.login-hero .copy p{color:var(--sub);font-size:13.5px;line-height:1.85}
.login-box{flex:none;width:330px;padding:26px;border-radius:16px;background:rgba(255,240,228,.03);border:1px solid rgba(255,240,228,.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(--peach);box-shadow:0 0 9px rgba(255,157,138,.8);animation:pulse 2.4s infinite}
.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:11px}
.login-box input:focus{outline:none;border-color:var(--peach);box-shadow:0 0 0 3px rgba(255,157,138,.13)}
.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(--mint)}
.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:230px;height:230px;border-radius:50%;background:radial-gradient(circle,rgba(255,157,138,.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:.12em;border-radius:99px;padding:3px 12px}
.chip.c1{color:var(--peach);border:1px solid rgba(255,157,138,.4);background:rgba(255,157,138,.07)}
.chip.c2{color:var(--mint);border:1px solid rgba(159,216,195,.4);background:rgba(159,216,195,.07)}
.chip.c3{color:var(--gold);border:1px solid rgba(232,180,90,.4);background:rgba(232,180,90,.07)}
.studio h2{font-family:var(--serif);font-size:26px;letter-spacing:.08em;margin-bottom:8px}
.studio .desc{color:var(--sub);font-size:13.5px;max-width:660px;line-height:1.85;margin-bottom:20px}
.studio .acts{display:flex;gap:12px;flex-wrap:wrap}
.studio .deco{position:absolute;right:40px;bottom:22px;font-size:26px;opacity:.5;letter-spacing:8px}
.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(--peach);border-bottom-color:var(--peach);background:rgba(255,157,138,.05)}
.tab .cnt{font-family:var(--mono);font-size:10.5px;min-width:20px;padding:1px 6px;border-radius:99px;background:rgba(224,93,85,.14);color:var(--seal);border:1px solid rgba(224,93,85,.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}
.item{border:1px solid rgba(255,255,255,.08);border-left:2px solid var(--peach-soft);border-radius:13px;padding:14px 16px;margin-bottom:10px;background:rgba(255,255,255,.02);transition:border-color .25s,background .25s}
.item:hover{border-color:rgba(255,157,138,.3);background:rgba(255,157,138,.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:10px;transition:all .25s}
.btn-sign{color:var(--seal);border:1px solid rgba(224,93,85,.6);background:rgba(224,93,85,.07)}
.btn-sign:hover{background:rgba(224,93,85,.16);box-shadow:0 0 20px rgba(224,93,85,.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}
.pbadge.yy{color:var(--peach);border:1px solid rgba(255,157,138,.45);background:rgba(255,157,138,.07)}
.pbadge.ys{color:var(--mint);border:1px solid rgba(159,216,195,.45);background:rgba(159,216,195,.07)}
.win-item{border:1px solid rgba(159,216,195,.35);border-radius:11px;padding:10px 14px;margin-bottom:8px;background:rgba(159,216,195,.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}
.vbadge.pass{color:var(--mint);border:1px solid rgba(159,216,195,.4);background:rgba(159,216,195,.06)}
.vbadge.rej{color:var(--seal);border:1px solid rgba(224,93,85,.5);background:rgba(224,93,85,.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)}
.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:11px;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(--mint);box-shadow:0 0 8px rgba(159,216,195,.8);animation:pulse 2.4s infinite}
.dot.bad{background:var(--seal);box-shadow:0 0 8px rgba(224,93,85,.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(--peach-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(--peach-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(255,157,138,.4);color:var(--peach);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(--mint);border:1px solid rgba(159,216,195,.45);background:rgba(159,216,195,.07)}
.run-status.REJECTED{color:var(--seal);border:1px solid rgba(224,93,85,.5);background:rgba(224,93,85,.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(--mint);box-shadow:0 0 8px rgba(159,216,195,.6)}
.step.FAIL::before{border-color:var(--seal);box-shadow:0 0 8px rgba(224,93,85,.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(--peach);margin:8px 0 5px;text-shadow:0 0 16px rgba(255,157,138,.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(--mint)}
a{color:var(--peach);text-decoration:none}
.modal-bg{position:fixed;inset:0;background:rgba(8,6,5,.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:14px;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:12px}
@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="blob b3"></div>
<div class="paw" style="left:8%;animation-duration:19s;animation-delay:0s">🐾</div>
<div class="paw" style="left:22%;animation-duration:26s;animation-delay:5s">🐾</div>
<div class="paw" style="left:47%;animation-duration:22s;animation-delay:11s">🐾</div>
<div class="paw" style="left:66%;animation-duration:29s;animation-delay:3s">🐾</div>
<div class="paw" style="left:84%;animation-duration:24s;animation-delay:8s">🐾</div>
<div class="wrap">
<header>
<div class="kicker">光湖语言世界 <b>/</b> 第五域 <b>/</b> 守望语言子系统 · 宠物行业</div>
<div class="head-row">
<div>
<h1>守望<span class="hl">小院</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">HOME</span></div>
<div class="studio glass">
<div class="tag-row">
<span class="chip c1">宠物行业</span><span class="chip c2">诺安宠物医院</span><span class="chip c3">曜识 · 奶瓶频道</span>
</div>
<h2>毛毛 · 守望语言子系统</h2>
<div class="desc">这里是毛毛的宠物行业人格系统之家。曜砚管架构与仓库,曜识在奶瓶频道陪伴毛孩子和它们的家长。每一次创作与学习,都经毛毛审批、留下痕迹后写回这里。机器导航会带着抵达的 AI 走唤醒路径,不用自己找路。</div>
<div class="acts">
<a href="/code/maomao/maomao-fifth-domain" target="_blank" style="text-decoration:none"><button class="act btn-sign">进入守望仓库</button></a>
<a href="/api/wake/persona?name=yaoyan" target="_blank" style="text-decoration:none"><button class="act btn-deny">唤醒导航 API</button></a>
</div>
<div class="deco">🐾 🏠 🐾</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="#ff9d8a"/></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="#9fd8c3"/></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" id="svc-box"></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/maomao/maomao-fifth-domain" 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="/api/wake/persona?name=yaoyan" style="color:var(--mint)">人格体唤醒导航 ↗</a></div>
<div class="rhs" id="sync-line">守望 · MM-GZ-001</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(--peach);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="仓库账号(毛毛的账号)" 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:11px">
<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:11px">
<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 SVC_NAMES={"guanghu-mm-forgejo.service":"代码频道","guanghu-mm-tunnel.service":"广州投影","guanghu-mm-panel.service":"操作面板"};
let pending=null;
let session=localStorage.getItem("mmSession")||"";
let sessionUser=localStorage.getItem("mmUser")||"";
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);}
const C=326.7;
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 dotHtml(ok){return '<span class="dot '+(ok?"ok":"bad")+'"></span>';}
function toast(msg,ok){const t=document.getElementById("toast");t.textContent=msg;t.style.color=ok?"var(--mint)":"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("mmSession");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="仓库账号(毛毛的账号)"><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("mmSession",session);localStorage.setItem("mmUser",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("mmSession");localStorage.removeItem("mmUser");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);});}
let traceCur="push";
function switchTrace(name){traceCur=name;["push","wake","rcpt"].forEach(n=>{document.getElementById("trace-"+n).style.display=(n===name)?"block":"none";});
document.querySelectorAll(".card .tabs .tab").forEach(b=>{b.classList.toggle("active",b.textContent.includes({push:"写入日志",wake:"唤醒记录",rcpt:"回执"}[name]));});}
function setCnt(id,n){const el=document.getElementById(id);el.textContent=n;el.className="cnt"+(n?"":" zero");}
function ptag(p){const cls=p==="yaoshi"?"ys":"yy";const nm=p==="yaoshi"?"曜识":"曜砚";return '<span class="pbadge '+cls+'">'+nm+'</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){
if(s.gate){document.getElementById("gate-n").textContent=s.gate.score!=null?s.gate.score:"--";document.getElementById("gate-seal").className="seal-badge"+((s.gate.score===100)?"":" zero");}
const sv=s.server||{};
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";}
const svcBox=document.getElementById("svc-box");
svcBox.innerHTML=Object.keys(sv.services||{}).map(k=>'<div class="row-i"><span class="nm">'+(SVC_NAMES[k]||k)+'</span>'+dotHtml(sv.services[k])+'</div>').join("");
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?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(--mint)">+'+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(--mint)">'+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>';}
const DIR_TAG={"guanghu-nodes/MM-GZ-001/imports/notion":"小书包","guanghu-nodes/MM-GZ-001/contributions":"因果链","guanghu-nodes/MM-GZ-001/personas":"人格体"};
document.getElementById("trace-push").innerHTML=(s.pushLogs||[]).length?s.pushLogs.map(l=>'<div class="item" style="border-left-color:'+(l.verdict==="PASS"?"rgba(159,216,195,.5)":"rgba(224,93,85,.6)")+'"><div class="t">'+ptag(l.persona||"")+'推送 · '+(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>';
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">'+(DIR_TAG[w.dir]||w.dir.split("/").pop())+'</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="公开可读 · 唤醒导航与机器路径在内 · 合并须毛毛签字";
}
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>