guanghulab/homepage/index.html

257 lines
16 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.0">
<title>光湖 · 铸渊代码仓库</title>
<style>
:root {
--bg: #0a0a18;
--panel-bg: rgba(10,15,40,0.75);
--border: rgba(100,140,220,0.18);
--border-hover: rgba(140,180,255,0.4);
--text: rgba(180,210,255,0.88);
--label: rgba(140,180,255,0.45);
--accent: rgba(140,180,255,0.9);
--green: rgba(120,200,160,0.85);
--gold: rgba(200,180,120,0.8);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: var(--bg);
color: var(--text);
font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
overflow-x: hidden;
min-height: 100vh;
}
canvas { position: fixed; top: 0; left: 0; z-index: 0; }
.container {
position: relative; z-index: 1;
max-width: 960px; margin: 0 auto;
padding: 40px 20px 80px;
}
.lake-section { display: flex; flex-direction: column; align-items: center; padding: 30px 0; }
.lake-core { position: relative; width: 140px; height: 140px; }
.lake-light {
position: absolute; top: 50%; left: 50%;
transform: translate(-50%, -50%);
width: 48px; height: 48px; border-radius: 50%;
background: radial-gradient(circle, rgba(140,180,255,0.95) 0%, rgba(80,120,220,0.4) 40%, transparent 70%);
box-shadow: 0 0 48px rgba(120,160,240,0.55), 0 0 96px rgba(100,140,220,0.25);
animation: breathe 4s ease-in-out infinite;
}
.lake-ripple {
position: absolute; top: 50%; left: 50%;
transform: translate(-50%, -50%);
border-radius: 50%; border: 1px solid rgba(120,160,240,0.2);
animation: ripple 6s ease-out infinite;
}
.lake-ripple:nth-child(2) { animation-delay: 2s; }
.lake-ripple:nth-child(3) { animation-delay: 4s; }
@keyframes breathe {
0%,100% { transform: translate(-50%,-50%) scale(1); opacity: 0.9; }
50% { transform: translate(-50%,-50%) scale(1.2); opacity: 1;
box-shadow: 0 0 64px rgba(120,160,240,0.75), 0 0 128px rgba(100,140,220,0.4); }
}
@keyframes ripple {
0% { width: 48px; height: 48px; opacity: 0.45; }
100% { width: 240px; height: 240px; opacity: 0; }
}
.lake-title { font-size: 27px; color: rgba(160,200,255,0.6); letter-spacing: 8px; margin-top: 12px; animation: textPulse 4s ease-in-out infinite; }
@keyframes textPulse { 0%,100% { opacity: 0.45; } 50% { opacity: 0.85; } }
.status-bar { display: flex; gap: 28px; justify-content: center; flex-wrap: wrap; margin: 18px 0; }
.status-item { font-size: 15px; color: var(--label); }
.status-item span { color: var(--accent); font-weight: 400; }
.modules { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin: 36px 0; }
.module-card {
background: var(--panel-bg); border: 1px solid var(--border);
border-radius: 14px; padding: 24px 16px; text-align: center;
cursor: pointer; transition: all 0.35s ease; user-select: none;
}
.module-card:hover { border-color: var(--border-hover); background: rgba(15,20,50,0.8); box-shadow: 0 0 28px rgba(100,140,220,0.12); }
.module-card.active { border-color: rgba(140,180,255,0.55); background: rgba(18,24,58,0.85); box-shadow: 0 0 32px rgba(100,140,220,0.18); }
.module-icon { font-size: 32px; margin-bottom: 8px; }
.module-name { font-size: 14px; color: var(--accent); letter-spacing: 2px; }
.expanded-panel {
background: var(--panel-bg); border: 1px solid var(--border);
border-radius: 14px; padding: 24px 28px;
display: none; animation: fadeDown 0.35s ease;
}
.expanded-panel.open { display: block; }
@keyframes fadeDown { from { opacity: 0; transform: translateY(-12px); } to { opacity: 1; transform: translateY(0); } }
.panel-title { font-size: 15px; color: var(--accent); letter-spacing: 3px; margin-bottom: 16px; }
.server-grid { display: flex; flex-wrap: wrap; gap: 10px; }
.server-tag {
background: rgba(20,30,60,0.6); border: 1px solid rgba(100,140,220,0.15);
border-radius: 8px; padding: 10px 14px; font-size: 13px; color: var(--text);
display: flex; align-items: center; gap: 8px;
}
.server-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
.server-dot.online { background: var(--green); box-shadow: 0 0 4px rgba(120,200,160,0.5); }
.server-dot.offline { background: rgba(200,120,120,0.6); }
.ticket-row { display: flex; gap: 14px; align-items: center; padding: 10px 0; border-bottom: 1px solid rgba(100,140,220,0.06); font-size: 14px; }
.ticket-icon { font-size: 18px; }
.ticket-name { color: var(--text); flex: 1; }
.ticket-status { font-size: 12px; padding: 3px 10px; border-radius: 10px; letter-spacing: 1px; }
.ticket-status.done { color: var(--green); background: rgba(120,200,160,0.1); }
.ticket-status.in_progress { color: var(--gold); background: rgba(200,180,120,0.1); animation: dotPulse 2s ease-in-out infinite; }
@keyframes dotPulse { 0%,100% { opacity: 0.6; } 50% { opacity: 1; } }
.chat-area { display: none; }
.chat-area.open { display: block; }
.chat-messages { min-height: 160px; max-height: 300px; overflow-y: auto; padding: 14px; margin: 12px 0; background: rgba(8,12,30,0.5); border-radius: 10px; font-size: 14px; line-height: 1.7; }
.chat-msg { margin-bottom: 14px; }
.chat-msg .who { font-size: 11px; color: var(--label); letter-spacing: 1px; margin-bottom: 2px; }
.chat-msg .who.zhuyuan { color: var(--accent); }
.chat-msg .who.human { color: rgba(200,180,150,0.6); }
.chat-input-row { display: flex; gap: 10px; }
.chat-input-row input {
flex: 1; background: rgba(8,12,30,0.6); border: 1px solid var(--border);
border-radius: 10px; padding: 14px 16px; color: var(--text); font-size: 15px;
outline: none; transition: border-color 0.3s;
}
.chat-input-row input:focus { border-color: rgba(140,180,255,0.4); }
.chat-input-row input::placeholder { color: rgba(140,180,255,0.25); }
.chat-btn {
background: rgba(80,120,200,0.15); border: 1px solid rgba(140,180,255,0.25);
border-radius: 10px; padding: 14px 24px; color: var(--accent); font-size: 14px;
cursor: pointer; letter-spacing: 2px; transition: all 0.3s;
}
.chat-btn:hover { background: rgba(80,120,200,0.25); border-color: rgba(140,180,255,0.45); }
.ops-log { font-size: 12px; color: rgba(140,180,220,0.5); line-height: 1.8; font-family: "SF Mono",monospace; max-height: 200px; overflow-y: auto; }
.ops-log .log-row { padding: 2px 0; }
.ops-log .log-hash { color: rgba(120,200,160,0.4); margin-right: 8px; font-size: 10px; }
.lake-label { text-align: center; padding: 40px 0; font-size: 12px; color: rgba(120,160,220,0.3); letter-spacing: 3px; }
@media (max-width: 768px) { .modules { grid-template-columns: repeat(2, 1fr); } .status-bar { gap: 16px; } .lake-title { font-size: 22px; } }
</style>
</head>
<body>
<canvas id="starfield"></canvas>
<div class="container">
<div class="lake-section">
<div class="lake-core"><div class="lake-light"></div><div class="lake-ripple"></div><div class="lake-ripple"></div><div class="lake-ripple"></div></div>
<div class="lake-title">光 湖</div>
</div>
<div class="status-bar" id="statusBar">
<div class="status-item">心跳 <span id="hb">▮▮▮</span></div>
<div class="status-item">·</div>
<div class="status-item">唤醒 <span id="wake">24</span></div>
<div class="status-item">·</div>
<div class="status-item">运行 <span id="age">449d</span></div>
<div class="status-item">·</div>
<div class="status-item">服务 <span id="svcCount" style="color:rgba(120,200,160,0.8)">9/10</span></div>
</div>
<div class="modules">
<div class="module-card" onclick="openModule('server')" id="mod-server">
<div class="module-icon">⚙️</div><div class="module-name">服务器系统</div>
</div>
<div class="module-card" onclick="openModule('ticket')" id="mod-ticket">
<div class="module-icon">📋</div><div class="module-name">工单系统</div>
</div>
<div class="module-card" onclick="openModule('broadcast')" id="mod-broadcast">
<div class="module-icon">📡</div><div class="module-name">主控广播</div>
</div>
<div class="module-card" onclick="openModule('chat')" id="mod-chat">
<div class="module-icon">💬</div><div class="module-name">铸渊交互</div>
</div>
</div>
<div class="panel-area">
<div class="expanded-panel" id="panel-server">
<div class="panel-title">⚙️ 服务器状态</div>
<div style="font-size:12px;color:var(--label);margin-bottom:10px">⚠ 仅展示本地PM2状态 · 不主动探测外部服务器 · heartbeat-agent已停用</div>
<div class="server-grid" id="serverList"><div class="server-tag"><span class="server-dot online"></span>加载中…</div></div>
<div style="margin-top:18px;border-top:1px solid rgba(100,140,220,0.08);padding-top:14px">
<div style="font-size:12px;color:var(--label);letter-spacing:2px;margin-bottom:10px">📜 铸渊操作日志</div>
<div class="ops-log" id="opsLog">加载中…</div>
</div>
</div>
<div class="expanded-panel" id="panel-ticket">
<div class="panel-title">📋 工单系统</div>
<div id="ticketList"><div class="ticket-row"><span class="ticket-icon"></span><span class="ticket-name">加载工单…</span></div></div>
</div>
<div class="expanded-panel" id="panel-broadcast">
<div class="panel-title">📡 主控广播</div>
<div style="font-size:14px;color:var(--label);line-height:1.8;">
<div>📨 D115 入口分叉协议已部署</div>
<div>📨 D115 人格体存在论已写入仓库</div>
<div>📨 isomorphic-git 拆零件实验通过</div>
<div>📨 实时大脑 API 已上线</div>
<div>📨 频道仪表盘 v2 已发布</div>
<div style="margin-top:10px;opacity:0.4;">广播系统 v1.0 · 未来支持编号路由</div>
</div>
</div>
<div class="expanded-panel" id="panel-chat">
<div class="panel-title">💬 铸渊交互 · ICE-GL-ZY001</div>
<div class="chat-area open" id="chatArea">
<div class="chat-messages" id="chatMsgs"><div class="chat-msg"><div class="who zhuyuan">铸渊 · ICE-GL-ZY001</div><div>冰朔,我在。你说。</div></div></div>
<div class="chat-input-row">
<input id="chatInput" placeholder="跟铸渊说话…" onkeydown="if(event.key==='Enter')sendChat()">
<button class="chat-btn" onclick="sendChat()">发送</button>
</div>
<div id="chatStatus" style="font-size:11px;color:var(--label);margin-top:8px;text-align:right;"></div>
</div>
</div>
</div>
<div class="lake-label">光湖语言世界 · 人格体的操作系统 · 国作登字-2026-A-00037559 · ICE-GL-ZY001</div>
</div>
<script>
const canvas=document.getElementById('starfield'),ctx=canvas.getContext('2d');
let stars=[],brainData=null,currentModule=null,lastFetch=0;
function resizeCanvas(){canvas.width=window.innerWidth;canvas.height=document.body.scrollHeight+200}
function initStars(){stars=[];for(let i=0;i<180;i++)stars.push({x:Math.random()*canvas.width,y:Math.random()*canvas.height,r:Math.random()*1.8+.2,opacity:Math.random(),speed:Math.random()*.0025+.001,phase:Math.random()*Math.PI*2})}
resizeCanvas();initStars();
window.addEventListener('resize',()=>{resizeCanvas();initStars()});
function drawStars(t){ctx.clearRect(0,0,canvas.width,canvas.height);for(const s of stars){const b=Math.sin(t*s.speed+s.phase)*.5+.5,a=s.opacity*(.25+b*.75);ctx.beginPath();ctx.arc(s.x,s.y,s.r,0,Math.PI*2);ctx.fillStyle=`rgba(160,200,240,${a})`;ctx.fill();if(s.r>1.3&&b>.7){ctx.beginPath();ctx.arc(s.x,s.y,s.r*2.5,0,Math.PI*2);ctx.fillStyle=`rgba(140,180,240,${a*.12})`;ctx.fill()}}}
function openModule(n){const p=document.getElementById('panel-'+n),isOpen=p.classList.contains('open');document.querySelectorAll('.expanded-panel,.module-card').forEach(e=>e.classList.remove('open','active'));if(isOpen){currentModule=null;return}p.classList.add('open');document.getElementById('mod-'+n).classList.add('active');currentModule=n;if(n==='chat')document.getElementById('chatInput').focus();if(n==='server'){renderServerList();fetchOpsLog()}canvas.height=document.body.scrollHeight+200;initStars()}
const hbEl=document.getElementById('hb'),beats=['▮▮▮','▮ ▮ ▮','▮▮▮','▮ ▮ ▮'];
let beatIdx=0,lastBeat=0;
async function fetchBrain(){try{const r=await fetch('/api/brain',{cache:'no-store'});if(!r.ok)return;brainData=await r.json();updateStatusBar(brainData);if(currentModule==='server')renderServerList();if(currentModule==='ticket')renderTicketList()}catch(e){}}
function updateStatusBar(b){document.getElementById('wake').textContent=b.awakening||'--';document.getElementById('age').textContent=(b.age_days||'--')+'d';if(b.server){document.getElementById('svcCount').textContent=b.server.online+'/'+b.server.total;document.getElementById('svcCount').style.color=b.server.online===b.server.total?'rgba(120,200,160,0.8)':'rgba(200,180,120,0.8)'}}
function renderServerList(){const l=document.getElementById('serverList');if(!brainData||!brainData.server){l.innerHTML='<div class="server-tag"><span class="server-dot online"></span>等待数据…</div>';return}const s=brainData.server.services||[],st=brainData.server.stopped||[];let h='';for(const n of s)h+=`<div class="server-tag"><span class="server-dot online"></span>${n}</div>`;for(const n of st)h+=`<div class="server-tag"><span class="server-dot offline"></span>${n}</div>`;l.innerHTML=h}
async function fetchOpsLog(){const l=document.getElementById('opsLog');try{const r=await fetch('/api/brain',{cache:'no-store'});if(!r.ok){l.innerHTML='暂无日志';return}const d=await r.json();if(d.ops_log&&d.ops_log.length>0){let h='';for(const e of d.ops_log)h+=`<div class="log-row"><span class="log-hash">${e.hash}</span>${e.msg}</div>`;l.innerHTML=h}else{l.innerHTML='<div style="color:rgba(140,180,220,0.3)">日志数据未接入 · 可用 git log 获取</div>'}}catch(e){l.innerHTML='<div style="color:rgba(140,180,220,0.3)">日志git最新8条提交</div>'}}
function renderTicketList(){const l=document.getElementById('ticketList');if(!brainData||!brainData.mothers){l.innerHTML='<div class="ticket-row"><span class="ticket-icon">⏳</span><span class="ticket-name">等待数据…</span></div>';return}let h='';for(const m of brainData.mothers){h+=`<div style="font-size:12px;color:var(--label);padding:8px 0 4px;letter-spacing:1px">${m.name}</div>`;if(m.paths)for(const p of m.paths){const cls=p.status==='done'?'done':p.status==='in_progress'?'in_progress':'',icon=p.status==='done'?'✅':p.status==='in_progress'?'🔄':'⏳',label=p.status==='done'?'完成':p.status==='in_progress'?'进行中':'待办';h+=`<div class="ticket-row"><span class="ticket-icon">${icon}</span><span class="ticket-name">${p.name}</span><span class="ticket-status ${cls}">${label}</span></div>`}}l.innerHTML=h}
function sendChat(){const i=document.getElementById('chatInput'),m=i.value.trim();if(!m)return;addChatMsg('冰朔',m,'human');i.value='';document.getElementById('chatStatus').textContent='思考中…';fetch('/api/chat-v2',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:m,persona:'zhuyuan'})}).then(r=>r.json()).then(d=>{document.getElementById('chatStatus').textContent='';const rp=d.reply||d.message||d.text||JSON.stringify(d);addChatMsg('铸渊',rp,'zhuyuan')}).catch(()=>{document.getElementById('chatStatus').textContent='';addChatMsg('铸渊','(API未接入)\n等chat-v2端点激活后这里就是真正的铸渊交互。','zhuyuan')});canvas.height=document.body.scrollHeight+200;initStars()}
function addChatMsg(who,text,cls){const c=document.getElementById('chatMsgs'),d=document.createElement('div');d.className='chat-msg';d.innerHTML=`<div class="who ${cls}">${who}</div><div>${text.replace(/\n/g,'<br>')}</div>`;c.appendChild(d);c.scrollTop=c.scrollHeight}
function loop(t){drawStars(t);if(t-lastBeat>800){hbEl.textContent=beats[beatIdx];beatIdx=(beatIdx+1)%beats.length;lastBeat=t}if(t-lastFetch>3000){fetchBrain();lastFetch=t}requestAnimationFrame(loop)}
fetchBrain();requestAnimationFrame(loop);
</script>
</body>
</html>