index.html

16.53 KB
11/12/2025 12:40
HTML
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>Merry Christmas • Happy New Year</title>

  <!-- Google Fonts -->
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600;800&family=Great+Vibes&display=swap" rel="stylesheet">

  <style>
    :root{
      --bg1: #0f1724;
      --bg2: #081126;
      --accent: #ffcf6b;
      --accent-2: #7be6ff;
      --card: rgba(255,255,255,0.04);
      --glass: rgba(255,255,255,0.06);
      --text: #e6f2ff;
      --muted: rgba(230,242,255,0.65);
    }

    *{box-sizing:border-box}
    html,body{height:100%}
    body{
      margin:0;
      background: radial-gradient(1200px 600px at 10% 20%, rgba(88,116,255,0.08), transparent),
                  linear-gradient(180deg,var(--bg1), var(--bg2) 60%);
      font-family: 'Poppins', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial;
      color:var(--text);
      -webkit-font-smoothing:antialiased;
      -moz-osx-font-smoothing:grayscale;
      overflow-x:hidden;
      display:flex;
      align-items:center;
      justify-content:center;
      padding:32px;
    }

    .container{
      width:min(1100px, 96%);
      display:grid;
      grid-template-columns: 1fr 420px;
      gap:28px;
      align-items:center;
    }

    /* card */
    .hero{
      background: linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01));
      border-radius:20px;
      padding:48px;
      position:relative;
      overflow:hidden;
      box-shadow: 0 10px 30px rgba(3,8,20,0.6);
      border: 1px solid rgba(255,255,255,0.03);
    }

    /* festive lights */
    .light-strings{
      position:absolute;
      left: -6%;
      top: -6%;
      width:112%;
      height:30%;
      pointer-events:none;
      transform:translateY(-6%);
    }
    .light{
      position:absolute;
      height:12px;
      width:12px;
      border-radius:50%;
      opacity:0.95;
      box-shadow: 0 0 8px rgba(255,255,255,0.12), 0 0 18px currentColor;
      transform-origin:center;
      animation: glow 2.5s infinite ease-in-out;
    }
    .light:nth-child(odd){ animation-duration:3s }
    @keyframes glow{
      0%{ transform:translateY(0) scale(0.85); opacity:0.55 }
      50%{ transform:translateY(4px) scale(1.05); opacity:1 }
      100%{ transform:translateY(0) scale(0.85); opacity:0.55 }
    }

    /* headline */
    .title{
      display:flex;
      gap:18px;
      align-items:center;
      margin-bottom:18px;
    }
    .badge{
      background:linear-gradient(90deg,var(--accent), #ff7aa2);
      padding:6px 12px;
      border-radius:12px;
      font-weight:700;
      font-size:14px;
      color:#081126;
      letter-spacing:0.4px;
    }
    h1{
      font-size:36px;
      margin:0;
      line-height:1.02;
      font-weight:800;
    }
    .script{
      font-family: 'Great Vibes', cursive;
      font-size:36px;
      display:block;
      color:var(--accent);
      margin-top:-6px;
      text-shadow:0 6px 18px rgba(0,0,0,0.6);
    }

    p.lead{
      color:var(--muted);
      margin-top:14px;
      margin-bottom:24px;
      max-width:70ch;
      font-size:16px;
    }

    /* countdown */
    .countdown{
      display:flex;
      gap:14px;
      align-items:center;
      margin-bottom:20px;
      flex-wrap:wrap;
    }
    .time-card{
      background:var(--card);
      padding:14px 18px;
      border-radius:12px;
      min-width:86px;
      text-align:center;
      backdrop-filter: blur(6px);
      border:1px solid rgba(255,255,255,0.03);
    }
    .time-card b{display:block; font-size:20px; font-weight:700}
    .time-card small{display:block; color:var(--muted); font-size:12px; margin-top:6px}

    /* CTA */
    .cta{
      display:flex;
      gap:12px;
      flex-wrap:wrap;
      align-items:center;
    }
    .btn{
      appearance:none;
      border:0;
      outline:0;
      padding:12px 18px;
      border-radius:12px;
      font-weight:700;
      cursor:pointer;
      background:linear-gradient(90deg,var(--accent), #ff7aa2);
      color:#081126;
      box-shadow: 0 6px 20px rgba(255,124,170,0.12);
      transition:transform .18s ease, box-shadow .18s ease;
    }
    .btn.secondary{
      background:transparent;
      color:var(--text);
      border:1px solid rgba(255,255,255,0.06);
      box-shadow:none;
    }
    .btn:active{ transform:translateY(1px) }

    /* right column - card with ornaments and message */
    .side{
      background: linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01));
      border-radius:20px;
      padding:22px;
      min-height:360px;
      display:flex;
      flex-direction:column;
      gap:14px;
      align-items:center;
      justify-content:center;
      position:relative;
      overflow:hidden;
      border:1px solid rgba(255,255,255,0.03);
    }

    .ornament-wrap{
      position:relative;
      width:100%;
      height:220px;
      display:flex;
      align-items:center;
      justify-content:center;
    }

    .ornament{
      width:86px; height:86px;
      border-radius:50%;
      background:linear-gradient(145deg,var(--accent), #ff7aa2);
      box-shadow: 0 8px 30px rgba(0,0,0,0.45);
      display:flex;
      align-items:center;
      justify-content:center;
      font-weight:800;
      color:#081126;
      transform-origin:center;
      position:absolute;
      will-change:transform,opacity;
      border: 6px solid rgba(255,255,255,0.06);
    }

    .side p{
      color:var(--muted);
      text-align:center;
      margin:0;
      padding:0 10px;
    }

    /* snowflakes layer */
    .snow{
      position:fixed;
      left:0; top:0;
      width:100%;
      height:100%;
      pointer-events:none;
      z-index:1;
      overflow:hidden;
    }

    .flake{
      position:absolute;
      top:-10vh;
      width:8px;
      height:8px;
      background:linear-gradient(180deg, rgba(255,255,255,0.95), rgba(255,255,255,0.7));
      border-radius:50%;
      opacity:0.9;
      filter:blur(0.2px);
      will-change:transform,opacity;
    }

    /* small footer */
    .foot{
      margin-top:14px;
      color:var(--muted);
      font-size:13px;
      text-align:center;
    }

    /* responsive */
    @media (max-width:980px){
      .container{ grid-template-columns: 1fr; }
      .side{ order:-1; }
      .hero{ padding:28px; }
      h1{ font-size:30px }
      .script{ font-size:30px }
    }
  </style>
</head>
<body>
  <div class="snow" id="snow"></div>

  <div class="container" role="main">
    <section class="hero" aria-labelledby="heroTitle">
      <div class="light-strings" aria-hidden="true" id="lights">
        <!-- lights created by JS -->
      </div>

      <div class="title">
        <div class="badge">Season's Greetings</div>
        <div>
          <h1 id="heroTitle">Merry Christmas & <span class="script">Happy New Year</span></h1>
        </div>
      </div>

      <p class="lead">Wishing you warmth, joy, and magic during the holidays. Enjoy an elegant celebration with friends and family — here's a little landing page to spread some festive cheer.</p>

      <div class="countdown" aria-live="polite" id="countdown">
        <div class="time-card" title="Days">
          <b id="days">--</b>
          <small>Days</small>
        </div>
        <div class="time-card" title="Hours">
          <b id="hours">--</b>
          <small>Hours</small>
        </div>
        <div class="time-card" title="Minutes">
          <b id="minutes">--</b>
          <small>Minutes</small>
        </div>
        <div class="time-card" title="Seconds">
          <b id="seconds">--</b>
          <small>Seconds</small>
        </div>
      </div>

      <div class="cta">
        <button class="btn" id="share">Share the Joy</button>
        <button class="btn secondary" id="music">Play Cheer</button>
      </div>

      <div class="foot">Designed with ✨ — enjoy the season.</div>
    </section>

    <aside class="side" aria-label="Festive Decorations">
      <div class="ornament-wrap" id="ornaments" aria-hidden="true">
        <div class="ornament" data-idx="0" style="left:22%;">🎄</div>
        <div class="ornament" data-idx="1" style="left:50%;">⭐</div>
        <div class="ornament" data-idx="2" style="left:78%;">❄️</div>
      </div>

      <p>May the new year bring you new opportunities, success, and delightful surprises. From our hearts to yours — happy holidays.</p>

      <div style="font-size:13px;color:var(--muted)">Tip: click "Share the Joy" to craft a quick festive message to share.</div>
    </aside>
  </div>

  <script>
    // LandingPage class encapsulates behavior
    class LandingPage {
      constructor(){
        this.elements = {
          days: document.getElementById('days'),
          hours: document.getElementById('hours'),
          minutes: document.getElementById('minutes'),
          seconds: document.getElementById('seconds'),
          snowContainer: document.getElementById('snow'),
          ornaments: document.getElementById('ornaments'),
          lights: document.getElementById('lights'),
          shareBtn: document.getElementById('share'),
          musicBtn: document.getElementById('music')
        };
        this.snowCount = 36;
        this.snowFlakes = [];
        this.animId = null;
        this.audio = null;
      }

      init(){
        this.initLights();
        this.initOrnaments();
        this.createSnow();
        this.startCountdown();
        this.initShare();
        this.initMusic();
        window.addEventListener('resize', ()=> this.onResize());
      }

      // create twinkling lights along top
      initLights(){
        const colors = ['#ffcf6b','#ff7aa2','#7be6ff','#7cffb2','#c9a7ff'];
        for(let i=0;i<12;i++){
          const el = document.createElement('span');
          el.className='light';
          const left = Math.round((i/11)*100);
          el.style.left = left + '%';
          el.style.top = (Math.sin(i)*8 + 6) + '%';
          el.style.background = colors[i % colors.length];
          el.style.transform = `translateY(0) rotate(${i*8}deg)`;
          el.style.boxShadow = `0 0 10px ${colors[i % colors.length]}`;
          el.style.opacity = (0.6 + Math.random()*0.45);
          this.elements.lights.appendChild(el);
        }
      }

      // ornaments floating animation with requestAnimationFrame
      initOrnaments(){
        this.orbs = Array.from(this.elements.ornaments.querySelectorAll('.ornament'));
        this.orbsState = this.orbs.map((el, idx)=>{
          const baseX = el.offsetLeft || (20 + idx*28);
          return {
            el,
            angle: Math.random()*Math.PI*2,
            radius: 18 + Math.random()*36,
            speed: 0.002 + Math.random()*0.006,
            baseX,
            baseY: 60 + Math.random()*40
          };
        });
        const loop = (t)=>{
          for(const s of this.orbsState){
            s.angle += s.speed * (1 + Math.sin(t*0.0002)*0.6);
            const x = s.baseX + Math.cos(s.angle) * s.radius;
            const y = s.baseY + Math.sin(s.angle*1.1) * (s.radius*0.6);
            s.el.style.transform = `translate(${x - s.el.offsetLeft}px, ${y}px) rotate(${Math.sin(s.angle)*8}deg)`;
            s.el.style.opacity = (0.85 + Math.sin(s.angle)*0.12);
          }
          this.animId = requestAnimationFrame(loop);
        };
        this.animId = requestAnimationFrame(loop);
      }

      // create snow DOM nodes and animate them
      createSnow(){
        const w = window.innerWidth;
        const h = window.innerHeight;
        const frag = document.createDocumentFragment();
        for(let i=0;i<this.snowCount;i++){
          const f = document.createElement('div');
          f.className = 'flake';
          const size = 4 + Math.random()*12;
          f.style.width = size + 'px';
          f.style.height = size + 'px';
          f.style.left = Math.random()*w + 'px';
          f.style.top = (-Math.random()*h*0.6) + 'px';
          f.style.opacity = (0.5 + Math.random()*0.9);
          f.dataset.speed = (0.2 + Math.random()*1.2);
          frag.appendChild(f);
          this.snowFlakes.push(f);
        }
        this.elements.snowContainer.appendChild(frag);
        this.snowLoop();
      }

      snowLoop(){
        const w = window.innerWidth;
        const h = window.innerHeight;
        for(const f of this.snowFlakes){
          const speed = parseFloat(f.dataset.speed);
          let top = parseFloat(f.style.top);
          let left = parseFloat(f.style.left);
          top += 0.6 + speed * 1.6;
          left += Math.sin(top*0.01) * (0.3 + speed*0.6);
          if(top > h + 20){
            top = -20 - Math.random()*h*0.4;
            left = Math.random()*w;
            f.style.opacity = (0.4 + Math.random()*0.9);
          }
          f.style.top = top + 'px';
          f.style.left = left + 'px';
          f.style.transform = `rotate(${top*0.05}deg)`;
        }
        requestAnimationFrame(()=>this.snowLoop());
      }

      // countdown to next Jan 1 (local timezone)
      startCountdown(){
        const nextYear = (new Date()).getFullYear() + 1;
        const target = new Date(nextYear, 0, 1, 0, 0, 0, 0); // Jan 1 next year
        const update = ()=>{
          const now = new Date();
          let diff = Math.max(0, target - now);
          const days = Math.floor(diff / (1000*60*60*24));
          diff -= days * (1000*60*60*24);
          const hours = Math.floor(diff / (1000*60*60));
          diff -= hours * (1000*60*60);
          const minutes = Math.floor(diff / (1000*60));
          diff -= minutes * (1000*60);
          const seconds = Math.floor(diff / 1000);
          this.elements.days.textContent = String(days).padStart(2,'0');
          this.elements.hours.textContent = String(hours).padStart(2,'0');
          this.elements.minutes.textContent = String(minutes).padStart(2,'0');
          this.elements.seconds.textContent = String(seconds).padStart(2,'0');
        };
        update();
        this.countdownTimer = setInterval(update, 1000);
      }

      initShare(){
        this.elements.shareBtn.addEventListener('click', async ()=>{
          const text = `Merry Christmas & Happy New Year! 🎄✨\nWishing you joy and a wonderful ${new Date().getFullYear()+1}.`;
          if(navigator.share){
            try{
              await navigator.share({ title: 'Seasonal Greetings', text });
            }catch(e){ /* user cancelled */ }
          } else {
            // fallback: copy to clipboard with visual hint
            try{
              await navigator.clipboard.writeText(text);
              alert('Message copied to clipboard — share it with friends!');
            }catch(e){
              prompt('Copy this message:', text);
            }
          }
        });
      }

      initMusic(){
        // small cheerful loop (generated tones using Web Audio)
        const btn = this.elements.musicBtn;
        let playing = false;
        let audioCtx = null;
        btn.addEventListener('click', ()=>{
          if(!playing){
            playing = true;
            btn.textContent = 'Stop Cheer';
            btn.style.opacity = '1';
            // start a simple WebAudio loop
            audioCtx = new (window.AudioContext || window.webkitAudioContext)();
            const master = audioCtx.createGain();
            master.gain.value = 0.05;
            master.connect(audioCtx.destination);

            // create a gently arpeggiated oscillator pattern
            const notes = [440, 523.25, 659.25, 740.0]; // A4, C5, E5, F#5-ish
            let step = 0;
            this.musicInterval = setInterval(()=>{
              const osc = audioCtx.createOscillator();
              const gain = audioCtx.createGain();
              osc.type = 'sine';
              osc.frequency.value = notes[step % notes.length];
              gain.gain.value = 0.0018;
              osc.connect(gain);
              gain.connect(master);
              osc.start();
              // fade out
              gain.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + 0.9);
              osc.stop(audioCtx.currentTime + 1.0);
              step++;
            }, 340);
            this.audioCtx = audioCtx;
          } else {
            playing = false;
            btn.textContent = 'Play Cheer';
            clearInterval(this.musicInterval);
            if(this.audioCtx && this.audioCtx.state !== 'closed'){
              this.audioCtx.close();
            }
          }
        });
      }

      onResize(){
        // simple reposition of ornaments base positions
        this.orbsState.forEach((s, i)=>{
          const leftPercent = (20 + i*30);
          s.baseX = (this.elements.ornaments.clientWidth * leftPercent / 100);
        });
      }
    }

    // init page
    const page = new LandingPage();
    page.init();
  </script>
</body>
</html>