Files
game/website/demo.html
T
李鹏宇 f5c7ea4ddf 1.世界等级五个阶段,自己想办法命名和设计
2.势力等级分为6个
3.人物角色等级分为14个
4.货币也分为6档

一个背景的菜单栏。
写特色,介绍,背景,说明,致玩家等等
2026-07-23 18:53:54 +08:00

1087 lines
42 KiB
HTML

<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>宇森 · 世界沙盘</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #0a0e17; overflow: hidden; font-family: 'Microsoft YaHei', sans-serif; color: #e0e0e0; }
#gameCanvas { display: block; cursor: default; }
#gameCanvas.dragging { cursor: grabbing; }
#gameCanvas.canDrag { cursor: grab; }
#topBar {
position: fixed; top: 0; left: 0; right: 0; height: 44px;
background: linear-gradient(180deg, rgba(10,14,23,0.95) 0%, rgba(10,14,23,0.7) 100%);
display: flex; align-items: center; padding: 0 20px;
border-bottom: 1px solid rgba(100,200,255,0.15);
z-index: 100; backdrop-filter: blur(8px);
}
#topBar .title {
font-size: 15px; font-weight: bold;
background: linear-gradient(90deg, #4af, #a8e6cf);
-webkit-background-clip: text; -webkit-text-fill-color:transparent;
text-decoration:none;
}
#topBar .nav-links { display:flex; gap:16px; margin-left:24px; }
#topBar .nav-links a { color:#667788; text-decoration:none; font-size:12px; transition:color 0.2s; }
#topBar .nav-links a:hover { color:#4af; }
#topBar .coords { margin-left:8px; font-size:12px; color:#8899aa; }
/* 微观世界视图 */
#microView {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: #0a0e17; z-index: 200;
display: none; flex-direction: column;
}
#microView.active { display: flex; }
#microHeader {
height: 50px; display: flex; align-items: center; padding: 0 16px;
background: linear-gradient(90deg, rgba(10,14,23,0.98), rgba(20,30,50,0.98));
border-bottom: 1px solid rgba(100,200,255,0.2);
z-index: 10; flex-shrink: 0;
pointer-events: auto;
}
#microHeader .backBtn {
background: rgba(100,200,255,0.15); border: 1px solid rgba(100,200,255,0.3);
color: #4af; padding: 6px 16px; border-radius: 6px; cursor: pointer;
font-size: 13px; transition: all 0.2s;
}
#microHeader .backBtn:hover { background: rgba(100,200,255,0.25); }
#microHeader .microTitle { margin-left: 16px; font-size: 16px; font-weight: bold; color: #e0e0e0; }
#microHeader .microSubtitle { margin-left: 12px; font-size: 12px; color: #8899aa; }
#microBody { flex: 1; display: flex; position: relative; overflow: hidden; }
#microCanvas { flex: 1; display: block; }
/* 微观世界右侧面板 */
#microSidebar {
width: 280px; background: linear-gradient(270deg, rgba(10,14,23,0.98) 0%, rgba(10,14,23,0.85) 100%);
border-left: 1px solid rgba(100,200,255,0.15);
padding: 16px; overflow-y: auto; flex-shrink: 0;
pointer-events: auto;
}
#microSidebar::-webkit-scrollbar { width: 4px; }
#microSidebar::-webkit-scrollbar-thumb { background: rgba(100,200,255,0.3); border-radius: 2px; }
.panel-section { margin-bottom: 20px; }
.panel-label { font-size: 11px; color: #667788; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 8px; }
.panel-value { font-size: 14px; color: #e0e0e0; margin-bottom: 4px; }
.resource-bar { height: 18px; background: rgba(255,255,255,0.08); border-radius: 9px; overflow: hidden; margin: 6px 0; position: relative; }
.resource-fill { height: 100%; border-radius: 9px; transition: width 0.5s ease; position: relative; }
.resource-fill::after {
content: ''; position: absolute; top: 0; left: 0; right: 0; height: 50%;
background: linear-gradient(180deg, rgba(255,255,255,0.2), transparent);
border-radius: 9px 9px 0 0;
}
.unit-card {
background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1);
border-radius: 8px; padding: 10px; margin-bottom: 8px;
cursor: pointer; transition: all 0.2s;
}
.unit-card:hover { background: rgba(100,200,255,0.1); border-color: rgba(100,200,255,0.3); }
.unit-card .unit-name { font-size: 13px; font-weight: bold; color: #e0e0e0; }
.unit-card .unit-role { font-size: 11px; color: #8899aa; }
.unit-card .unit-status { font-size: 11px; margin-top: 4px; }
.status-working { color: #8bc34a; }
.status-resting { color: #ff9800; }
.status-moving { color: #4af; }
/* 飞入过渡遮罩 */
#flyInOverlay {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: #0a0e17; z-index: 300;
display: none; justify-content: center; align-items: center; flex-direction: column;
}
#flyInOverlay.active { display: flex; }
.fly-circle {
width: 60px; height: 60px; border: 2px solid rgba(100,200,255,0.5);
border-top-color: #4af; border-radius: 50%;
animation: flySpin 1s linear infinite;
}
#flyInOverlay .flyText {
font-size: 18px; color: #4af; margin-top: 20px;
animation: flyPulse 1s ease-in-out infinite;
}
@keyframes flySpin { to { transform: rotate(360deg); } }
@keyframes flyPulse { 0%, 100% { opacity: 0.5; } 50% { opacity: 1; } }
/* 提示 */
#tip {
position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%);
background: rgba(10,14,23,0.9); border: 1px solid rgba(100,200,255,0.3);
padding: 8px 20px; border-radius: 20px; font-size: 12px; color: #8899aa;
z-index: 100; pointer-events: none; transition: opacity 0.3s;
}
/* 加载界面 */
#loadingScreen {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: #0a0e17; z-index: 500;
display: flex; flex-direction: column; justify-content: center; align-items: center;
}
#loadingScreen .loadTitle {
font-size: 24px; font-weight: bold; margin-bottom: 20px;
background: linear-gradient(90deg, #4af, #a8e6cf);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
}
#loadingScreen .loadBar { width: 200px; height: 3px; background: rgba(255,255,255,0.1); border-radius: 2px; overflow: hidden; }
#loadingScreen .loadFill { height: 100%; width: 0%; background: linear-gradient(90deg, #4af, #a8e6cf); border-radius: 2px; transition: width 0.3s; }
#loadingScreen .loadText { font-size: 12px; color: #667788; margin-top: 12px; }
/* 单位详情弹窗 */
#unitModal {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.7); z-index: 400; display: none;
justify-content: center; align-items: center;
}
#unitModal.active { display: flex; }
#unitModal .modalContent {
background: #1a2030; border: 1px solid rgba(100,200,255,0.3); border-radius: 12px;
padding: 24px; max-width: 340px; width: 90%; pointer-events: auto;
}
#unitModal .modalTitle { font-size: 16px; font-weight: bold; color: #e0e0e0; margin-bottom: 16px; }
#unitModal .statRow { display: flex; justify-content: space-between; align-items: center; margin: 6px 0; font-size: 12px; }
#unitModal .statLabel { color: #8899aa; }
#unitModal .statValue { color: #e0e0e0; }
#unitModal .statBar { flex: 1; height: 8px; background: rgba(255,255,255,0.08); border-radius: 4px; margin: 0 8px; overflow: hidden; }
#unitModal .statFill { height: 100%; border-radius: 4px; }
#unitModal .cmdBtn {
display: block; width: 100%; margin-top: 8px; padding: 8px;
background: rgba(100,200,255,0.1); border: 1px solid rgba(100,200,255,0.25);
color: #cce; border-radius: 6px; cursor: pointer; font-size: 12px; text-align: center;
transition: all 0.2s;
}
#unitModal .cmdBtn:hover { background: rgba(100,200,255,0.2); }
#unitModal .closeBtn {
margin-top: 16px; width: 100%; padding: 8px; background: rgba(255,80,80,0.15);
border: 1px solid rgba(255,80,80,0.3); color: #f88; border-radius: 6px;
cursor: pointer; font-size: 13px;
}
</style>
<link rel="stylesheet" href="auth.css">
</head>
<body>
<!-- 加载界面 -->
<div id="loadingScreen">
<div class="loadTitle">元宇宙世界</div>
<div class="loadBar"><div class="loadFill" id="loadFill"></div></div>
<div class="loadText" id="loadText">正在生成世界...</div>
</div>
<!-- 主视图 -->
<canvas id="gameCanvas"></canvas>
<div id="topBar">
<a href="index.html" class="title">✦ 宇森</a>
<div class="nav-links">
<a href="intro.html">介绍</a>
<a href="platform.html">架构</a>
<a href="world.html">世界</a>
<a href="races.html">种族</a>
</div>
<div id="auth-area" style="margin-left:auto; margin-right:8px;"></div>
<span class="coords" id="coords">坐标: (0, 0)</span>
</div>
<div id="tip">🖱️ 滚轮缩放 · 拖拽移动 · 点击格子进入微观世界</div>
<!-- 飞入过渡 -->
<div id="flyInOverlay">
<div class="fly-circle"></div>
<div class="flyText">正在进入微观世界...</div>
</div>
<!-- 微观世界视图 -->
<div id="microView">
<div id="microHeader">
<button class="backBtn" id="backBtn">← 返回世界</button>
<span class="microTitle" id="microTitle">平原村落</span>
<span class="microSubtitle" id="microSubtitle">微观世界视角</span>
</div>
<div id="microBody">
<canvas id="microCanvas"></canvas>
<div id="microSidebar">
<div class="panel-section">
<div class="panel-label">地块信息</div>
<div class="panel-value" id="detailTerrain">🟢 平原</div>
<div class="panel-value" id="detailCoords">坐标: (0, 0)</div>
</div>
<div class="panel-section">
<div class="panel-label">建筑</div>
<div id="detailBuilding">无</div>
</div>
<div class="panel-section">
<div class="panel-label">资源产出</div>
<div id="detailResources"></div>
</div>
<div class="panel-section">
<div class="panel-label">人口</div>
<div id="detailPopulation"></div>
</div>
</div>
</div>
</div>
<!-- 单位详情弹窗 -->
<div id="unitModal">
<div class="modalContent">
<div class="modalTitle" id="modalTitle">👤 单位详情</div>
<div id="modalBody"></div>
<button class="closeBtn" onclick="closeUnitModal()">关闭</button>
</div>
</div>
<script>
// ============ 配置 ============
const MAP_RADIUS = 4;
const POLY_SIZE = 52;
const SIDES = 6; // 正六边形
let currentLayer = 'surface';
// ============ 地形 ============
const TERRAIN = {
plain: { name: '平原', color: '#4a7c4a', microBg: '#3d6b3d', groundColor: '#5a8a4a', scene: 'village' },
forest: { name: '森林', color: '#2d5a2d', microBg: '#1e4a1e', groundColor: '#2a5a2a', scene: 'forest' },
mountain: { name: '山脉', color: '#6b6b6b', microBg: '#5a5a5a', groundColor: '#7a7a7a', scene: 'mountain' },
water: { name: '水域', color: '#1a4a7a', microBg: '#0e3a6a', groundColor: '#1a5a8a', scene: 'water' },
desert: { name: '荒漠', color: '#b8944a', microBg: '#a07e3a', groundColor: '#c8a85a', scene: 'desert' },
snow: { name: '雪地', color: '#8aaaaa', microBg: '#7a9aaa', groundColor: '#c8d8e8', scene: 'snow' },
wetland: { name: '湿地', color: '#3a6a5a', microBg: '#2a5a4a', groundColor: '#4a7a6a', scene: 'wetland' }
};
// ============ 建筑 ============
const BUILDINGS = {
farm: { name: '农场', icon: '🌾', color: '#8bc34a', yield: { food: 8 } },
lumber: { name: '伐木场', icon: '🪓', color: '#795548', yield: { wood: 6 } },
mine: { name: '矿场', icon: '⛏', color: '#ff9800', yield: { metal: 5 } },
house: { name: '民居', icon: '🏠', color: '#64b5f6', yield: { pop: 4 } },
market: { name: '集市', icon: '🏪', color: '#4dd0e1', yield: { gold: 6 } },
temple: { name: '神庙', icon: '⛩', color: '#fff176', yield: { culture: 3 } },
barracks: { name: '兵营', icon: '⚔', color: '#ef5350', yield: { military: 8 } },
library: { name: '图书馆', icon: '📚', color: '#ab47bc', yield: { research: 4 } }
};
// ============ 单位 ============
const UNITS = {
farmer: { name: '农夫', icon: '👨🌾', role: 'farm' },
lumberjack:{ name: '伐木工', icon: '🪓', role: 'lumber' },
miner: { name: '矿工', icon: '⛏️', role: 'mine' },
merchant: { name: '商人', icon: '🧑💼', role: 'trade' },
guard: { name: '守卫', icon: '💂', role: 'defense' },
scholar: { name: '学者', icon: '📖', role: 'research' },
priest: { name: '祭司', icon: '🧙', role: 'culture' },
child: { name: '孩童', icon: '👶', role: 'none' },
fisher: { name: '渔民', icon: '🎣', role: 'fish' }
};
// ============ 全局状态 ============
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
let W, H;
let camera = { x: 0, y: 0, zoom: 1.6 };
let hoveredTile = null;
let selectedTile = null;
// 拖拽状态(修复版)
let isMouseDown = false;
let hasDragged = false;
let mouseDownX = 0;
let mouseDownY = 0;
let dragCamStartX = 0;
let dragCamStartY = 0;
let tiles = [];
let animTime = 0;
let stars = [];
// 微观世界
let microState = null;
let microCanvas, microCtx;
let microAnimId = null;
// ============ 地图生成 ============
function generateMap() {
tiles = [];
for (let q = -MAP_RADIUS; q <= MAP_RADIUS; q++) {
for (let r = -MAP_RADIUS; r <= MAP_RADIUS; r++) {
const s = -q - r;
if (Math.abs(s) > MAP_RADIUS) continue;
const dist = Math.sqrt(q * q + r * r) / MAP_RADIUS;
let terrain;
if (dist < 0.15) terrain = 'plain';
else if (dist < 0.3) terrain = Math.random() > 0.4 ? 'forest' : 'plain';
else if (dist < 0.5) terrain = Math.random() > 0.5 ? 'mountain' : 'plain';
else if (Math.random() > 0.65) terrain = 'desert';
else if (Math.random() > 0.8) terrain = 'snow';
else if (Math.random() > 0.85) terrain = 'water';
else terrain = 'plain';
const hasBuilding = Math.random() > 0.7;
const buildingType = hasBuilding ? Object.keys(BUILDINGS)[Math.floor(Math.random() * Object.keys(BUILDINGS).length)] : null;
const unitCount = hasBuilding ? Math.floor(Math.random() * 4) + 1 : (Math.random() > 0.55 ? Math.floor(Math.random() * 2) + 1 : 0);
const units = [];
for (let i = 0; i < unitCount; i++) {
const uKeys = Object.keys(UNITS);
units.push({
...UNITS[uKeys[Math.floor(Math.random() * uKeys.length)]],
id: Math.random().toString(36).substr(2, 6),
status: Math.random() > 0.3 ? 'working' : 'resting',
hp: 80 + Math.floor(Math.random() * 40),
energy: 60 + Math.floor(Math.random() * 40),
mood: ['😊','😐','😄'][Math.floor(Math.random()*3)],
level: Math.floor(Math.random() * 3) + 1
});
}
tiles.push({ q, r, s, terrain, layer: 'surface', hasBuilding, buildingType,
buildingLevel: hasBuilding ? Math.floor(Math.random() * 3) + 1 : 0,
units, unitCount, explored: Math.random() > 0.2
});
}
}
}
// ============ 坐标工具 ============
function hexToPixel(q, r) {
const x = POLY_SIZE * (Math.sqrt(3) * q + Math.sqrt(3) / 2 * r);
const y = POLY_SIZE * (3 / 2 * r);
return { x: x * camera.zoom + W / 2 + camera.x, y: y * camera.zoom + H / 2 + camera.y };
}
function pixelToHex(px, py) {
const size = POLY_SIZE * camera.zoom;
const x = (px - W / 2 - camera.x) / size;
const y = (py - H / 2 - camera.y) / size;
let q = (Math.sqrt(3) / 3 * x - 1 / 3 * y);
let r = (2 / 3 * y);
const fq = Math.round(q), fr = Math.round(r);
return { q: fq, r: fr, s: -fq - fr };
}
function getTile(q, r) { return tiles.find(t => t.q === q && t.r === r && t.layer === currentLayer); }
// ============ 绘制12边形 ============
function drawPoly(x, y, size, fillColor, strokeColor, lineWidth) {
ctx.beginPath();
for (let i = 0; i < SIDES; i++) {
const angle = (Math.PI * 2 / SIDES) * i - Math.PI / 2;
const px = x + size * Math.cos(angle);
const py = y + size * Math.sin(angle);
if (i === 0) ctx.moveTo(px, py); else ctx.lineTo(px, py);
}
ctx.closePath();
if (fillColor) { ctx.fillStyle = fillColor; ctx.fill(); }
if (strokeColor) { ctx.strokeStyle = strokeColor; ctx.lineWidth = lineWidth || 1; ctx.stroke(); }
}
// ============ 渲染 ============
function render() {
animTime += 0.016;
ctx.clearRect(0, 0, W, H);
drawStarfield();
const size = POLY_SIZE * camera.zoom;
const sortedTiles = tiles.filter(t => t.layer === currentLayer).sort((a, b) => {
return (a.r + a.q * 0.5) - (b.r + b.q * 0.5);
});
sortedTiles.forEach(tile => {
const { x, y } = hexToPixel(tile.q, tile.r);
if (x < -size * 2 || x > W + size * 2 || y < -size * 2 || y > H + size * 2) return;
const terrain = TERRAIN[tile.terrain];
const isHovered = hoveredTile && hoveredTile.q === tile.q && hoveredTile.r === tile.r;
const isSelected = selectedTile && selectedTile.q === tile.q && selectedTile.r === tile.r;
let fillColor = terrain.color;
let strokeColor = 'rgba(255,255,255,0.12)';
let lineWidth = 1;
if (isSelected) { strokeColor = '#4af'; lineWidth = 2.5; }
else if (isHovered) { strokeColor = 'rgba(100,200,255,0.6)'; lineWidth = 1.5; }
drawPoly(x, y, size * 0.99, fillColor, strokeColor, lineWidth);
// 悬停光晕
if (isHovered) {
ctx.save();
ctx.shadowColor = '#4af'; ctx.shadowBlur = 12;
drawPoly(x, y, size * 0.99, 'rgba(100,200,255,0.08)', '#4af', 1.5);
ctx.restore();
}
// 地形图标
if (camera.zoom > 0.3) {
ctx.font = `${13 * camera.zoom}px sans-serif`;
ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
const icons = { plain:'🌿', forest:'🌲', mountain:'⛰', water:'💧', desert:'🏜', snow:'❄', wetland:'🌾' };
ctx.fillText(icons[tile.terrain] || '', x, y - 5 * camera.zoom);
}
// 建筑
if (tile.hasBuilding && camera.zoom > 0.25) {
const b = BUILDINGS[tile.buildingType];
const bSize = 15 * camera.zoom;
const pulse = 0.7 + 0.3 * Math.sin(animTime * 3 + tile.q * 0.7 + tile.r * 0.7);
ctx.save();
ctx.shadowColor = b.color; ctx.shadowBlur = 8 * pulse;
ctx.fillStyle = b.color; ctx.globalAlpha = 0.2 * pulse;
ctx.beginPath(); ctx.arc(x, y + 7 * camera.zoom, bSize * 0.7, 0, Math.PI * 2); ctx.fill();
ctx.restore();
ctx.font = `${14 * camera.zoom}px sans-serif`;
ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
ctx.fillText(b.icon, x, y + 12 * camera.zoom);
// 等级
if (tile.buildingLevel > 1 && camera.zoom > 0.45) {
for (let i = 0; i < Math.min(tile.buildingLevel, 3); i++) {
ctx.fillStyle = '#ffeb3b';
ctx.beginPath();
ctx.arc(x - 7 * camera.zoom + i * 7 * camera.zoom, y - 12 * camera.zoom, 2 * camera.zoom, 0, Math.PI * 2);
ctx.fill();
}
}
}
// 单位指示
if (tile.unitCount > 0 && camera.zoom > 0.4) {
ctx.font = `${8 * camera.zoom}px sans-serif`;
ctx.textAlign = 'center';
ctx.fillText('👥 ' + tile.unitCount, x + 14 * camera.zoom, y + 14 * camera.zoom);
}
// 选中虚线动画
if (isSelected) {
const p = 0.5 + 0.5 * Math.sin(animTime * 4);
ctx.save();
ctx.strokeStyle = `rgba(100,200,255,${0.3 + 0.3 * p})`;
ctx.lineWidth = 2; ctx.setLineDash([4 * camera.zoom, 4 * camera.zoom]);
drawPoly(x, y, size * 0.99, null, `rgba(100,200,255,${0.3 + 0.3 * p})`, 2);
ctx.setLineDash([]);
ctx.restore();
}
});
requestAnimationFrame(render);
}
// ============ 星空背景 ============
function initStars() {
stars = [];
for (let i = 0; i < 100; i++) {
stars.push({ x: Math.random() * 2000, y: Math.random() * 2000,
r: Math.random() * 1.2 + 0.3, speed: Math.random() * 0.3 + 0.1,
alpha: Math.random() * 0.5 + 0.2 });
}
}
function drawStarfield() {
ctx.fillStyle = '#0a0e17'; ctx.fillRect(0, 0, W, H);
stars.forEach(s => {
s.y += s.speed * 0.3;
if (s.y > H + 10) { s.y = -10; s.x = Math.random() * W; }
const twinkle = s.alpha + 0.15 * Math.sin(animTime * 2 + s.x);
ctx.fillStyle = `rgba(180,200,255,${twinkle})`;
ctx.beginPath(); ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2); ctx.fill();
});
}
// ============ 进入微观世界 ============
function enterMicroWorld(tile) {
if (!tile) return;
const overlay = document.getElementById('flyInOverlay');
overlay.classList.add('active');
setTimeout(() => {
overlay.classList.remove('active');
microState = {
tile,
buildings: tile.hasBuilding ? [{ ...BUILDINGS[tile.buildingType], type: tile.buildingType, level: tile.buildingLevel, hp: 80 + tile.buildingLevel * 10 }] : [],
units: tile.units || [],
resources: calcResources(tile),
animTime: 0
};
document.getElementById('microView').classList.add('active');
document.getElementById('microTitle').textContent = `${tile.terrain === 'plain' ? '🌿' : TERRAIN[tile.terrain].name} ${TERRAIN[tile.terrain].name}村落`;
document.getElementById('microSubtitle').textContent = `坐标(${tile.q}, ${tile.r}) · ${microState.units.length}位居民`;
updateSidebar();
initMicroCanvas();
startMicroRender();
}, 500);
}
function calcResources(tile) {
const res = {};
if (tile.hasBuilding && BUILDINGS[tile.buildingType]) {
for (const [k, v] of Object.entries(BUILDINGS[tile.buildingType].yield)) res[k] = v * tile.buildingLevel;
}
return res;
}
function updateSidebar() {
const tile = microState.tile;
document.getElementById('detailTerrain').textContent = `${TERRAIN[tile.terrain].name} · 坐标(${tile.q}, ${tile.r})`;
const buildingDiv = document.getElementById('detailBuilding');
if (tile.hasBuilding) {
const b = BUILDINGS[tile.buildingType];
buildingDiv.innerHTML = `<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
<span style="font-size:22px;">${b.icon}</span>
<div><div style="font-size:14px;font-weight:bold;color:#e0e0e0;">${b.name}</div>
<div style="font-size:11px;color:#8899aa;">Lv.${tile.buildingLevel}</div></div></div>`;
} else { buildingDiv.innerHTML = '<div style="color:#667788;font-size:12px;">可建造建筑</div>'; }
const resDiv = document.getElementById('detailResources');
const res = microState.resources;
if (Object.keys(res).length > 0) {
const resNames = { food:'🍖 食物', wood:'🪵 木材', metal:'⛏ 金属', gold:'💰 金币', culture:'🏛 文化', research:'🔬 科研', defense:'🛡 防御', military:'⚔ 军事', pop:'👥 人口' };
resDiv.innerHTML = Object.entries(res).map(([k,v]) => `<div style="display:flex;justify-content:space-between;align-items:center;margin:4px 0;">
<span style="font-size:12px;color:#ccd;">${resNames[k]||k}</span>
<div class="resource-bar" style="width:100px;">
<div class="resource-fill" style="width:${Math.min(v*12,100)}%;background:linear-gradient(90deg,#4af,#a8e6cf);"></div></div>
<span style="font-size:12px;color:#8bc34a;">+${v}</span></div>`).join('');
} else { resDiv.innerHTML = '<div style="color:#667788;font-size:12px;">无产出</div>'; }
const popDiv = document.getElementById('detailPopulation');
if (microState.units.length > 0) {
popDiv.innerHTML = microState.units.map(u => `<div class="unit-card" onclick="showUnitModal('${u.id}')">
<div style="display:flex;align-items:center;gap:8px;">
<span style="font-size:18px;">${u.icon}</span>
<div><div class="unit-name">${u.name}</div><div class="unit-role">${u.status === 'working' ? '🟢' : u.status === 'resting' ? '🟡' : '🔵'} ${u.status}</div></div>
</div></div>`).join('');
} else { popDiv.innerHTML = '<div style="color:#667788;font-size:12px;">暂无居民</div>'; }
}
// ============ 微观世界 Canvas 渲染 ============
function initMicroCanvas() {
microCanvas = document.getElementById('microCanvas');
const body = document.getElementById('microBody');
microCanvas.width = body.clientWidth - 280;
microCanvas.height = body.clientHeight;
microCtx = microCanvas.getContext('2d');
}
function startMicroRender() {
if (!microState) return;
if (microAnimId) cancelAnimationFrame(microAnimId);
function loop() {
if (!microState) return;
microAnimId = requestAnimationFrame(loop);
microState.animTime += 0.016;
const mW = microCanvas.width, mH = microCanvas.height;
const tile = microState.tile;
const terrain = TERRAIN[tile.terrain];
// 背景天空
const skyGrad = microCtx.createLinearGradient(0, 0, 0, mH * 0.55);
if (tile.terrain === 'snow') { skyGrad.addColorStop(0, '#8aaaaa'); skyGrad.addColorStop(1, '#c8d8e8'); }
else if (tile.terrain === 'desert') { skyGrad.addColorStop(0, '#e8c878'); skyGrad.addColorStop(1, '#c8a85a'); }
else { skyGrad.addColorStop(0, '#4a7aaa'); skyGrad.addColorStop(1, terrain.groundColor + 'cc'); }
microCtx.fillStyle = skyGrad;
microCtx.fillRect(0, 0, mW, mH);
// 太阳/月亮
drawSkyDecor(microCtx, mW, mH, tile.terrain);
// 远处山/树林(剪影)
drawDistantScenery(microCtx, mW, mH, tile.terrain);
// 地面
const groundY = mH * 0.55;
drawGround(microCtx, mW, mH, groundY, tile);
// 场景元素(农田/建筑/街道)
drawSceneElements(microCtx, mW, mH, groundY, microState);
// 单位
microState.units.forEach((u, i) => drawMicroUnit(microCtx, mW, mH, groundY, u, i, microState.animTime));
// 粒子
drawParticles(microCtx, mW, mH);
// 提示文字
microCtx.fillStyle = 'rgba(255,255,255,0.35)';
microCtx.font = '12px sans-serif';
microCtx.textAlign = 'left';
microCtx.fillText('🖱️ 滚动缩放微观场景 · 点击单位卡片查看详情', 16, mH - 16);
}
loop();
}
// ============ 微观世界场景绘制 ============
function drawSkyDecor(c, mW, mH, terrain) {
// 太阳或月亮
if (terrain === 'snow' || terrain === 'mountain') {
// 月亮
c.fillStyle = 'rgba(200,220,255,0.8)';
c.beginPath(); c.arc(mW * 0.8, 60, 25, 0, Math.PI * 2); c.fill();
c.fillStyle = skyGrad || '#8aaaaa';
c.beginPath(); c.arc(mW * 0.8 + 8, 55, 22, 0, Math.PI * 2); c.fill();
} else {
// 太阳
const sunGlow = c.createRadialGradient(mW * 0.75, 50, 0, mW * 0.75, 50, 60);
sunGlow.addColorStop(0, 'rgba(255,230,100,0.6)');
sunGlow.addColorStop(1, 'rgba(255,230,100,0)');
c.fillStyle = sunGlow;
c.fillRect(0, 0, mW, mH * 0.55);
c.fillStyle = '#ffe664';
c.beginPath(); c.arc(mW * 0.75, 50, 18, 0, Math.PI * 2); c.fill();
}
// 云
for (let i = 0; i < 4; i++) {
const cx = (microState.animTime * 8 + i * mW * 0.3) % (mW + 200) - 100;
const cy = 30 + i * 25;
c.fillStyle = 'rgba(255,255,255,0.12)';
c.beginPath(); c.ellipse(cx, cy, 50 + i * 10, 14, 0, 0, Math.PI * 2); c.fill();
c.beginPath(); c.ellipse(cx - 20, cy + 5, 30, 10, 0, 0, Math.PI * 2); c.fill();
c.beginPath(); c.ellipse(cx + 20, cy + 3, 35, 12, 0, 0, Math.PI * 2); c.fill();
}
}
function drawDistantScenery(c, mW, mH, terrain) {
const groundY = mH * 0.55;
// 远山剪影
c.fillStyle = 'rgba(0,0,0,0.15)';
c.beginPath(); c.moveTo(0, groundY);
for (let x = 0; x <= mW; x += 40) {
const h = Math.sin(x * 0.008) * 40 + Math.sin(x * 0.003) * 60 + 20;
c.lineTo(x, groundY - h);
}
c.lineTo(mW, groundY); c.fill();
// 远景树林剪影
if (terrain === 'forest' || terrain === 'plain') {
c.fillStyle = 'rgba(0,0,0,0.1)';
for (let i = 0; i < 12; i++) {
const tx = (i / 12) * mW + Math.sin(i * 3) * 20;
const th = 30 + Math.sin(i * 2.7) * 15;
drawTreeSilhouette(c, tx, groundY - th + 10, th * 0.6, 12);
}
}
}
function drawTreeSilhouette(c, x, y, h, size) {
// 树干
c.fillStyle = 'rgba(80,50,20,0.3)';
c.fillRect(x - 2, y, 4, h * 0.4);
// 树冠
c.fillStyle = 'rgba(20,60,20,0.25)';
c.beginPath(); c.arc(x, y - h * 0.2, size, 0, Math.PI * 2); c.fill();
c.beginPath(); c.arc(x - size * 0.5, y, size * 0.8, 0, Math.PI * 2); c.fill();
c.beginPath(); c.arc(x + size * 0.4, y + 2, size * 0.7, 0, Math.PI * 2); c.fill();
}
function drawGround(c, mW, mH, groundY, tile) {
const terrain = TERRAIN[tile.terrain];
// 地面主体
c.fillStyle = terrain.groundColor;
c.fillRect(0, groundY, mW, mH - groundY);
// 地面纹理/草地
if (tile.terrain === 'plain' || tile.terrain === 'forest') {
for (let i = 0; i < 30; i++) {
const gx = (Math.sin(i * 7.3) * 0.5 + 0.5) * mW;
const gy = groundY + (Math.cos(i * 5.7) * 0.5 + 0.5) * (mH - groundY) * 0.7;
c.fillStyle = `rgba(100,180,100,${0.15 + 0.1 * Math.sin(microState.animTime * 2 + i)})`;
c.font = '10px sans-serif';
c.fillText(['🌱','🌿','🍀'][i % 3], gx, gy);
}
// 小路
c.strokeStyle = 'rgba(160,130,80,0.4)';
c.lineWidth = 18; c.lineCap = 'round';
c.beginPath(); c.moveTo(mW * 0.1, groundY + 20); c.quadraticCurveTo(mW * 0.5, groundY + 60, mW * 0.9, groundY + 30); c.stroke();
} else if (tile.terrain === 'desert') {
for (let i = 0; i < 15; i++) {
const gx = (Math.sin(i * 9.3) * 0.5 + 0.5) * mW;
const gy = groundY + (Math.cos(i * 6.7) * 0.5 + 0.5) * (mH - groundY) * 0.6;
c.fillStyle = `rgba(180,160,100,${0.08 + 0.05 * Math.sin(microState.animTime + i)})`;
c.font = '10px sans-serif'; c.fillText('·', gx, gy);
}
} else if (tile.terrain === 'snow') {
// 雪地雪花
for (let i = 0; i < 20; i++) {
const gx = Math.random() * mW;
const gy = groundY + Math.random() * (mH - groundY);
c.fillStyle = `rgba(255,255,255,${0.1 + 0.1 * Math.sin(microState.animTime + i)})`;
c.font = '8px sans-serif'; c.fillText('❄', gx, gy);
}
}
}
function drawSceneElements(c, mW, mH, groundY, state) {
const tile = state.tile;
const hasBuilding = tile.hasBuilding;
if (hasBuilding) {
// 绘制建筑
state.buildings.forEach((b, bi) => {
const bx = mW * 0.35 + bi * 80;
const by = groundY + 30;
drawBuilding(c, bx, by, b, state.animTime, mW, mH);
});
// 如果有农场,画农田
if (tile.buildingType === 'farm') {
drawFarmland(c, mW, mH, groundY);
}
} else {
// 无建筑时画几间小屋
for (let i = 0; i < 2; i++) {
drawSmallHouse(c, mW * 0.3 + i * 100, groundY + 25, state.animTime);
}
}
// 街道/路径(连接建筑)
if (hasBuilding) {
c.strokeStyle = 'rgba(180,160,120,0.3)';
c.lineWidth = 12; c.lineCap = 'round';
c.beginPath();
c.moveTo(mW * 0.15, groundY + 50);
c.lineTo(mW * 0.85, groundY + 40);
c.stroke();
// 石头纹理
for (let i = 0; i < 8; i++) {
const sx = mW * 0.15 + (mW * 0.7 / 8) * i + Math.sin(i) * 5;
const sy = groundY + 45 + Math.cos(i * 2) * 5;
c.fillStyle = 'rgba(180,170,150,0.25)';
c.beginPath(); c.arc(sx, sy, 4, 0, Math.PI * 2); c.fill();
}
}
}
function drawBuilding(c, x, y, building, time, mW, mH) {
const pulse = 0.8 + 0.2 * Math.sin(time * 3);
const w = 44, h = 50;
// 阴影
c.fillStyle = 'rgba(0,0,0,0.3)';
c.beginPath(); c.ellipse(x, y + h * 0.45, w * 0.6, 8, 0, 0, Math.PI * 2); c.fill();
// 建筑主体
c.fillStyle = building.color + 'dd';
c.strokeStyle = building.color;
c.lineWidth = 2;
// 自定义圆角矩形(兼容非Chrome浏览器)
const r = 4;
c.beginPath();
c.moveTo(x - w/2 + r, y - h/2);
c.lineTo(x + w/2 - r, y - h/2);
c.quadraticCurveTo(x + w/2, y - h/2, x + w/2, y - h/2 + r);
c.lineTo(x + w/2, y + h/2 - r);
c.quadraticCurveTo(x + w/2, y + h/2, x + w/2 - r, y + h/2);
c.lineTo(x - w/2 + r, y + h/2);
c.quadraticCurveTo(x - w/2, y + h/2, x - w/2, y + h/2 - r);
c.lineTo(x - w/2, y - h/2 + r);
c.quadraticCurveTo(x - w/2, y - h/2, x - w/2 + r, y - h/2);
c.closePath();
c.fill(); c.stroke();
// 屋顶
c.fillStyle = shadeColor(building.color, -30);
c.beginPath();
c.moveTo(x - w/2 - 6, y - h/2);
c.lineTo(x, y - h/2 - 16);
c.lineTo(x + w/2 + 6, y - h/2);
c.closePath(); c.fill();
// 门
c.fillStyle = shadeColor(building.color, -40);
c.fillRect(x - 5, y + h/2 - 18, 10, 18);
// 门把手
c.fillStyle = '#ffeb3b';
c.beginPath(); c.arc(x + 3, y + h/2 - 9, 1.5, 0, Math.PI * 2); c.fill();
// 窗户(亮灯动画)
const windowLit = Math.sin(time * 2) > -0.3;
c.fillStyle = windowLit ? 'rgba(255,230,100,0.7)' : 'rgba(80,80,80,0.5)';
c.fillRect(x - 12, y - h/2 + 8, 8, 7);
c.fillRect(x + 4, y - h/2 + 8, 8, 7);
// 建筑图标
c.font = '22px sans-serif';
c.textAlign = 'center'; c.textBaseline = 'middle';
c.fillText(building.icon, x, y - 2);
// 名称
c.fillStyle = '#e0e0e0'; c.font = '10px sans-serif';
c.textAlign = 'center';
c.fillText(building.name, x, y + h/2 + 14);
// 工作动画
if (building.type === 'farm' || building.type === 'mine') {
const workAnim = Math.sin(time * 4) > 0;
if (workAnim) {
c.font = '12px sans-serif';
c.fillText(building.type === 'farm' ? '💪' : '⚒', x + 22, y - 8 + Math.sin(time * 5) * 4);
}
}
}
function drawSmallHouse(c, x, y, time) {
// 小屋
c.fillStyle = '#8b7355cc';
const r = 3;
c.beginPath();
c.moveTo(x - 16 + r, y - 14);
c.lineTo(x + 16 - r, y - 14);
c.quadraticCurveTo(x + 16, y - 14, x + 16, y - 14 + r);
c.lineTo(x + 16, y + 14 - r);
c.quadraticCurveTo(x + 16, y + 14, x + 16 - r, y + 14);
c.lineTo(x - 16 + r, y + 14);
c.quadraticCurveTo(x - 16, y + 14, x - 16, y + 14 - r);
c.lineTo(x - 16, y - 14 + r);
c.quadraticCurveTo(x - 16, y - 14, x - 16 + r, y - 14);
c.closePath(); c.fill();
// 屋顶
c.fillStyle = '#a0522d';
c.beginPath(); c.moveTo(x - 20, y - 14); c.lineTo(x, y - 28); c.lineTo(x + 20, y - 14); c.closePath(); c.fill();
// 门
c.fillStyle = '#5a3a1a'; c.fillRect(x - 4, y + 2, 8, 12);
// 窗
const lit = Math.sin(time * 1.5) > 0;
c.fillStyle = lit ? 'rgba(255,230,100,0.6)' : 'rgba(80,80,80,0.4)';
c.fillRect(x - 12, y - 8, 6, 5);
}
function drawFarmland(c, mW, mH, groundY) {
// 农田行
const farmY = groundY + 70;
for (let row = 0; row < 3; row++) {
for (let col = 0; col < 5; col++) {
const fx = mW * 0.55 + col * 24;
const fy = farmY + row * 22;
// 田垄
c.fillStyle = 'rgba(120,180,80,0.3)';
c.fillRect(fx - 8, fy - 3, 16, 6);
// 作物
c.fillStyle = 'rgba(80,140,40,0.5)';
c.fillRect(fx - 1, fy - 7, 2, 7);
// 生长动画
const grow = 0.8 + 0.2 * Math.sin(microState.animTime * 2 + row + col);
c.fillStyle = `rgba(100,180,60,${0.3 * grow})`;
c.fillRect(fx - 1, fy - 9, 2, 9 * grow);
}
}
}
function drawMicroUnit(c, mW, mH, groundY, unit, index, time) {
// 围绕建筑散布
const angle = (index / Math.max(microState.units.length, 1)) * Math.PI * 2 + time * 0.3;
const radius = 60 + index * 18;
const ux = mW * 0.65 + Math.cos(angle) * radius * 0.5;
const uy = groundY + 40 + Math.sin(angle) * radius * 0.3;
const bob = Math.sin(time * 2 + index * 1.5) * 3;
// 阴影
c.fillStyle = 'rgba(0,0,0,0.2)';
c.beginPath(); c.ellipse(ux, uy + 22, 10, 4, 0, 0, Math.PI * 2); c.fill();
// 身体(小人)
const bodyColor = unit.status === 'working' ? '#8bc34a' : unit.status === 'resting' ? '#ff9800' : '#4af';
c.fillStyle = bodyColor + '60';
c.beginPath(); c.arc(ux, uy + bob, 14, 0, Math.PI * 2); c.fill();
c.strokeStyle = bodyColor; c.lineWidth = 1.5;
c.stroke();
// 图标
c.font = '20px sans-serif';
c.textAlign = 'center'; c.textBaseline = 'middle';
c.fillText(unit.icon, ux, uy + bob);
// 名字
c.fillStyle = '#ccd'; c.font = '9px sans-serif';
c.textAlign = 'center';
c.fillText(unit.name, ux, uy + bob + 24);
// 状态灯
const dotColor = unit.status === 'working' ? '#8bc34a' : unit.status === 'resting' ? '#ff9800' : '#4af';
c.fillStyle = dotColor;
c.beginPath(); c.arc(ux + 12, uy + bob - 12, 3, 0, Math.PI * 2); c.fill();
}
function drawParticles(c, mW, mH) {
// 初始化粒子
if (!microState._particles) {
microState._particles = [];
for (let i = 0; i < 12; i++) {
microState._particles.push({
x: Math.random() * mW, y: mH + Math.random() * 50,
vx: (Math.random() - 0.5) * 0.4, vy: -(Math.random() * 0.6 + 0.2),
life: Math.random() * 120 + 60, maxLife: Math.random() * 120 + 60,
text: ['✨','🌟','💫','+1'][Math.floor(Math.random()*4)],
size: 10 + Math.random() * 6
});
}
}
microState._particles.forEach(p => {
p.x += p.vx; p.y += p.vy; p.life--;
if (p.life <= 0 || p.y < -20) {
p.x = Math.random() * mW; p.y = mH + 10; p.life = p.maxLife;
}
const alpha = p.life / p.maxLife * 0.5;
c.globalAlpha = alpha;
c.font = `${p.size}px sans-serif`; c.textAlign = 'center';
c.fillText(p.text, p.x, p.y);
c.globalAlpha = 1;
});
}
// ============ 单位详情弹窗 ============
function showUnitModal(unitId) {
if (!microState) return;
const unit = microState.units.find(u => u.id === unitId);
if (!unit) return;
const modal = document.getElementById('unitModal');
document.getElementById('modalTitle').textContent = `${unit.icon} ${unit.name}`;
const hpColor = unit.hp > 70 ? '#8bc34a' : unit.hp > 40 ? '#ff9800' : '#f44336';
const enColor = unit.energy > 70 ? '#4af' : unit.energy > 40 ? '#ff9800' : '#f44336';
document.getElementById('modalBody').innerHTML = `
<div class="statRow"><span class="statLabel">状态</span><span class="statValue" style="color:${unit.status==='working'?'#8bc34a':unit.status==='resting'?'#ff9800':'#4af'}">${unit.status==='working'?'🟢 工作中':unit.status==='resting'?'🟡 休息中':'🔵 移动中'}</span></div>
<div class="statRow"><span class="statLabel">生命值</span><div class="statBar"><div class="statFill" style="width:${unit.hp}%;background:${hpColor};"></div></div><span class="statValue">${unit.hp}</span></div>
<div class="statRow"><span class="statLabel">能量</span><div class="statBar"><div class="statFill" style="width:${unit.energy}%;background:${enColor};"></div></div><span class="statValue">${unit.energy}</span></div>
<div class="statRow"><span class="statLabel">等级</span><span class="statValue">Lv.${unit.level} ${'★'.repeat(unit.level)}${'☆'.repeat(3-unit.level)}</span></div>
<div class="statRow"><span class="statLabel">心情</span><span class="statValue">${unit.mood}</span></div>
<div style="margin-top:16px;"><div class="panel-label">操作指令</div>
<div class="cmdBtn" onclick="alert('已派遣工作')">📋 派遣工作</div>
<div class="cmdBtn" onclick="alert('已令其休息')">😴 令其休息</div>
<div class="cmdBtn" onclick="alert('已下达移动指令')">🚶 移动到指定位置</div>
<div class="cmdBtn" onclick="alert('训练中...')">⬆️ 训练升级</div>
</div>
`;
modal.classList.add('active');
}
function closeUnitModal() { document.getElementById('unitModal').classList.remove('active'); }
// ============ 工具函数 ============
function shadeColor(color, percent) {
const num = parseInt(color.replace('#', ''), 16);
const r = Math.min(255, Math.max(0, (num >> 16) + percent));
const g = Math.min(255, Math.max(0, ((num >> 8) & 0x00FF) + percent));
const b = Math.min(255, Math.max(0, (num & 0x0000FF) + percent));
return `rgb(${r},${g},${b})`;
}
// ============ 事件(修复版)============
canvas.addEventListener('wheel', e => {
e.preventDefault();
const zoomDelta = e.deltaY > 0 ? 0.9 : 1.1;
camera.zoom = Math.max(0.4, Math.min(4, camera.zoom * zoomDelta));
// 更新光标状态
canvas.classList.add('canDrag');
}, { passive: false });
canvas.addEventListener('mousedown', e => {
if (e.button !== 0) return;
isMouseDown = true;
hasDragged = false;
mouseDownX = e.clientX;
mouseDownY = e.clientY;
dragCamStartX = camera.x;
dragCamStartY = camera.y;
canvas.classList.add('dragging');
});
canvas.addEventListener('mousemove', e => {
const rect = canvas.getBoundingClientRect();
const mx = e.clientX - rect.left;
const my = e.clientY - rect.top;
const hex = pixelToHex(mx, my);
document.getElementById('coords').textContent = `坐标: (${hex.q}, ${hex.r})`;
if (isMouseDown) {
const dx = e.clientX - mouseDownX;
const dy = e.clientY - mouseDownY;
if (Math.abs(dx) > 8 || Math.abs(dy) > 8) {
hasDragged = true;
}
if (hasDragged) {
camera.x = dragCamStartX + (e.clientX - mouseDownX);
camera.y = dragCamStartY + (e.clientY - mouseDownY);
}
} else {
// 只有没按下鼠标时才检测悬停
hoveredTile = getTile(hex.q, hex.r);
}
});
canvas.addEventListener('mouseup', e => {
if (!isMouseDown) return;
isMouseDown = false;
canvas.classList.remove('dragging');
if (!hasDragged) {
// 这是点击,不是拖拽
const rect = canvas.getBoundingClientRect();
const mx = e.clientX - rect.left;
const my = e.clientY - rect.top;
const hex = pixelToHex(mx, my);
const tile = getTile(hex.q, hex.r);
if (tile) {
selectedTile = tile;
enterMicroWorld(tile);
}
}
});
canvas.addEventListener('mouseleave', () => {
isMouseDown = false;
hasDragged = false;
canvas.classList.remove('dragging');
hoveredTile = null;
});
// ESC退出微观世界
document.addEventListener('keydown', e => { if (e.key === 'Escape') exitMicroWorld(); });
document.getElementById('backBtn').addEventListener('click', exitMicroWorld);
document.getElementById('unitModal').addEventListener('click', e => { if (e.target === document.getElementById('unitModal')) closeUnitModal(); });
function exitMicroWorld() {
document.getElementById('microView').classList.remove('active');
if (microAnimId) { cancelAnimationFrame(microAnimId); microAnimId = null; }
microState = null;
}
// ============ 初始化 ============
function init() {
W = canvas.width = window.innerWidth;
H = canvas.height = window.innerHeight;
canvas.classList.add('canDrag');
initStars();
// 加载动画
const loadFill = document.getElementById('loadFill');
const loadText = document.getElementById('loadText');
let progress = 0;
const loadInterval = setInterval(() => {
progress += Math.random() * 18 + 7;
if (progress >= 100) {
progress = 100; clearInterval(loadInterval);
setTimeout(() => {
document.getElementById('loadingScreen').style.display = 'none';
requestAnimationFrame(render);
}, 300);
}
loadFill.style.width = progress + '%';
const msgs = ['正在生成世界...','种植森林...','建造村落...','召唤居民...','世界已就绪!'];
loadText.textContent = msgs[Math.min(Math.floor(progress / 25), msgs.length - 1)];
}, 120);
}
window.addEventListener('resize', () => {
W = canvas.width = window.innerWidth;
H = canvas.height = window.innerHeight;
});
generateMap();
init();
</script>
<script src="auth.js"></script>
<script>YuSenAuth.init();</script>
</body>
</html>