<!-- ✅ XE 소스 편집기에 그대로 붙여넣기 -->
<!-- 🌸 봄(3~5월) 벚꽃 🌧️ 여름(6~8월) 비 🍂 가을(9~11월) 낙엽 ❄️ 겨울(12~2월) 눈 -->
<style>
#seasonBtn {
position:fixed; bottom:2rem; right:2rem; z-index:9000;
width:54px; height:54px; border-radius:50%;
border:none; cursor:pointer; font-size:1.5rem;
display:flex; align-items:center; justify-content:center;
transition:transform .2s cubic-bezier(.34,1.56,.64,1), box-shadow .2s;
}
#seasonBtn:hover { transform:scale(1.15) rotate(-10deg); }
#seasonCanvas {
position:fixed; inset:0; width:100vw; height:100vh;
pointer-events:none; z-index:8999;
}
</style>
<canvas id="seasonCanvas"></canvas>
<button id="seasonBtn" title="계절 효과">
<svg id="seasonIcon" width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg"></svg>
</button>
<script>
(function(){
var canvas = document.getElementById('seasonCanvas');
var btn = document.getElementById('seasonBtn');
var ctx = canvas.getContext('2d');
var W, H;
function resize(){ W = canvas.width = window.innerWidth; H = canvas.height = window.innerHeight; }
resize(); window.addEventListener('resize', resize);
/* ── 계절 감지 ── */
var month = new Date().getMonth() + 1; // 1~12
var season = month >= 3 && month <= 5 ? 'spring'
: month >= 6 && month <= 8 ? 'summer'
: month >= 9 && month <= 11 ? 'autumn'
: 'winter';
/* 버튼 스타일 세팅 */
var SVG_ICONS = {
spring: '<circle cx="14" cy="14" r="4" fill="#fff"/>'
+ '<ellipse cx="14" cy="6" rx="3" ry="5" fill="#fda4c0"/>'
+ '<ellipse cx="14" cy="22" rx="3" ry="5" fill="#fda4c0"/>'
+ '<ellipse cx="6" cy="14" rx="5" ry="3" fill="#fda4c0"/>'
+ '<ellipse cx="22" cy="14" rx="5" ry="3" fill="#fda4c0"/>'
+ '<ellipse cx="8" cy="8" rx="3" ry="5" transform="rotate(-45 8 8)" fill="#f9c8d8"/>'
+ '<ellipse cx="20" cy="8" rx="3" ry="5" transform="rotate(45 20 8)" fill="#f9c8d8"/>'
+ '<ellipse cx="8" cy="20" rx="3" ry="5" transform="rotate(45 8 20)" fill="#f9c8d8"/>'
+ '<ellipse cx="20" cy="20" rx="3" ry="5" transform="rotate(-45 20 20)" fill="#f9c8d8"/>'
+ '<circle cx="14" cy="14" r="3.5" fill="#fff9c4"/>'
+ '<circle cx="14" cy="14" r="1.5" fill="#fbbf24"/>',
summer: '<line x1="4" y1="2" x2="6" y2="10" stroke="#bae6fd" stroke-width="2" stroke-linecap="round"/>'
+ '<line x1="10" y1="2" x2="12" y2="10" stroke="#93c5fd" stroke-width="2" stroke-linecap="round"/>'
+ '<line x1="16" y1="2" x2="18" y2="10" stroke="#bae6fd" stroke-width="2" stroke-linecap="round"/>'
+ '<line x1="22" y1="2" x2="24" y2="10" stroke="#93c5fd" stroke-width="2" stroke-linecap="round"/>'
+ '<line x1="2" y1="12" x2="4" y2="20" stroke="#93c5fd" stroke-width="2" stroke-linecap="round"/>'
+ '<line x1="8" y1="12" x2="10" y2="20" stroke="#bae6fd" stroke-width="2" stroke-linecap="round"/>'
+ '<line x1="14" y1="12" x2="16" y2="20" stroke="#93c5fd" stroke-width="2" stroke-linecap="round"/>'
+ '<line x1="20" y1="12" x2="22" y2="20" stroke="#bae6fd" stroke-width="2" stroke-linecap="round"/>'
+ '<line x1="6" y1="22" x2="8" y2="28" stroke="#93c5fd" stroke-width="2" stroke-linecap="round"/>'
+ '<line x1="14" y1="22" x2="16" y2="28" stroke="#bae6fd" stroke-width="2" stroke-linecap="round"/>',
autumn: '<path d="M14 24 C14 24 4 18 5 10 C6 4 12 3 14 8 C16 3 22 4 23 10 C24 18 14 24 14 24Z" fill="#f97316"/>'
+ '<path d="M14 24 C14 24 6 17 7 11 C8 6 12 5 14 9" fill="#fbbf24" opacity="0.7"/>'
+ '<line x1="14" y1="24" x2="14" y2="10" stroke="#92400e" stroke-width="1.2" stroke-linecap="round"/>'
+ '<line x1="14" y1="18" x2="9" y2="14" stroke="#92400e" stroke-width="0.9" stroke-linecap="round"/>'
+ '<line x1="14" y1="14" x2="19" y2="11" stroke="#92400e" stroke-width="0.9" stroke-linecap="round"/>'
+ '<line x1="14" y1="21" x2="18" y2="17" stroke="#92400e" stroke-width="0.9" stroke-linecap="round"/>'
+ '<path d="M14 8 Q16 4 18 3" stroke="#92400e" stroke-width="1.2" fill="none" stroke-linecap="round"/>',
winter: '<line x1="14" y1="3" x2="14" y2="25" stroke="white" stroke-width="2.2" stroke-linecap="round"/>'
+ '<line x1="3" y1="14" x2="25" y2="14" stroke="white" stroke-width="2.2" stroke-linecap="round"/>'
+ '<line x1="6" y1="6" x2="22" y2="22" stroke="white" stroke-width="2.2" stroke-linecap="round"/>'
+ '<line x1="22" y1="6" x2="6" y2="22" stroke="white" stroke-width="2.2" stroke-linecap="round"/>'
+ '<line x1="14" y1="7" x2="11" y2="10" stroke="white" stroke-width="1.5" stroke-linecap="round"/>'
+ '<line x1="14" y1="7" x2="17" y2="10" stroke="white" stroke-width="1.5" stroke-linecap="round"/>'
+ '<line x1="14" y1="21" x2="11" y2="18" stroke="white" stroke-width="1.5" stroke-linecap="round"/>'
+ '<line x1="14" y1="21" x2="17" y2="18" stroke="white" stroke-width="1.5" stroke-linecap="round"/>'
+ '<line x1="7" y1="14" x2="10" y2="11" stroke="white" stroke-width="1.5" stroke-linecap="round"/>'
+ '<line x1="7" y1="14" x2="10" y2="17" stroke="white" stroke-width="1.5" stroke-linecap="round"/>'
+ '<line x1="21" y1="14" x2="18" y2="11" stroke="white" stroke-width="1.5" stroke-linecap="round"/>'
+ '<line x1="21" y1="14" x2="18" y2="17" stroke="white" stroke-width="1.5" stroke-linecap="round"/>'
+ '<circle cx="14" cy="14" r="2.5" fill="white"/>',
};
var btnStyles = {
spring: { bg:'linear-gradient(135deg,#fb7185,#f9a8d4)', shadow:'rgba(251,113,133,.55)' },
summer: { bg:'linear-gradient(135deg,#38bdf8,#7dd3fc)', shadow:'rgba(56,189,248,.55)' },
autumn: { bg:'linear-gradient(135deg,#f97316,#fbbf24)', shadow:'rgba(249,115,22,.55)' },
winter: { bg:'linear-gradient(135deg,#bae6fd,#e0f2fe)', shadow:'rgba(186,230,253,.7)' },
};
var bs = btnStyles[season];
document.getElementById('seasonIcon').innerHTML = SVG_ICONS[season];
btn.style.background = bs.bg;
btn.style.boxShadow = '0 4px 20px ' + bs.shadow;
/* ════════════════════════════════
봄 - 벚꽃
════════════════════════════════ */
function makePetal(size, r, g, b){
var oc = document.createElement('canvas');
oc.width = oc.height = size*3;
var c = oc.getContext('2d'), cx=size*1.5, cy=size*1.5;
c.save(); c.translate(cx,cy);
c.beginPath();
c.moveTo(0,size*.9);
c.bezierCurveTo(-size*.55,size*.4,-size*.62,-size*.5,0,-size*.9);
c.bezierCurveTo(size*.62,-size*.5,size*.55,size*.4,0,size*.9);
var grd=c.createRadialGradient(-size*.1,-size*.2,0,0,0,size);
grd.addColorStop(0,'rgba(255,235,240,.98)');
grd.addColorStop(.35,'rgba('+r+','+g+','+b+',.90)');
grd.addColorStop(.7,'rgba('+(r-15)+','+(g-25)+','+(b-5)+',.75)');
grd.addColorStop(1,'rgba('+(r-30)+','+(g-50)+','+(b-15)+',.40)');
c.fillStyle=grd; c.fill();
c.beginPath(); c.moveTo(0,size*.85); c.bezierCurveTo(0,0,0,-size*.5,0,-size*.85);
c.strokeStyle='rgba('+(r-50)+','+(g-80)+','+(b-40)+',.22)'; c.lineWidth=size*.06; c.stroke();
[-1,1].forEach(function(s){
c.beginPath(); c.moveTo(0,size*.2); c.quadraticCurveTo(s*size*.35,-size*.1,s*size*.5,-size*.35);
c.strokeStyle='rgba('+(r-50)+','+(g-80)+','+(b-40)+',.13)'; c.lineWidth=size*.045; c.stroke();
});
c.restore(); return oc;
}
function makeBlossom(size, r, g, b){
var oc=document.createElement('canvas');
oc.width=oc.height=size*2.6;
var c=oc.getContext('2d'), cx=size*1.3, cy=size*1.3;
for(var i=0;i<5;i++){
var angle=(Math.PI*2/5)*i-Math.PI/2;
var px=cx+Math.cos(angle)*size*.42, py=cy+Math.sin(angle)*size*.42;
c.save(); c.translate(px,py); c.rotate(angle+Math.PI/2);
c.beginPath(); c.ellipse(0,-size*.22,size*.28,size*.42,0,0,Math.PI*2);
var grd=c.createRadialGradient(0,-size*.1,0,0,-size*.1,size*.55);
grd.addColorStop(0,'rgba(255,235,242,.97)');
grd.addColorStop(.4,'rgba('+r+','+g+','+b+',.88)');
grd.addColorStop(1,'rgba('+(r-20)+','+(g-35)+','+(b-10)+',.55)');
c.fillStyle=grd; c.fill();
c.strokeStyle='rgba('+(r-30)+','+(g-50)+','+(b-20)+',.18)'; c.lineWidth=.6; c.stroke();
c.restore();
}
for(var j=0;j<8;j++){
var a=(Math.PI*2/8)*j, d=size*.18;
c.beginPath(); c.arc(cx+Math.cos(a)*d,cy+Math.sin(a)*d,size*.04,0,Math.PI*2);
c.fillStyle='rgba(255,200,100,.9)'; c.fill();
}
c.beginPath(); c.arc(cx,cy,size*.12,0,Math.PI*2);
var cg=c.createRadialGradient(cx-size*.03,cy-size*.03,0,cx,cy,size*.12);
cg.addColorStop(0,'rgba(255,230,200,1)'); cg.addColorStop(1,'rgba(240,170,120,.8)');
c.fillStyle=cg; c.fill();
return oc;
}
var PSM=[makePetal(9,255,175,200),makePetal(10,255,160,188),makePetal(8,250,190,210),makePetal(11,245,168,195)];
var PLG=[makePetal(18,255,180,205),makePetal(20,250,170,195),makePetal(22,255,185,210),makePetal(16,240,165,192)];
var BLS=[makeBlossom(22,255,182,210),makeBlossom(26,248,172,200),makeBlossom(20,255,195,218),makeBlossom(24,242,168,198)];
/* ════════════════════════════════
여름 - 빗방울
════════════════════════════════ */
function makeRaindrop(){
var oc=document.createElement('canvas');
oc.width=4; oc.height=18;
var c=oc.getContext('2d');
var grd=c.createLinearGradient(0,0,0,18);
grd.addColorStop(0,'rgba(147,210,255,0)');
grd.addColorStop(.3,'rgba(147,210,255,.7)');
grd.addColorStop(1,'rgba(200,235,255,.9)');
c.fillStyle=grd;
c.beginPath();
c.moveTo(2,0); c.lineTo(4,18); c.lineTo(0,18); c.closePath();
c.fill();
return oc;
}
var RAIN=[makeRaindrop()];
/* ════════════════════════════════
가을 - 낙엽
════════════════════════════════ */
function makeLeaf(size, r, g, b){
var oc=document.createElement('canvas');
oc.width=oc.height=size*3;
var c=oc.getContext('2d'), cx=size*1.5, cy=size*1.5;
c.save(); c.translate(cx,cy);
// 잎 모양
c.beginPath();
c.moveTo(0, size*.95);
c.bezierCurveTo(-size*.7, size*.4, -size*.85, -size*.3, 0, -size*.95);
c.bezierCurveTo( size*.85,-size*.3, size*.7, size*.4, 0, size*.95);
var grd=c.createRadialGradient(-size*.1,-size*.1,0,0,0,size);
grd.addColorStop(0,'rgba('+(r+20)+','+(g+15)+','+b+',.95)');
grd.addColorStop(.5,'rgba('+r+','+g+','+b+',.88)');
grd.addColorStop(1,'rgba('+(r-30)+','+(g-20)+','+(b-10)+',.6)');
c.fillStyle=grd; c.fill();
// 중앙 줄기
c.beginPath(); c.moveTo(0,size*.9); c.bezierCurveTo(0,size*.2,0,-size*.5,0,-size*.9);
c.strokeStyle='rgba('+(r-60)+','+(g-50)+','+(b-20)+',.35)'; c.lineWidth=size*.07; c.stroke();
// 잎맥 3쌍
for(var i=0;i<3;i++){
var yp = size*(.5 - i*.45);
[-1,1].forEach(function(s){
c.beginPath(); c.moveTo(0,yp); c.quadraticCurveTo(s*size*.4,yp-size*.15,s*size*.72,yp-size*.05);
c.strokeStyle='rgba('+(r-60)+','+(g-50)+','+(b-20)+',.2)'; c.lineWidth=size*.04; c.stroke();
});
}
c.restore(); return oc;
}
var LEAVES=[
makeLeaf(14,220,80,20), makeLeaf(12,200,60,15), makeLeaf(16,230,100,10),
makeLeaf(13,180,90,20), makeLeaf(15,210,130,30),makeLeaf(11,240,110,15),
];
/* ════════════════════════════════
겨울 - 눈송이
════════════════════════════════ */
function makeSnow(size){
var oc=document.createElement('canvas');
oc.width=oc.height=size*2.4;
var c=oc.getContext('2d'), cx=size*1.2, cy=size*1.2;
c.save(); c.translate(cx,cy);
var grd=c.createRadialGradient(0,0,0,0,0,size);
grd.addColorStop(0,'rgba(255,255,255,1)');
grd.addColorStop(.5,'rgba(220,240,255,.9)');
grd.addColorStop(1,'rgba(180,220,255,.5)');
// 눈송이 6갈래
for(var i=0;i<6;i++){
var a=(Math.PI/3)*i;
c.save(); c.rotate(a);
c.beginPath(); c.moveTo(0,0); c.lineTo(0,-size);
c.strokeStyle='rgba(220,240,255,.9)'; c.lineWidth=size*.13; c.stroke();
// 가지
[-1,1].forEach(function(s){
c.beginPath(); c.moveTo(0,-size*.45); c.lineTo(s*size*.25,-size*.65);
c.strokeStyle='rgba(200,230,255,.75)'; c.lineWidth=size*.09; c.stroke();
c.beginPath(); c.moveTo(0,-size*.7); c.lineTo(s*size*.18,-size*.85);
c.strokeStyle='rgba(200,230,255,.65)'; c.lineWidth=size*.07; c.stroke();
});
c.restore();
}
// 중앙 원
c.beginPath(); c.arc(0,0,size*.18,0,Math.PI*2);
c.fillStyle=grd; c.fill();
c.restore(); return oc;
}
function makeSnowball(size){
var oc=document.createElement('canvas');
oc.width=oc.height=size*2.4;
var c=oc.getContext('2d'), cx=size*1.2, cy=size*1.2;
c.beginPath(); c.arc(cx,cy,size,0,Math.PI*2);
var grd=c.createRadialGradient(cx-size*.25,cy-size*.25,0,cx,cy,size);
grd.addColorStop(0,'rgba(255,255,255,.95)');
grd.addColorStop(.6,'rgba(220,238,255,.75)');
grd.addColorStop(1,'rgba(180,215,255,.3)');
c.fillStyle=grd; c.fill();
return oc;
}
var SNOWS=[
makeSnow(10),makeSnow(7),makeSnow(13),makeSnow(8),
makeSnowball(5),makeSnowball(4),makeSnowball(7),
];
/* ════════════════════════════════
파티클 공통 클래스
════════════════════════════════ */
function Particle(burst){
var r=Math.random();
if(season==='spring'){
this.img = r<.55 ? PSM[Math.random()*PSM.length|0]
: r<.85 ? PLG[Math.random()*PLG.length|0]
: BLS[Math.random()*BLS.length|0];
this.scale = r<.55 ? .5+Math.random()*.9 : r<.85 ? .8+Math.random()*.8 : .7+Math.random()*.7;
this.vy = r<.55 ? .7+Math.random()*1.5 : r<.85 ? .5+Math.random()*1.1 : .4+Math.random()*.9;
this.vx = (Math.random()-.5)*(r<.9?1.0:.6);
this.vrot = (Math.random()-.5)*(r<.9?.055:.025);
this.swayA = .3+Math.random()*.9;
} else if(season==='summer'){
this.img = RAIN[0];
this.scale = .7+Math.random()*1.1;
this.vy = 8+Math.random()*7; // 빠르게
this.vx = -1.5+Math.random()*.5; // 살짝 기울어짐
this.vrot = 0;
this.swayA = 0;
} else if(season==='autumn'){
this.img = LEAVES[Math.random()*LEAVES.length|0];
this.scale = .6+Math.random()*1.0;
this.vy = .4+Math.random()*.9;
this.vx = (Math.random()-.5)*1.4;
this.vrot = (Math.random()-.5)*.06;
this.swayA = .5+Math.random()*1.2;
} else {
this.img = SNOWS[Math.random()*SNOWS.length|0];
this.scale = .5+Math.random()*1.1;
this.vy = .3+Math.random()*.8;
this.vx = (Math.random()-.5)*.6;
this.vrot = (Math.random()-.5)*.02;
this.swayA = .4+Math.random()*.8;
}
this.x = Math.random()*W;
this.y = burst ? Math.random()*H*.3 : -this.img.height;
this.rot = Math.random()*Math.PI*2;
this.swayF = .008+Math.random()*.018;
this.swayO = Math.random()*Math.PI*2;
this.alpha = season==='summer' ? .45+Math.random()*.35 : .72+Math.random()*.28;
this.t = Math.random()*400;
this.alive = true;
}
Particle.prototype.update=function(){
this.t++;
this.x += this.vx + Math.sin(this.t*this.swayF+this.swayO)*this.swayA;
this.y += this.vy;
if(season!=='summer') this.rot += this.vrot+Math.sin(this.t*.025)*.012;
if(this.y > H+80) this.alive=false;
};
Particle.prototype.draw=function(ctx){
var fade=Math.min(1,(H-this.y+80)/80);
ctx.save();
ctx.globalAlpha=this.alpha*fade;
ctx.translate(this.x,this.y);
ctx.rotate(this.rot);
ctx.scale(this.scale,this.scale);
var s=this.img.width;
ctx.drawImage(this.img,-s/2,-s/2);
ctx.restore();
};
/* ════════════════════════════════
메인 루프
════════════════════════════════ */
var particles=[], running=false, spawnInt=null, animId=null;
function spawnOne(){ particles.push(new Particle(false)); }
function loop(){
ctx.clearRect(0,0,W,H);
particles=particles.filter(function(p){ return p.alive; });
particles.forEach(function(p){ p.update(); p.draw(ctx); });
if(running||particles.length>0) animId=requestAnimationFrame(loop);
else animId=null;
}
function start(){
var burstCount = season==='summer' ? 80 : 70;
for(var i=0;i<burstCount;i++) particles.push(new Particle(true));
var interval = season==='summer' ? 40 : 100;
var maxCount = season==='summer' ? 300 : 200;
spawnInt=setInterval(function(){ if(particles.length<maxCount) spawnOne(); }, interval);
if(!animId) loop();
}
function stop(){ clearInterval(spawnInt); spawnInt=null; }
btn.addEventListener('click',function(){
this.style.transform='scale(1.3) rotate(-15deg)';
var self=this;
setTimeout(function(){ self.style.transform=''; },280);
if(!running){
running=true;
self.style.boxShadow='0 6px 35px '+bs.shadow;
start();
} else {
running=false;
self.style.boxShadow='0 4px 20px '+bs.shadow;
stop();
}
});
})();
</script>