婚禮邀請函

by j91157j91157

HTML

<!-- 在外部 CSS 載入前就蓋住畫面,防止背景閃爍 -->
  
  <link rel="stylesheet" href="style.css" />
  <link href="https://unpkg.com/[email protected]/dist/aos.css" rel="stylesheet" />
  <!-- 引入 Google Fonts -->
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link
    href="https://fonts.googleapis.com/css2?family=Noto+Serif+TC:wght@400;700&family=Noto+Sans+TC:wght@300;400;500;700&display=swap"
    rel="stylesheet" />
  <link href="https://fonts.googleapis.com/css2?family=Chiron+GoRound+TC:[email protected]&display=swap" rel="stylesheet">

<!-- Vanta Fog 背景容器 -->
<div id="vanta-bg"></div>

<!-- 載入畫面:必須在所有外部資源之前,確保第一幀就蓋住畫面 -->
<div class="loader" id="loader">
  <div class="loader-content">
    <div class="heart-loader">
      <div class="heart-wrapper">
        <div class="heart"></div>
      </div>
      <div class="heart-wrapper">
        <div class="heart"></div>
      </div>
    </div>
    <p class="loader-text">Loading...</p>
  </div>
</div>

<div class="global-petals-container" id="globalPetalsContainer"></div>

<!-- 漢堡選單按鈕 -->
<button class="menu-toggle" onclick="toggleMenu()" aria-label="開啟選單">
  <span></span>
  <span></span>
  <span></span>
</button>

<!-- 側邊導航列 -->
<nav class="sidebar" id="sidebar">
  <div class="sidebar-header">
    <h3>💕 選單</h3>
    <button class="close-btn" onclick="toggleMenu()">✕</button>
  </div>
  <div class="nav-links">
    <a href="#home" onclick="toggleMenu()">
      <span class="icon">🏠</span>
      <span>首頁</span>
    </a>
    <a href="#date" onclick="toggleMenu()">
      <span class="icon">📅</span>
      <span>婚宴日期</span>
    </a>
    <a href="#location" onclick="toggleMenu()">
      <span class="icon">📍</span>
      <span>婚宴地點</span>
    </a>
    <a href="#schedule" onclick="toggleMenu()">
      <span class="icon">⏰</span>
      <span>婚禮流程</span>
    </a>
    <a href="#gallery" onclick="toggleMenu()">
     ...

CSS

/* ==================== 基礎設定 ==================== */
:root {
    --primary: #c5a4f8;
    --secondary: #8fa8d8;
    --accent: #ede0ff;
    --dark: #212529;
    --mid: #495057;
    --muted: #adb5bd;
    --light-purple: #f0e8ff;
    --soft: #f8f5ff;
    --blue-purple: #a78bfa;
    --green-muted: #6c757d;
    --gold: #d4af37;
    --gradient-main: linear-gradient(135deg, #c5a4f8 0%, #8fa8d8 100%);
}

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

html {
    scroll-behavior: smooth;
    overflow-x: hidden;
}

/* ==================== Vanta Fog 背景 ==================== */
body {
    font-family: 'Noto Sans TC', sans-serif;
    line-height: 1.8;
    color: var(--dark);
    overflow-x: hidden;
    background-color: transparent;
}

/* Vanta 固定背景容器 */
#vanta-bg {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    /* fallback:舊瀏覽器 */
    height: 100vh;
    /* lvh = Largest Viewport Height,網址列隱藏時的最大高度,避免底部白塊 */
    height: 100lvh;
    z-index: -1;
}

/* 固定 SVG 背景層(停用) */
#svg-bg {
    display: none;
}
.container {
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 20px;
}

/* ==================== 載入畫面 ==================== */
.loader {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: linear-gradient(135deg, #c5d8f5 0%, #d4c5f5 100%);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 99999;
    transition: opacity 0.5s, visibility 0.5s;
}

.loader.hidden {
    opacity: 0;
    visibility: hidden;
}

.loader-content {
    text-align: center;
}

.heart-loader {
    position: relative;
    width: 100px;
    height: 100px;
    margin: 0 auto 30px;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* 包裝層 - 負責旋轉 */
.heart-wrapper {
    position: absolute;
    transform: rotate(-45deg);
    /* 固定旋轉角度 */
}

/* 愛心本體 - 只負責縮放動畫...

JavaScript

// ==================== 初始化 ====================
document.addEventListener('DOMContentLoaded', function () {
  AOS.init({
    duration: 1000,
    once: true,
    offset: 100,
    easing: 'ease-out-cubic',
  });

  initVantaBg();
  initLoader();
  initPetals();
  initGallery();
  initScrollEffects();
  initCountdownDisplay();
  initScrollAnimations();

  console.log('💕 電子喜帖載入完成!');
});

// ==================== Vanta Fog 背景 ====================
function initVantaBg() {
  if (typeof VANTA === 'undefined' || typeof VANTA.FOG === 'undefined') return;

  const vantaEffect = VANTA.FOG({
    el: '#vanta-bg',
    mouseControls: true,
    touchControls: true,
    gyroControls: false,
    // 色系:以網頁的紫色、藍紫、玫瑰金為主
    highlightColor: 0xf0d6ff,   // 淡薰衣草紫(亮部)
    midtoneColor:   0xc5a4f8,   // 主紫色(中間調)
    lowlightColor:  0x8fa8d8,   // 藍紫(暗部)
    baseColor:      0xf5eeff,   // 極淡紫白(底色)
    blurFactor:     0.62,       // 霧感模糊程度
    speed:          1.2,        // 流動速度(慢一點更浪漫)
    zoom:           0.8,        // 縮放(稍微拉遠讓霧更大片)
  });

  // 保底:舊裝置不支援 lvh 時,監聽 resize 強制更新 canvas 尺寸
  // 使用 debounce 避免 resize 觸發過於頻繁
  let resizeTimer;
  window.addEventListener('resize', function () {
    clearTimeout(resizeTimer);
    resizeTimer = setTimeout(function () {
      if (vantaEffect && typeof vantaEffect.resize === 'function') {
        vantaEffect.resize();
      }
    }, 150);
  });
}

// ==================== 載入畫面 ====================
function initLoader() {
  const loader = document.getElementById('loader');
  setTimeout(() => {
    loader.classList.add('hidden');
    setTimeout(() => {
      loader.remove();
    }, 500);
  }, 2000);
}

// ==================== 花瓣飄落 ====================
function initPetals() {
  const container = document.getElementById('globalPetalsContainer');
  if (!container) return;

  const isMobile = window.innerWidth < 768;
  const maxPetals = isMobile ? 12 : 20;
  let petalCount = 0;

  const...