ddbb79b9f6
单容器 Docker 架构的国土空间规划课程智能问答系统,集成 FastAPI 后端与 Next.js 前端。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
196 lines
7.1 KiB
HTML
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>
|