SOURCE

console 命令行工具 X clear

                    
>
console
const canvas = document.getElementById("snow");
const ctx = canvas.getContext("2d");
const startText = document.getElementById("startText");

let W, H;
let particles = [];
let snows = [];
let hearts = [];
let ripples = [];
let glow = 0;
let isClicking = false;
// 初始数量
let snowCount = 80;
let heartCount = 8;

// ✅一共5条(新增2条,第5条是最终句:这个冬天,我陪你)
const blessingList = [
  "冬至,岁岁安暖",
  "冬至,幸得有你",
  "凛冬至,共朝夕",
  "长夜逢冬至,有幸遇见你",
  "这个冬天,我陪你"
];
let stage = -1; // -1=冬至,0,1,2,3,4

function resize() {
  const dpr = window.devicePixelRatio || 1;
  W = window.innerWidth;
  H = window.innerHeight;
  canvas.width = W * dpr;
  canvas.height = H * dpr;
  canvas.style.width = W + "px";
  canvas.style.height = H + "px";
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
resize();
window.addEventListener("resize", resize);

class Snow{
  constructor(){
    this.reset();
  }
  reset(){
    this.x = Math.random()*W;
    this.y = -10;
    this.r = Math.random()*2 + 0.6;
    this.sy = Math.random()*1.4 + 0.6;
    this.sx = (Math.random()-0.5)*0.7;
    this.alpha = Math.random()*0.6 + 0.3;
  }
  update(){
    this.y += this.sy;
    this.x += this.sx + Math.sin(this.y*0.01)*0.2;
    if(this.y>H) this.reset();
  }
  draw(){
    ctx.save();
    ctx.globalAlpha = this.alpha;
    ctx.fillStyle="#fff";
    ctx.beginPath();
    ctx.arc(this.x,this.y,this.r,0,Math.PI*2);
    ctx.fill();
    ctx.restore();
  }
}

class Heart{
  constructor(){
    this.reset();
  }
  reset(){
    this.x = Math.random()*W;
    this.y = -10;
    this.size = Math.random()*10 + 6;
    this.sy = Math.random()*0.8 + 0.4;
    this.sx = (Math.random()-0.5)*0.4;
    this.alpha = Math.random()*0.5 + 0.4;
  }
  update(){
    this.y += this.sy;
    this.x += this.sx + Math.sin(this.y*0.01)*0.15;
    if(this.y>H) this.reset();
  }
  draw(){
    ctx.save();
    ctx.globalAlpha = this.alpha;
    ctx.translate(this.x,this.y);
    ctx.scale(this.size/16,this.size/16);
    ctx.fillStyle="#ff6b9d";
    ctx.beginPath();
    ctx.moveTo(0,-4);
    ctx.bezierCurveTo(6,-12,12,-4,0,8);
    ctx.bezierCurveTo(-12,-4,-6,-12,0,-4);
    ctx.fill();
    ctx.restore();
  }
}

class Ripple{
  constructor(x,y){
    this.x=x;this.y=y;
    this.r=0;
    this.maxR = Math.min(W,H)*0.25;
    this.alpha=0.7;
  }
  update(){
    this.r +=4;
    this.alpha -=0.025;
  }
  draw(){
    ctx.save();
    ctx.globalAlpha = Math.max(0,this.alpha);
    ctx.strokeStyle="rgba(255,255,255,0.8)";
    ctx.lineWidth=2;
    ctx.beginPath();
    ctx.arc(this.x,this.y,this.r,0,Math.PI*2);
    ctx.stroke();
    ctx.restore();
  }
}

class Particle{
  constructor(tx,ty,char){
    this.x = Math.random()*W;
    this.y = -H;
    this.tx = tx;
    this.ty = ty;
    this.char = char;
    this.size = 18;
    this.formed = false;
  }
  update(){
    if(!this.formed){
      const dx = this.tx - this.x;
      const dy = this.ty - this.y;
      this.x += dx*0.1;
      this.y += dy*0.1;
      if(Math.abs(dx)<6 && Math.abs(dy)<6){
        this.formed = true;
      }
    }else{
      this.x += Math.sin(Date.now()*0.001 + this.tx)*0.15;
      this.y += Math.cos(Date.now()*0.0013 + this.ty)*0.15;
    }
  }
  draw(){
    ctx.save();
    if(this.formed){
      ctx.shadowBlur = 12*glow;
      ctx.shadowColor = "rgba(255,255,255,0.9)";
    }
    ctx.fillStyle="#fff";
    ctx.font = `bold ${this.size}px "Microsoft YaHei"`;
    ctx.fillText(this.char,this.x,this.y);
    ctx.restore();
  }
}

function addSnow(num){
  for(let i=0;i<num;i++){
    snows.push(new Snow());
  }
}
function addHeart(num){
  for(let i=0;i<num;i++){
    hearts.push(new Heart());
  }
}

function initTextParticle(text){
  particles = [];
  ctx.save();
  ctx.font="bold 48px Microsoft YaHei";
  const m = ctx.measureText(text);
  const startX = W/2 - m.width/2;
  const baseY = H/2;
  for(let i=0;i<text.length;i++){
    const char = text[i];
    const w = ctx.measureText(char).width;
    particles.push(new Particle(startX + w*i, baseY, char));
  }
  ctx.restore();
}

function tapHandle(x,y){
  if(isClicking) return;
  isClicking = true;
  ripples.push(new Ripple(x,y));
  particles = [];
  glow = 0;
  stage++;
  // ✅点击一次增加雪花爱心
  addSnow(25);
  addHeart(3);

  if(stage < blessingList.length){
    initTextParticle(blessingList[stage]);
    startText.style.opacity = "0";
    startText.style.display = "none";
  }else{
    // 全部走完回到冬至
    stage = -1;
    startText.style.display = "block";
    startText.style.opacity = "1";
  }
  setTimeout(()=>{
    isClicking = false;
  },250);
}
canvas.addEventListener("click",e=>tapHandle(e.clientX,e.clientY));
 canvas.addEventListener("touchstart",e=>{
   e.preventDefault();
   for(let t of e.touches) tapHandle(t.clientX,t.clientY);
 },{passive:false})
 function loop(){
   ctx.clearRect(0,0,W,H);
   snows.forEach(s=>{s.update();s.draw()});
   hearts.forEach(h=>{h.update();h.draw()});
   ripples = ripples.filter(r=>r.alpha>0);
   ripples.forEach(r=>{r.update();r.draw()});
   if(particles.length>0){
     particles.forEach(p=>{p.update();p.draw()});
     glow = Math.min(1, glow+0.02);
   }
   requestAnimationFrame(loop);
 }
 //初始化
 for(let i=0;i<snowCount;i++) snows.push(new Snow());
 for(let i=0;i<heartCount;i++) hearts.push(new Heart());
 loop();
<div id="startText">冬至</div>
<canvas id="snow"></canvas>
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html, body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: #0f172a;
  font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

#startText {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: #ffffff;
  font-size: clamp(48px, 11vw, 96px);
  letter-spacing: clamp(10px, 3vw, 24px);
  z-index: 5;
  pointer-events: none;
  opacity: 1;
  transition: opacity 0.7s ease;
  white-space: nowrap;
}

#snow {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 2;
}