Files
course-agent-od/web/public/p5-course.html
T
pengxiao ddbb79b9f6 Initial commit: 国土空间规划课程智能体 v1.0
单容器 Docker 架构的国土空间规划课程智能问答系统,集成 FastAPI 后端与 Next.js 前端。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 09:40:18 +08:00

196 lines
7.1 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>国土空间规划课程智能体</title>
<script src="https://cdn.jsdelivr.net/npm/p5@1.9.4/lib/p5.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&display=swap');
* { margin: 0; padding: 0; box-sizing: border-box; }
body { overflow: hidden; background: #0a0a1a; font-family: 'Noto Sans SC', system-ui, sans-serif; }
canvas { display: block; position: fixed; top: 0; left: 0; z-index: 0; }
#overlay {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
z-index: 1; pointer-events: none;
display: flex; flex-direction: column; justify-content: center; align-items: center;
}
#title {
font-size: 48px; font-weight: 700; color: white;
text-shadow: 0 0 40px rgba(100, 140, 255, 0.4);
margin-bottom: 12px; letter-spacing: 4px;
}
#subtitle {
font-size: 18px; font-weight: 300; color: rgba(180, 200, 255, 0.7);
margin-bottom: 40px; letter-spacing: 2px;
}
#tagline {
font-size: 14px; color: rgba(140, 160, 220, 0.5);
letter-spacing: 1px;
}
#info {
position: fixed; bottom: 20px; left: 50%;
transform: translateX(-50%);
color: rgba(255,255,255,0.3);
font-size: 12px; z-index: 2;
pointer-events: none;
}
</style>
</head>
<body>
<div id="overlay">
<div id="title">国土空间规划课程智能体</div>
<div id="subtitle">基于大模型的智能问答系统</div>
<div id="tagline">哈尔滨工业大学建筑与设计学院</div>
</div>
<div id="info">移动鼠标与知识网络互动</div>
<script>
const keywords = [
'容积率', '建筑密度', '绿地率', '控规', '总规', '详规',
'用地性质', '红线', '蓝线', '绿线', '紫线', '黄线',
'城市更新', '国土空间', '双评价', '三区三线',
'生态保护', '耕地保护', '城镇开发', '用途管制',
'空间结构', '交通规划', '基础设施', '公共服务',
'人口预测', '用地布局', '天际线', '风环境',
'海绵城市', '韧性城市', '智慧城市', '碳中和'
];
let nodes = [];
let nodeCount;
let connectDist = 150;
function setup() {
createCanvas(windowWidth, windowHeight);
nodeCount = min(floor(width * height / 12000), 80);
for (let i = 0; i < nodeCount; i++) {
nodes.push(createNode());
}
}
function createNode() {
let kw = random(keywords);
return {
x: random(width),
y: random(height),
vx: random(-0.3, 0.3),
vy: random(-0.3, 0.3),
size: textWidth(kw) || kw.length * 14,
baseSize: random(3, 6),
keyword: kw,
showText: random() > 0.5,
hue: random(200, 280),
phase: random(TWO_PI),
textAlpha: random() > 0.6 ? random(60, 150) : 0,
};
}
function draw() {
background(10, 10, 26, 30);
// 渐变背景(每帧微量覆盖)
noStroke();
fill(10, 10, 26, 25);
rect(0, 0, width, height);
// 绘制连线
for (let i = 0; i < nodes.length; i++) {
for (let j = i + 1; j < nodes.length; j++) {
let d = dist(nodes[i].x, nodes[i].y, nodes[j].x, nodes[j].y);
if (d < connectDist) {
let alpha = map(d, 0, connectDist, 60, 0);
let h = (nodes[i].hue + nodes[j].hue) / 2;
stroke(h, 60, 200, alpha);
strokeWeight(0.8);
line(nodes[i].x, nodes[i].y, nodes[j].x, nodes[j].y);
}
}
}
// 鼠标吸引线
for (let n of nodes) {
let d = dist(mouseX, mouseY, n.x, n.y);
if (d < 200) {
let alpha = map(d, 0, 200, 80, 0);
stroke(220, 180, 255, alpha);
strokeWeight(1);
line(mouseX, mouseY, n.x, n.y);
}
}
// 更新和绘制节点
for (let n of nodes) {
// 鼠标交互
let dm = dist(mouseX, mouseY, n.x, n.y);
if (dm < 180) {
let force = map(dm, 0, 180, 0.5, 0);
let angle = atan2(n.y - mouseY, n.x - mouseX);
n.vx += cos(angle) * force * 0.05;
n.vy += sin(angle) * force * 0.05;
}
// 运动
n.x += n.vx;
n.y += n.vy;
// 摩擦力
n.vx *= 0.99;
n.vy *= 0.99;
// 添加微小随机运动
n.vx += random(-0.02, 0.02);
n.vy += random(-0.02, 0.02);
// 边界
if (n.x < 0) { n.x = 0; n.vx *= -1; }
if (n.x > width) { n.x = width; n.vx *= -1; }
if (n.y < 0) { n.y = 0; n.vy *= -1; }
if (n.y > height) { n.y = height; n.vy *= -1; }
// 脉动效果
let pulse = sin(frameCount * 0.02 + n.phase) * 0.3 + 0.7;
// 绘制光晕
noStroke();
let glowAlpha = 20 * pulse;
fill(n.hue, 50, 200, glowAlpha);
ellipse(n.x, n.y, n.baseSize * 8, n.baseSize * 8);
fill(n.hue, 50, 200, glowAlpha * 1.5);
ellipse(n.x, n.y, n.baseSize * 4, n.baseSize * 4);
// 绘制节点
let nodeAlpha = (dm < 180) ? map(dm, 0, 180, 255, 120) : 120 * pulse;
fill(n.hue, 60, 240, nodeAlpha);
ellipse(n.x, n.y, n.baseSize * 2, n.baseSize * 2);
// 绘制关键词文字
if (n.textAlpha > 0) {
let ta = n.textAlpha * pulse;
if (dm < 180) ta = min(ta + 80, 220);
fill(n.hue, 30, 230, ta);
noStroke();
textAlign(CENTER, CENTER);
textSize(12);
textFont('Noto Sans SC');
text(n.keyword, n.x, n.y - n.baseSize * 2 - 8);
}
}
// 鼠标光标光晕
noStroke();
fill(230, 200, 255, 8);
ellipse(mouseX, mouseY, 200, 200);
fill(230, 200, 255, 15);
ellipse(mouseX, mouseY, 80, 80);
fill(230, 200, 255, 30);
ellipse(mouseX, mouseY, 20, 20);
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}
</script>
</body>
</html>