/* ══════════════════════════
별빛 배경 스크립트
══════════════════════════ */
(function() {
const canvas = document.getElementById('starCanvas');
if(!canvas) return;
const ctx = canvas.getContext('2d');
let W, H;
function resize() {
W = canvas.width = window.innerWidth;
H = canvas.height = window.innerHeight;
}
resize();
window.addEventListener('resize', resize);
// 별 생성 — 크기 다양하게
const STARS = Array.from({length: 280}, () => {
const size = Math.random();
return {
x: Math.random() * 2000,
y: Math.random() * 2000,
// 작은 별(70%), 중간 별(20%), 큰 별(10%)
r: size < 0.7 ? Math.random() * 0.8 + 0.3
: size < 0.9 ? Math.random() * 1.2 + 1.0
: Math.random() * 2.0 + 2.0,
// 밝기 & 반짝임 속도
brightness: Math.random() * 0.5 + 0.3,
phase: Math.random() * Math.PI * 2,
speed: Math.random() * 0.008 + 0.002,
// 별 색상 — 대부분 흰색, 간혹 색깔 별
color: Math.random() < 0.08 ? '#f0c040' // 황금별
: Math.random() < 0.06 ? '#c084fc' // 보라별
: Math.random() < 0.04 ? '#38bdf8' // 파란별
: '#ffffff',
// 큰 별은 십자 모양
isBig: size >= 0.9,
// 유성 여부
isShooting: false,
shootX: 0, shootY: 0, shootLen: 0, shootOpacity: 0,
};
});
// 유성 풀
const METEORS = Array.from({length: 3}, () => ({
active: false, x: 0, y: 0, vx: 0, vy: 0,
len: 0, opacity: 0, timer: Math.random() * 300,
}));
function drawStar(star, alpha) {
ctx.save();
ctx.globalAlpha = alpha;
if(star.isBig) {
// 큰 별 — 십자 광채
const grd = ctx.createRadialGradient(star.x % W, star.y % H, 0, star.x % W, star.y % H, star.r * 3);
grd.addColorStop(0, star.color);
grd.addColorStop(1, 'transparent');
ctx.fillStyle = grd;
ctx.beginPath();
ctx.arc(star.x % W, star.y % H, star.r * 3, 0, Math.PI * 2);
ctx.fill();
// 십자
ctx.strokeStyle = star.color;
ctx.lineWidth = 0.5;
ctx.globalAlpha = alpha * 0.6;
const cx = star.x % W, cy = star.y % H;
const len = star.r * 6;
ctx.beginPath();
ctx.moveTo(cx - len, cy); ctx.lineTo(cx + len, cy);
ctx.moveTo(cx, cy - len); ctx.lineTo(cx, cy + len);
ctx.stroke();
} else {
// 작은/중간 별
if(star.r > 1.0) {
// 약한 글로우
const grd = ctx.createRadialGradient(star.x % W, star.y % H, 0, star.x % W, star.y % H, star.r * 2.5);
grd.addColorStop(0, star.color);
grd.addColorStop(1, 'transparent');
ctx.fillStyle = grd;
ctx.beginPath();
ctx.arc(star.x % W, star.y % H, star.r * 2.5, 0, Math.PI * 2);
ctx.fill();
ctx.globalAlpha = alpha;
}
ctx.fillStyle = star.color;
ctx.beginPath();
ctx.arc(star.x % W, star.y % H, star.r, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
}
function drawMeteor(m) {
if(!m.active) return;
ctx.save();
const grad = ctx.createLinearGradient(m.x, m.y, m.x - m.vx * m.len, m.y - m.vy * m.len);
grad.addColorStop(0, `rgba(255,255,255,${m.opacity})`);
grad.addColorStop(1, 'transparent');
ctx.strokeStyle = grad;
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.moveTo(m.x, m.y);
ctx.lineTo(m.x - m.vx * m.len, m.y - m.vy * m.len);
ctx.stroke();
ctx.restore();
}
let frame = 0;
function loop() {
frame++;
ctx.clearRect(0, 0, W, H);
// 별 그리기
STARS.forEach(star => {
star.phase += star.speed;
const flicker = star.brightness * (0.5 + 0.5 * Math.sin(star.phase));
drawStar(star, flicker);
});
// 유성
METEORS.forEach(m => {
if(!m.active) {
m.timer--;
if(m.timer <= 0) {
m.active = true;
m.x = Math.random() * W * 1.2;
m.y = Math.random() * H * 0.4;
const angle = Math.PI / 4 + (Math.random() - 0.5) * 0.3;
const speed = 8 + Math.random() * 6;
m.vx = Math.cos(angle) * speed;
m.vy = Math.sin(angle) * speed;
m.len = 12 + Math.random() * 10;
m.opacity = 0.9;
m.timer = 0;
}
} else {
m.x += m.vx; m.y += m.vy;
m.opacity -= 0.015;
drawMeteor(m);
if(m.opacity <= 0 || m.x > W + 100 || m.y > H + 100) {
m.active = false;
m.timer = 200 + Math.random() * 400;
}
}
});
requestAnimationFrame(loop);
}
loop();
})();