<!DOCTYPE html> <html lang="ko"> <head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Vibrant Math Tree</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;700&display=swap');
body {
margin: 0;
overflow: hidden;
background-color: #020205;
font-family: 'Fira Code', monospace;
cursor: move;
}
canvas {
display: block;
}
</style>
</head> <body>
</html>
<canvas id="canvas"></canvas> <script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const _0x5a1 = [70, 76, 65, 71, 123, 84, 119, 105, 110, 107, 108, 105, 110, 103, 95, 76, 105, 103, 104, 116, 115, 95, 65, 110, 100, 95, 66, 114, 105, 103, 104, 116, 95, 79, 114, 110, 97, 109, 101, 110, 116, 115, 125]; const hiddenPathChars = _0x5a1.map(c => String.fromCharCode(c));
let width, height; let particles = []; let snowParticles = []; let angleY = 0; let targetAngleY = 0; let isDragging = false; let lastMouseX = 0; let cameraZ = 550;
let mathDecorations = [
"x", "y", "z", "i", "e", "π", "ω", "t", "s", "f", "g",
"sin", "cos", "tan", "log", "ln", "lim", "det", "max", "min",
"f(t)", "F(ω)", "L(s)", "dy/dx", "x²+y²", "e^x", "sin(x)",
"ℱ{f}", "ℒ{f}", "∫e⁻ˢᵗ", "∫e⁻ⁱωᵗ",
"E=mc²", "F=ma", "V=IR", "A=πr²", "∇∙F", "∇×F",
"e^(iπ)+1=0", "sin²θ+cos²θ=1", "d/dx(e^x)=e^x",
"x=(-b±√D)/2a", "PV=nRT", "S=klogW"
];
mathDecorations.sort((a, b) => a.length - b.length);
const treeGreens = ["#00FF00", "#32CD32", "#00FA9A", "#98FB98", "#7CFC00"]; const ornamentColors = ["#FF0000", "#FFD700", "#FF00FF", "#00FFFF"]; const lightColors = ["#FF5555", "#55FF55", "#5555FF", "#FFFF55", "#FF55FF", "#55FFFF"]; const snowSymbols = ["+", "-", "×", "÷", "=", "≠"];
class Particle {
constructor(x, y, z, text, color, type = 'leaf') {
this.x = x;
this.y = y;
this.z = z;
this.text = text;
this.color = color;
this.type = type;
if (this.type === 'star') this.baseSize = 60;
else if (this.type === 'light') this.baseSize = 24;
else if (this.type === 'ornament') this.baseSize = 20;
else if (this.type === 'trunk') this.baseSize = 14;
else this.baseSize = 14;
this.projX = 0;
this.projY = 0;
this.projScale = 0;
this.visible = false;
this.depth = 0;
this.alpha = Math.random() * 0.5 + 0.5;
this.blinkPhase = Math.random() * Math.PI * 2;
if (this.type === 'light') {
this.lightColor = lightColors[Math.floor(Math.random() * lightColors.length)];
}
}
project(cx, cy, angle) {
const cos = Math.cos(angle);
const sin = Math.sin(angle);
const rx = this.x * cos - this.z * sin;
const rz = this.x * sin + this.z * cos;
const focalLength = 450;
const scale = focalLength / (focalLength + rz + cameraZ);
this.projScale = scale;
this.projX = rx * scale + cx;
this.projY = this.y * scale + cy;
this.visible = (scale > 0 && rz > -cameraZ);
this.depth = rz;
}
draw(ctx) {
if (!this.visible) return;
let alpha = this.alpha;
if (this.type === 'trunk') alpha = Math.max(alpha, 0.8);
let drawAlpha = Math.max(0.1, Math.min(1, alpha * this.projScale));
if(this.type === 'star' || this.type === 'light' || this.type === 'ornament') {
drawAlpha = 1;
}
ctx.globalAlpha = drawAlpha;
const fontSize = this.baseSize * this.projScale;
ctx.font = `bold ${fontSize}px 'Fira Code', monospace`;
if (this.type === 'star') {
ctx.shadowBlur = 40 + Math.abs(Math.sin(Date.now() * 0.002)) * 20;
ctx.shadowColor = "#FFD700";
ctx.fillStyle = "#FFFF00";
}
else if (this.type === 'light') {
const time = Date.now() * 0.005 + this.blinkPhase;
let twinkle = Math.pow(Math.sin(time) * 0.5 + 0.5, 4);
ctx.globalAlpha = 0.4 + twinkle * 0.6;
ctx.shadowBlur = 10 + twinkle * 25;
ctx.shadowColor = this.lightColor;
ctx.fillStyle = "#FFFFFF";
}
else if (this.type === 'ornament') {
ctx.shadowBlur = 15;
ctx.shadowColor = this.color;
ctx.fillStyle = this.color;
}
else if (this.projScale > 0.6) {
ctx.shadowBlur = 5;
ctx.shadowColor = this.color;
ctx.fillStyle = this.color;
} else {
ctx.shadowBlur = 0;
ctx.fillStyle = this.color;
}
ctx.fillText(this.text, this.projX, this.projY);
ctx.globalAlpha = 1;
ctx.shadowBlur = 0;
}
}
class SnowParticle {
constructor() {
this.reset();
this.y = Math.random() * height - height / 2;
}
reset() {
this.x = (Math.random() - 0.5) * width * 1.5;
this.y = -height / 2 - Math.random() * 200;
this.z = (Math.random() - 0.5) * 1000;
this.text = snowSymbols[Math.floor(Math.random() * snowSymbols.length)];
this.color = "#FFFFFF";
this.speed = Math.random() * 2 + 1;
this.size = Math.random() * 10 + 8;
this.drift = Math.random() * 0.5 - 0.25;
}
update() {
this.y += this.speed;
this.x += this.drift;
if (this.y > height / 2 + 100) {
this.reset();
}
}
project(cx, cy, angle) {
const cos = Math.cos(angle * 0.1);
const sin = Math.sin(angle * 0.1);
const rx = this.x * cos - this.z * sin;
const rz = this.x * sin + this.z * cos;
const focalLength = 450;
const scale = focalLength / (focalLength + rz + cameraZ);
this.projScale = scale;
this.projX = rx * scale + cx;
this.projY = this.y * scale + cy;
this.visible = (scale > 0 && rz > -cameraZ);
}
draw(ctx) {
if (!this.visible) return;
ctx.globalAlpha = Math.min(1, this.projScale * 0.7);
ctx.font = `${this.size * this.projScale}px 'Fira Code', monospace`;
ctx.fillStyle = this.color;
if (this.projScale > 0.7) {
ctx.shadowBlur = 5;
ctx.shadowColor = "#FFFFFF";
}
ctx.fillText(this.text, this.projX, this.projY);
ctx.globalAlpha = 1;
ctx.shadowBlur = 0;
}
}
function createTree() {
particles = [];
const treeHeight = 650;
const maxRadius = 280;
const leafCount = 600;
const phi = Math.PI * (3 - Math.sqrt(5));
for (let i = 0; i < leafCount; i++) {
const y = - (treeHeight / 2) + (i / leafCount) * treeHeight - 80;
const radius = (i / leafCount) * maxRadius;
const theta = phi * i;
const x = Math.cos(theta) * radius;
const z = Math.sin(theta) * radius;
const progress = i / leafCount;
const centerIndex = progress * (mathDecorations.length - 1);
const noise = (Math.random() - 0.5) * 8;
let index = Math.floor(centerIndex + noise);
index = Math.max(0, Math.min(mathDecorations.length - 1, index));
const text = mathDecorations[index];
let color, type;
if (Math.random() < 0.15) {
color = ornamentColors[Math.floor(Math.random() * ornamentColors.length)];
type = 'ornament';
} else {
color = treeGreens[Math.floor(Math.random() * treeGreens.length)];
type = 'leaf';
}
particles.push(new Particle(x, y, z, text, color, type));
}
const flagLen = hiddenPathChars.length;
for (let i = 0; i < flagLen; i++) {
const y = - (treeHeight / 2) + (i / flagLen) * (treeHeight * 0.8) - 60;
const radius = ((i + 2) / (flagLen + 5)) * maxRadius * 0.95;
const theta = (i * 1.5) + 0;
const x = Math.cos(theta) * radius;
const z = Math.sin(theta) * radius;
particles.push(new Particle(x, y, z, hiddenPathChars[i], "#FFFFFF", 'light'));
}
const trunkH = 160;
const trunkW = 70;
const trunkCount = 120;
const trunkStartY = (treeHeight / 2) - 60;
for(let i=0; i<trunkCount; i++) {
const h = Math.random() * trunkH;
const angle = Math.random() * Math.PI * 2;
const r = trunkW;
const x = Math.cos(angle) * r;
const z = Math.sin(angle) * r;
const y = trunkStartY + h;
const text = Math.random() > 0.5 ? "1" : "0";
particles.push(new Particle(x, y, z, text, "#D2691E", 'trunk'));
}
particles.push(new Particle(0, -treeHeight/2 - 120, 0, "★", "#FFFF00", 'star'));
}
function createSnow() {
snowParticles = [];
const snowCount = 200;
for (let i = 0; i < snowCount; i++) {
snowParticles.push(new SnowParticle());
}
}
function resize() {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
}
function animate() {
ctx.fillStyle = "#020205";
ctx.fillRect(0, 0, width, height);
if (!isDragging) targetAngleY += 0.002;
angleY += (targetAngleY - angleY) * 0.1;
snowParticles.forEach(p => {
p.update();
p.project(width / 2, height / 2, angleY);
p.draw(ctx);
});
particles.forEach(p => p.project(width / 2, height / 2, angleY));
particles.sort((a, b) => b.depth - a.depth);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
particles.forEach(p => p.draw(ctx));
requestAnimationFrame(animate);
}
window.addEventListener('resize', resize);
window.addEventListener('wheel', (e) => {
cameraZ += e.deltaY * 0.5;
cameraZ = Math.max(200, Math.min(1000, cameraZ));
});
const onStart = (x) => { isDragging = true; lastMouseX = x; };
const onMove = (x) => {
if (!isDragging) return;
const delta = (x - lastMouseX) * 0.005;
targetAngleY += delta;
angleY += delta;
lastMouseX = x;
};
const onEnd = () => { isDragging = false; };
canvas.addEventListener('mousedown', e => onStart(e.clientX));
window.addEventListener('mousemove', e => onMove(e.clientX));
window.addEventListener('mouseup', onEnd);
canvas.addEventListener('touchstart', e => onStart(e.touches[0].clientX));
window.addEventListener('touchmove', e => onMove(e.touches[0].clientX));
window.addEventListener('touchend', onEnd);
resize(); createTree(); createSnow(); animate();
</script>