diff --git a/.claude/launch.json b/.claude/launch.json new file mode 100644 index 0000000..bd7cee6 --- /dev/null +++ b/.claude/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.0.1", + "configurations": [ + { + "name": "local", + "runtimeExecutable": "npx", + "runtimeArgs": ["http-server", ".", "-p", "3000", "-c-1"], + "port": 3000 + } + ] +} diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..d33ac74 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "mcp__Claude_Preview__preview_start" + ] + } +} diff --git a/index.html b/index.html index 70ed50b..48e41ad 100644 --- a/index.html +++ b/index.html @@ -28,6 +28,8 @@ + + @@ -37,6 +39,7 @@ @import url("./static/css/memos.css"); @import url("./static/css/game_dialog.css"); @import url("./static/css/guestbook.css"); + @import url("./static/css/galaxy.css"); 白荼 - BAITU @@ -171,10 +174,23 @@

旅途剪影

- +
+
+ +
+ +
- + +
+ +
+ +
滚轮缩放 | 拖拽旋转 | 点击星星查看照片
@@ -508,6 +524,7 @@ + diff --git a/static/css/galaxy.css b/static/css/galaxy.css new file mode 100644 index 0000000..bdd5c3c --- /dev/null +++ b/static/css/galaxy.css @@ -0,0 +1,618 @@ +/* ==================== 星空预览样式 ==================== */ + +/* 星空模式切换按钮(在弹窗header中) - 透明玻璃效果 + 黑色字体 */ +.galaxy-toggle-btn { + display: none; /* 隐藏旧按钮 */ +} + +/* 模式切换容器 */ +.mode-switch-container { + margin-right: 12px; +} + +/* 模式切换按钮 */ +.mode-switch-btn { + display: flex; + background: rgba(0, 0, 0, 0.05); + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 6px; + padding: 2px; + cursor: pointer; + gap: 2px; +} + +.mode-label { + padding: 6px 12px; + font-size: 13px; + color: #666; + border-radius: 4px; + transition: all 0.3s ease; +} + +.mode-label.active { + background: rgba(0, 0, 0, 0.1); + color: #1a1a1a; +} + +.mode-label:hover:not(.active) { + color: #333; +} + +/* 瀑布模式容器 */ +.waterfall-wrapper { + display: none; + flex: 1; + overflow-y: auto; + padding: 20px; + background: linear-gradient(180deg, #f5f5f5 0%, #fff 100%); +} + +.waterfall-wrapper.active { + display: block; +} + +.waterfall-container { + column-count: 3; + column-gap: 16px; + max-width: 1200px; + margin: 0 auto; +} + +/* 星空模式header样式 */ +.galaxy-mode-header { + background: rgba(10, 10, 46, 0.95) !important; + border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important; +} + +.galaxy-mode-header h2 { + color: #fff !important; + text-shadow: 0 0 10px rgba(255, 215, 0, 0.5), 0 1px 2px rgba(0, 0, 0, 0.5) !important; +} + +.galaxy-mode-header .guestbook-close { + background: rgba(255, 255, 255, 0.1) !important; + border: 1px solid rgba(255, 255, 255, 0.2) !important; + color: #fff !important; +} + +.galaxy-mode-header .guestbook-close:hover { + background: rgba(255, 0, 0, 0.5) !important; + border-color: rgba(255, 0, 0, 0.6) !important; +} + +.galaxy-mode-header .mode-switch-btn { + background: rgba(255, 255, 255, 0.1) !important; + border: 1px solid rgba(255, 255, 255, 0.2) !important; +} + +.galaxy-mode-header .mode-label { + color: rgba(255, 255, 255, 0.7) !important; +} + +.galaxy-mode-header .mode-label.active { + background: rgba(255, 255, 255, 0.2) !important; + color: #fff !important; +} + +.waterfall-item { + break-inside: avoid; + margin-bottom: 16px; + background: #fff; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); + transition: transform 0.3s ease, box-shadow 0.3s ease; + cursor: pointer; +} + +.waterfall-item:hover { + transform: translateY(-4px); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); +} + +.waterfall-item img { + width: 100%; + display: block; + border-radius: 12px 12px 0 0; +} + +.waterfall-item-info { + padding: 12px; + font-size: 12px; + color: #666; + text-align: center; + background: #fff; +} + +/* 响应式设计 */ +@media (max-width: 900px) { + .waterfall-container { + column-count: 2; + } +} + +@media (max-width: 600px) { + .waterfall-container { + column-count: 1; + } +} + +.header-controls { + display: flex; + align-items: center; +} + +/* 星空画布容器(在弹窗内) */ +.galaxy-canvas-wrapper { + display: none; + flex: 1; + position: relative; + background: radial-gradient(ellipse at center, #1a1a3e 0%, #0a0a2e 50%, #050520 100%); + overflow: hidden; +} + +.galaxy-canvas-wrapper.active { + display: block; +} + +/* 星空背景动画 - 繁杂无序 */ +.galaxy-canvas-wrapper::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-image: + /* 第一大区域 - 各种随机位置和大小 */ + radial-gradient(1px 1px at 5px 15px, #fff, transparent), + radial-gradient(2px 2px at 45px 8px, rgba(255,255,255,0.85), transparent), + radial-gradient(1px 1px at 82px 42px, rgba(255,255,255,0.6), transparent), + radial-gradient(3px 3px at 120px 25px, #fff, transparent), + radial-gradient(1px 1px at 155px 58px, rgba(255,255,255,0.9), transparent), + radial-gradient(2px 2px at 192px 12px, rgba(255,255,255,0.4), transparent), + radial-gradient(1px 1px at 228px 48px, #fff, transparent), + radial-gradient(2px 2px at 265px 32px, rgba(255,255,255,0.7), transparent), + radial-gradient(1px 1px at 302px 65px, rgba(255,255,255,0.5), transparent), + radial-gradient(1px 1px at 338px 8px, #fff, transparent), + radial-gradient(2px 2px at 375px 42px, rgba(255,255,255,0.8), transparent), + radial-gradient(1px 1px at 412px 18px, rgba(255,255,255,0.6), transparent), + radial-gradient(2px 2px at 448px 55px, rgba(255,255,255,0.3), transparent), + radial-gradient(1px 1px at 485px 38px, #fff, transparent), + radial-gradient(1px 1px at 522px 72px, rgba(255,255,255,0.7), transparent), + radial-gradient(3px 3px at 558px 28px, rgba(255,255,255,0.5), transparent), + radial-gradient(1px 1px at 595px 52px, #fff, transparent), + /* 第二大区域 - 更随机的分布 */ + radial-gradient(2px 2px at 22px 78px, rgba(255,255,255,0.45), transparent), + radial-gradient(1px 1px at 68px 92px, #fff, transparent), + radial-gradient(1px 1px at 108px 85px, rgba(255,255,255,0.55), transparent), + radial-gradient(2px 2px at 145px 98px, rgba(255,255,255,0.35), transparent), + radial-gradient(1px 1px at 182px 88px, #fff, transparent), + radial-gradient(1px 1px at 218px 75px, rgba(255,255,255,0.65), transparent), + radial-gradient(2px 2px at 255px 95px, rgba(255,255,255,0.45), transparent), + radial-gradient(1px 1px at 292px 82px, #fff, transparent), + radial-gradient(1px 1px at 328px 98px, rgba(255,255,255,0.75), transparent), + radial-gradient(2px 2px at 365px 78px, rgba(255,255,255,0.5), transparent), + radial-gradient(1px 1px at 402px 92px, #fff, transparent), + radial-gradient(1px 1px at 438px 85px, rgba(255,255,255,0.4), transparent), + radial-gradient(2px 2px at 475px 95px, rgba(255,255,255,0.6), transparent), + radial-gradient(1px 1px at 512px 78px, #fff, transparent), + radial-gradient(1px 1px at 548px 88px, rgba(255,255,255,0.8), transparent), + radial-gradient(1px 1px at 585px 82px, rgba(255,255,255,0.3), transparent), + /* 第三大区域 - 散落的小星星 */ + radial-gradient(1px 1px at 15px 108px, rgba(255,255,255,0.25), transparent), + radial-gradient(1px 1px at 52px 118px, rgba(255,255,255,0.15), transparent), + radial-gradient(1px 1px at 88px 112px, rgba(255,255,255,0.35), transparent), + radial-gradient(1px 1px at 125px 105px, rgba(255,255,255,0.2), transparent), + radial-gradient(1px 1px at 162px 120px, rgba(255,255,255,0.4), transparent), + radial-gradient(1px 1px at 198px 108px, rgba(255,255,255,0.3), transparent), + radial-gradient(1px 1px at 235px 115px, rgba(255,255,255,0.5), transparent), + radial-gradient(1px 1px at 272px 102px, rgba(255,255,255,0.2), transparent), + radial-gradient(1px 1px at 308px 120px, rgba(255,255,255,0.35), transparent), + radial-gradient(1px 1px at 345px 108px, rgba(255,255,255,0.45), transparent), + radial-gradient(1px 1px at 382px 115px, rgba(255,255,255,0.15), transparent), + radial-gradient(1px 1px at 418px 102px, rgba(255,255,255,0.25), transparent), + radial-gradient(1px 1px at 455px 112px, rgba(255,255,255,0.4), transparent), + radial-gradient(1px 1px at 492px 105px, rgba(255,255,255,0.3), transparent), + radial-gradient(1px 1px at 528px 118px, rgba(255,255,255,0.5), transparent), + radial-gradient(1px 1px at 565px 108px, rgba(255,255,255,0.2), transparent), + radial-gradient(1px 1px at 602px 115px, rgba(255,255,255,0.35), transparent), + /* 额外的亮点 */ + radial-gradient(2px 2px at 35px 55px, rgba(255,255,255,0.6), transparent), + radial-gradient(3px 3px at 175px 35px, rgba(255,255,255,0.8), transparent), + radial-gradient(2px 2px at 315px 68px, rgba(255,255,255,0.7), transparent), + radial-gradient(2px 2px at 455px 25px, rgba(255,255,255,0.5), transparent), + radial-gradient(1px 1px at 535px 42px, rgba(255,255,255,0.9), transparent), + radial-gradient(2px 2px at 75px 68px, rgba(255,255,255,0.65), transparent), + radial-gradient(1px 1px at 205px 82px, rgba(255,255,255,0.75), transparent), + radial-gradient(3px 3px at 385px 48px, rgba(255,255,255,0.55), transparent), + radial-gradient(1px 1px at 505px 72px, rgba(255,255,255,0.85), transparent), + radial-gradient(2px 2px at 625px 15px, rgba(255,255,255,0.4), transparent), + radial-gradient(1px 1px at 652px 58px, rgba(255,255,255,0.7), transparent), + radial-gradient(2px 2px at 688px 32px, rgba(255,255,255,0.6), transparent), + radial-gradient(1px 1px at 725px 45px, rgba(255,255,255,0.8), transparent), + radial-gradient(2px 2px at 762px 62px, rgba(255,255,255,0.5), transparent), + radial-gradient(1px 1px at 798px 18px, rgba(255,255,255,0.65), transparent), + radial-gradient(1px 1px at 835px 75px, rgba(255,255,255,0.45), transparent), + radial-gradient(2px 2px at 872px 42px, rgba(255,255,255,0.7), transparent), + radial-gradient(1px 1px at 908px 28px, rgba(255,255,255,0.55), transparent), + radial-gradient(2px 2px at 945px 65px, rgba(255,255,255,0.8), transparent), + radial-gradient(1px 1px at 982px 38px, rgba(255,255,255,0.4), transparent); + background-repeat: repeat; + background-size: 1000px 130px; + animation: twinkle 3s ease-in-out infinite alternate, drift 20s linear infinite; + pointer-events: none; + z-index: 0; +} + +@keyframes twinkle { + 0% { opacity: 0.4; } + 33% { opacity: 0.7; } + 66% { opacity: 0.5; } + 100% { opacity: 0.8; } +} + +@keyframes drift { + 0% { transform: translate(0, 0); } + 100% { transform: translate(-10px, -5px); } +} + +.galaxy-canvas-wrapper canvas { + width: 100%; + height: 100%; + display: block; + position: relative; + z-index: 1; +} + +.galaxy-tip { + position: absolute; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + background: rgba(0, 0, 0, 0.6); + color: rgba(255, 255, 255, 0.8); + padding: 8px 16px; + border-radius: 20px; + font-size: 12px; + pointer-events: none; + opacity: 0; + transition: opacity 0.3s ease; +} + +.galaxy-canvas-wrapper.active .galaxy-tip { + opacity: 1; + animation: fadeInOut 3s ease-in-out forwards; +} + +@keyframes fadeInOut { + 0%, 100% { opacity: 0; } + 20%, 80% { opacity: 1; } +} + +/* 星空模式独立按钮(旧版本,保留备用) */ +.galaxy-mode-btn { + background: transparent; + border: none; + padding: 8px 12px; + cursor: pointer; + display: none; + flex-direction: column; + align-items: center; + gap: 4px; + transition: all 0.3s ease; + border-radius: 8px; + position: relative; + margin-top: 8px; +} + +.galaxy-mode-btn:hover { + background: rgba(255, 215, 0, 0.1); + transform: scale(1.1); +} + +.galaxy-mode-btn svg { + transition: transform 0.3s ease; +} + +.galaxy-mode-btn:hover svg { + transform: rotate(15deg); +} + +/* 星空预览容器 */ +.galaxy-container { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(135deg, #0a0a2e 0%, #16162e 50%, #0d0d3d 100%); + z-index: 10000; + flex-direction: column; +} + +.galaxy-container.active { + display: flex; + animation: galaxyFadeIn 0.5s ease; +} + +@keyframes galaxyFadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +/* 星空头部 */ +.galaxy-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 24px; + background: rgba(0, 0, 0, 0.4); + backdrop-filter: blur(10px); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + z-index: 10001; +} + +.galaxy-header h2 { + color: #FFD700; + font-size: 20px; + margin: 0; + text-shadow: 0 0 10px rgba(255, 215, 0, 0.5); +} + +.galaxy-controls { + display: flex; + align-items: center; + gap: 16px; +} + +.galaxy-tip { + color: rgba(255, 255, 255, 0.6); + font-size: 12px; +} + +.galaxy-close-btn { + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + color: #fff; + font-size: 24px; + width: 36px; + height: 36px; + cursor: pointer; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s ease; +} + +.galaxy-close-btn:hover { + background: rgba(255, 0, 0, 0.5); + border-color: rgba(255, 0, 0, 0.6); + transform: rotate(90deg); +} + +/* 星空画布 */ +#galaxy-canvas { + flex: 1; + cursor: grab; +} + +#galaxy-canvas:active { + cursor: grabbing; +} + +/* 星星点击提示 */ +.star-tooltip { + position: fixed; + background: rgba(0, 0, 0, 0.88); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 10px; + padding: 14px 18px; + pointer-events: none; + z-index: 10002; + opacity: 0; + transition: opacity 0.2s ease, transform 0.2s ease; + max-width: 320px; + backdrop-filter: blur(12px); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); +} + +.star-tooltip.visible { + opacity: 1; + transform: translateY(0); +} + +.star-tooltip img { + width: 100px; + height: 100px; + object-fit: cover; + border-radius: 8px; + border: 2px solid rgba(255, 255, 255, 0.3); +} + +.star-tooltip p { + color: rgba(255, 255, 255, 0.9); + margin: 10px 0 0; + font-size: 12px; + text-align: center; + letter-spacing: 0.5px; +} + +/* 星星放大弹窗 */ +.star-lightbox { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.92); + z-index: 10003; + align-items: center; + justify-content: center; + flex-direction: column; + backdrop-filter: blur(10px); +} + +.star-lightbox.active { + display: flex; + animation: lightboxFadeIn 0.25s ease; +} + +@keyframes lightboxFadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +.star-lightbox-container { + position: relative; + max-width: 90vw; + max-height: 85vh; + display: flex; + flex-direction: column; + align-items: center; +} + +.star-lightbox img { + max-width: 90vw; + max-height: 80vh; + object-fit: contain; + border-radius: 12px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.1); + animation: imageZoomIn 0.25s ease; +} + +@keyframes imageZoomIn { + from { + transform: scale(0.9); + opacity: 0; + } + to { + transform: scale(1); + opacity: 1; + } +} + +.star-lightbox-close { + position: absolute; + top: -50px; + right: -10px; + background: rgba(255, 255, 255, 0.15); + border: 1px solid rgba(255, 255, 255, 0.2); + color: #fff; + font-size: 24px; + width: 40px; + height: 40px; + cursor: pointer; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s ease; + backdrop-filter: blur(10px); +} + +.star-lightbox-close:hover { + background: rgba(255, 255, 255, 0.25); + transform: rotate(90deg); + border-color: rgba(255, 255, 255, 0.4); +} + +.star-lightbox-info { + color: rgba(255, 255, 255, 0.9); + font-size: 14px; + margin-top: 16px; + padding: 8px 16px; + background: rgba(255, 255, 255, 0.1); + border-radius: 20px; + backdrop-filter: blur(10px); + letter-spacing: 0.5px; +} + +.star-lightbox-nav { + position: absolute; + top: 50%; + transform: translateY(-50%); + background: rgba(255, 255, 255, 0.15); + border: 1px solid rgba(255, 255, 255, 0.2); + color: #fff; + font-size: 24px; + width: 50px; + height: 50px; + cursor: pointer; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s ease; + backdrop-filter: blur(10px); +} + +.star-lightbox-nav:hover { + background: rgba(255, 255, 255, 0.25); + border-color: rgba(255, 255, 255, 0.4); +} + +.star-lightbox-nav.prev { + left: -70px; +} + +.star-lightbox-nav.next { + right: -70px; +} + +.star-lightbox-counter { + color: rgba(255, 255, 255, 0.6); + font-size: 12px; + margin-top: 10px; +} + +/* 加载动画 */ +.galaxy-loading { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: #FFD700; + font-size: 18px; + text-shadow: 0 0 20px rgba(255, 215, 0, 0.5); + animation: pulse 1.5s infinite; +} + +@keyframes pulse { + 0%, 100% { + opacity: 0.6; + } + 50% { + opacity: 1; + } +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .galaxy-header { + padding: 12px 16px; + } + + .galaxy-header h2 { + font-size: 16px; + } + + .galaxy-tip { + display: none; + } + + .star-lightbox img { + max-width: 95%; + } +} diff --git a/static/img/photos/photos.json b/static/img/photos/photos.json index de417e4..012ddf2 100644 --- a/static/img/photos/photos.json +++ b/static/img/photos/photos.json @@ -23,20 +23,7 @@ "2799e89f4632184d151941741b59257f.jpg", "4c5a2a39e6ae9e01bf2ea9860ae8e41f.jpg", "3e0ad66c001c9894c26eac4b98601c73.jpg", - "20955b34b4cbc661f3ec7ff2eda0b815.jpg", - "1 (7).jpg", - "1 (11).jpg", - "1 (9).jpg", - "1 (6).jpg", - "1 (5).jpg", - "1 (4).jpg", - "1 (3).jpg", - "1 (2).jpg", - "1 (1).jpg", - "1 (8).jpg", - "1 (13).jpg", - "1 (10).jpg", - "1 (12).jpg" + "20955b34b4cbc661f3ec7ff2eda0b815.jpg" ], "total": 37, "lastUpdated": "2026-05-18 02:36:56" diff --git a/static/js/galaxy.js b/static/js/galaxy.js new file mode 100644 index 0000000..2afca77 --- /dev/null +++ b/static/js/galaxy.js @@ -0,0 +1,948 @@ +// ==================== 星空预览功能 ==================== +(function() { + // DOM 元素 + const modeSwitch = document.getElementById('mode-switch'); + const photoAlbumModal = document.getElementById('photo-album-full-modal'); + const photoAlbumBody = document.querySelector('.photo-album-body'); + const galaxyCanvasWrapper = document.getElementById('galaxy-canvas-wrapper'); + const galaxyCanvas = document.getElementById('galaxy-canvas'); + const guestbookHeader = document.querySelector('#photo-album-full-modal .guestbook-header'); + + // Three.js 变量 + let scene, camera, renderer; + let stars = []; + let galaxyGroups = []; // 多个银河系组 + let mouse = { x: 0, y: 0, isDragging: false }; + let targetRotation = { x: 0, y: 0 }; + let currentRotation = { x: 0, y: 0 }; + let cameraDistance = 1000; + let cameraTargetDistance = 1000; + let photoData = []; + let raycaster, mousePosition; + let hoveredStar = null; + let tooltip = null; + let isGalaxyMode = false; + let isWaterfallMode = false; + let lastMode = 'galaxy'; // 保存用户最后选择的模式 + let animationId = null; + let currentLightboxIndex = -1; + let galaxyRotations = []; // 多个银河系的自旋角度 + + // 初始化 + function init() { + if (!modeSwitch || !photoAlbumModal) return; + + // 绑定事件 + modeSwitch.addEventListener('click', toggleMode); + + // 绑定模式标签点击事件 + document.querySelectorAll('.mode-label').forEach(label => { + label.addEventListener('click', (e) => { + e.stopPropagation(); + const mode = label.dataset.mode; + switchMode(mode); + }); + }); + + // 创建提示元素 + createTooltip(); + + // 页面加载完成后,检查弹窗状态并初始化模式 + checkAndInitMode(); + } + + // 检查并初始化模式 + function checkAndInitMode() { + // 如果弹窗已经打开,初始化星空模式 + if (photoAlbumModal.classList.contains('active')) { + initDefaultMode(); + } else { + // 否则等待弹窗打开 + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.attributeName === 'class') { + if (photoAlbumModal.classList.contains('active')) { + initDefaultMode(); + observer.disconnect(); // 只执行一次 + } + } + }); + }); + + observer.observe(photoAlbumModal, { + attributes: true, + attributeFilter: ['class'] + }); + } + } + + // 初始化默认模式 + async function initDefaultMode() { + if (isGalaxyMode || isWaterfallMode) return; // 已经激活了模式 + + // 切换到用户最后选择的模式(默认星空模式) + await switchMode(lastMode); + } + + // 创建提示元素 + function createTooltip() { + tooltip = document.createElement('div'); + tooltip.className = 'star-tooltip'; + tooltip.innerHTML = ` + star-preview +

+ `; + document.body.appendChild(tooltip); + } + + // 切换模式 + function toggleMode() { + if (isGalaxyMode) { + // 当前是星空模式,切换到瀑布模式 + switchMode('waterfall'); + } else if (isWaterfallMode) { + // 当前是瀑布模式,切换到星空模式 + switchMode('galaxy'); + } else { + // 默认打开星空模式 + switchMode('galaxy'); + } + } + + // 切换到指定模式 + async function switchMode(mode) { + // 保存用户选择的模式 + lastMode = mode; + + // 关闭所有模式 + closeAllModes(); + + // 加载照片数据 + await loadPhotoData(); + + // 更新UI状态 + document.querySelectorAll('.mode-label').forEach(label => { + label.classList.remove('active'); + if (label.dataset.mode === mode) { + label.classList.add('active'); + } + }); + + // 切换header样式 + if (guestbookHeader) { + if (mode === 'galaxy') { + guestbookHeader.classList.add('galaxy-mode-header'); + } else { + guestbookHeader.classList.remove('galaxy-mode-header'); + } + } + + if (mode === 'galaxy') { + // 打开星空模式 + photoAlbumBody.style.display = 'none'; + galaxyCanvasWrapper.classList.add('active'); + + // 初始化Three.js场景 + initScene(); + + // 创建星空 + createGalaxy(); + + // 开始动画 + animate(); + + isGalaxyMode = true; + } else if (mode === 'waterfall') { + // 瀑布模式 - 使用原来的photo-album-body + // photo-album-body已经默认显示,不需要额外操作 + isWaterfallMode = true; + } + } + + // 关闭所有模式 + function closeAllModes() { + // 关闭星空模式 + if (isGalaxyMode) { + galaxyCanvasWrapper.classList.remove('active'); + cleanupScene(); + isGalaxyMode = false; + } + + // 关闭瀑布模式 + if (isWaterfallMode) { + isWaterfallMode = false; + } + + // 恢复照片显示 + photoAlbumBody.style.display = ''; + + // 隐藏提示 + if (tooltip) { + tooltip.classList.remove('visible'); + } + } + + // 加载照片数据 + async function loadPhotoData() { + try { + const response = await fetch('./static/img/photos/photos.json'); + if (!response.ok) { + throw new Error('Failed to load photos.json'); + } + + const data = await response.json(); + const photoFiles = data.photos || []; + + photoData = photoFiles.map((filename, index) => ({ + id: index + 1, + src: `./static/img/photos/${encodeURIComponent(filename)}`, + filename: filename + })); + } catch (error) { + console.error('加载照片失败:', error); + photoData = []; + } + } + + // 初始化Three.js场景 + function initScene() { + // 创建场景 + scene = new THREE.Scene(); + + // 创建多个银河系组 + galaxyGroups = []; + galaxyRotations = []; + + // 创建9个银河系(合理距离,多种角度) + const galaxyPositions = [ + // 中心主银河系 + { x: 0, y: 0, z: 0, scale: 1.0, rotX: 0, rotZ: 0 }, + // 前面银河系(z正方向) + { x: 500, y: 200, z: 600, scale: 0.7, rotX: 0.1, rotZ: -0.05 }, + { x: -400, y: 350, z: 500, scale: 0.65, rotX: -0.08, rotZ: 0.12 }, + { x: 300, y: -250, z: 700, scale: 0.6, rotX: 0.15, rotZ: -0.1 }, + // 近距离(600-900单位) + { x: 700, y: 200, z: -500, scale: 0.75, rotX: 0.1, rotZ: -0.05 }, + { x: -600, y: -350, z: -400, scale: 0.7, rotX: -0.08, rotZ: 0.12 }, + // 中距离(1000-1500单位) + { x: 1200, y: 500, z: -1000, scale: 0.6, rotX: 0.15, rotZ: -0.1 }, + { x: -1100, y: 400, z: -900, scale: 0.55, rotX: -0.12, rotZ: 0.08 }, + { x: 400, y: -900, z: -800, scale: 0.65, rotX: 0.05, rotZ: 0.15 }, + ]; + + galaxyPositions.forEach((pos, index) => { + const galaxyGroup = new THREE.Group(); + galaxyGroup.position.set(pos.x, pos.y, pos.z); + galaxyGroup.scale.set(pos.scale, pos.scale, pos.scale); + + // 设置不同的角度 + galaxyGroup.rotation.x = pos.rotX; + galaxyGroup.rotation.z = pos.rotZ; + + scene.add(galaxyGroup); + galaxyGroups.push(galaxyGroup); + galaxyRotations.push(0); + }); + + // 获取容器尺寸 + const containerRect = galaxyCanvasWrapper.getBoundingClientRect(); + + // 创建相机 + camera = new THREE.PerspectiveCamera( + 75, + containerRect.width / containerRect.height, + 1, + 5000 // 调整远裁剪面 + ); + camera.position.z = cameraDistance; + + // 创建渲染器 + renderer = new THREE.WebGLRenderer({ + canvas: galaxyCanvas, + antialias: true, + alpha: true + }); + renderer.setSize(containerRect.width, containerRect.height); + renderer.setPixelRatio(window.devicePixelRatio); + + // 添加环境光 + const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); + scene.add(ambientLight); + + // 添加点光源 + const pointLight = new THREE.PointLight(0xFFD700, 1, 5000); + pointLight.position.set(0, 0, 1000); + scene.add(pointLight); + + // 创建射线检测器 + raycaster = new THREE.Raycaster(); + mousePosition = new THREE.Vector2(); + + // 绑定事件 + bindEvents(); + } + + // 创建星空 + function createGalaxy() { + // 为每个银河系创建星星 + galaxyGroups.forEach((galaxyGroup, index) => { + if (index === 0) { + // 主银河系 - 有照片星星 + if (photoData.length > 0) { + photoData.forEach((photo, i) => { + createPhotoStar(photo, i, galaxyGroup); + }); + } + + // 添加装饰性星星 + addDecorativeStars(galaxyGroup, 1, index); + } else { + // 其他银河系 - 只有装饰性星星,使用不同的配色 + addDecorativeStars(galaxyGroup, 0.7, index); // 稍微小一些,传入银河系索引 + } + }); + } + + // 创建照片星星 + function createPhotoStar(photo, index, galaxyGroup) { + // 创建圆形纹理 + const textureLoader = new THREE.TextureLoader(); + const texture = textureLoader.load(photo.src); + + // 创建正方形几何体 + const geometry = new THREE.PlaneGeometry(30, 30); + + // 创建材质 - 无滤镜 + const material = new THREE.MeshBasicMaterial({ + map: texture, + transparent: true, + opacity: 0.95, + side: THREE.DoubleSide + }); + + // 创建网格 + const star = new THREE.Mesh(geometry, material); + + // 随机位置(银河系分布) + const radius = 200 + Math.random() * 600; + const theta = Math.random() * Math.PI * 2; + const phi = (Math.random() - 0.5) * Math.PI * 0.3; // 扁平分布 + + star.position.x = radius * Math.cos(theta) * Math.cos(phi); + star.position.y = radius * Math.sin(phi) * 0.3; // 更扁平 + star.position.z = radius * Math.sin(theta) * Math.cos(phi); + + // 存储照片信息 + star.userData = { + photoId: photo.id, + photoSrc: photo.src, + photoFilename: photo.filename + }; + + // 面向相机 + star.lookAt(camera.position); + + galaxyGroup.add(star); + stars.push(star); + } + + // 创建默认星星 + function createDefaultStars() { + for (let i = 0; i < 100; i++) { + const geometry = new THREE.CircleGeometry(2, 32); + const material = new THREE.MeshBasicMaterial({ + color: 0xFFD700, + transparent: true, + opacity: 0.7 + }); + const star = new THREE.Mesh(geometry, material); + + // 随机位置 + const radius = 100 + Math.random() * 800; + const theta = Math.random() * Math.PI * 2; + const phi = (Math.random() - 0.5) * Math.PI; + + star.position.x = radius * Math.cos(theta) * Math.cos(phi); + star.position.y = radius * Math.sin(phi); + star.position.z = radius * Math.sin(theta) * Math.cos(phi); + + galaxyGroup.add(star); + stars.push(star); + } + } + + // 添加装饰性星星(银河系效果) + function addDecorativeStars(galaxyGroup, scale = 1, galaxyIndex = 0) { + const starCount = Math.floor(4000 * scale); // 根据缩放调整星星数量 + const starGeometry = new THREE.BufferGeometry(); + const positions = new Float32Array(starCount * 3); + const colors = new Float32Array(starCount * 3); + const sizes = new Float32Array(starCount); + + // 银河系参数 + const bulgeRadius = 120; // 银核半径 + const diskRadius = 700; // 银盘半径 + const armCount = 4; // 旋臂数量 + const spiralAngle = 12; // 螺旋角(度) + const spiralTightness = 0.005; // 螺旋紧密度 + + // 根据银河系索引选择配色方案 + const colorScheme = galaxyIndex === 0 ? 'warm' : getColorScheme(galaxyIndex); + + // 获取银河系配色方案 + function getColorScheme(index) { + const schemes = [ + 'blue', // 蓝色系 + 'purple', // 紫色系 + 'cyan', // 青色系 + 'pink', // 粉色系 + 'red', // 红色系 + 'green', // 绿色系 + 'orange', // 橙色系 + 'white', // 白色系 + ]; + return schemes[(index - 1) % schemes.length]; + } + + // 淡化颜色(向白色靠拢) + function fadeColor(color, fadeAmount = 0.4) { + return [ + color[0] + (1 - color[0]) * fadeAmount, + color[1] + (1 - color[1]) * fadeAmount, + color[2] + (1 - color[2]) * fadeAmount + ]; + } + + // 根据配色方案获取颜色 + function getColor(scheme, brightness, distanceFromCenter) { + let color; + + switch (scheme) { + case 'warm': // 主银河系 - 暖黄色(不淡化) + if (distanceFromCenter < 0.3) { + color = [1, 0.9 + brightness * 0.1, 0.7 + brightness * 0.3]; + } else if (distanceFromCenter < 0.5) { + color = [1, 0.8 + Math.random() * 0.15, 0.4 + Math.random() * 0.3]; + } else { + color = [0.9 + Math.random() * 0.1, 0.85 + Math.random() * 0.1, 0.6 + Math.random() * 0.2]; + } + return color; // 主银河系不淡化 + + case 'blue': // 蓝色系(淡化) + if (distanceFromCenter < 0.3) { + color = [0.3, 0.5 + brightness * 0.3, 0.9 + brightness * 0.1]; + } else if (distanceFromCenter < 0.5) { + color = [0.2 + Math.random() * 0.1, 0.4 + Math.random() * 0.2, 0.8 + Math.random() * 0.15]; + } else { + color = [0.1 + Math.random() * 0.15, 0.3 + Math.random() * 0.2, 0.7 + Math.random() * 0.2]; + } + return fadeColor(color, 0.45); + + case 'purple': // 紫色系(淡化) + if (distanceFromCenter < 0.3) { + color = [0.7 + brightness * 0.3, 0.3, 0.9 + brightness * 0.1]; + } else if (distanceFromCenter < 0.5) { + color = [0.6 + Math.random() * 0.2, 0.2 + Math.random() * 0.1, 0.8 + Math.random() * 0.15]; + } else { + color = [0.5 + Math.random() * 0.2, 0.1 + Math.random() * 0.15, 0.7 + Math.random() * 0.2]; + } + return fadeColor(color, 0.45); + + case 'cyan': // 青色系(淡化) + if (distanceFromCenter < 0.3) { + color = [0.2, 0.8 + brightness * 0.2, 0.9 + brightness * 0.1]; + } else if (distanceFromCenter < 0.5) { + color = [0.1 + Math.random() * 0.1, 0.7 + Math.random() * 0.15, 0.8 + Math.random() * 0.15]; + } else { + color = [0.05 + Math.random() * 0.1, 0.6 + Math.random() * 0.2, 0.7 + Math.random() * 0.2]; + } + return fadeColor(color, 0.45); + + case 'pink': // 粉色系(淡化) + if (distanceFromCenter < 0.3) { + color = [0.9 + brightness * 0.1, 0.4 + brightness * 0.2, 0.6 + brightness * 0.2]; + } else if (distanceFromCenter < 0.5) { + color = [0.8 + Math.random() * 0.15, 0.3 + Math.random() * 0.15, 0.5 + Math.random() * 0.2]; + } else { + color = [0.7 + Math.random() * 0.2, 0.2 + Math.random() * 0.15, 0.4 + Math.random() * 0.2]; + } + return fadeColor(color, 0.45); + + case 'red': // 红色系(淡化) + if (distanceFromCenter < 0.3) { + color = [0.9 + brightness * 0.1, 0.3, 0.3]; + } else if (distanceFromCenter < 0.5) { + color = [0.8 + Math.random() * 0.15, 0.2 + Math.random() * 0.1, 0.2 + Math.random() * 0.1]; + } else { + color = [0.7 + Math.random() * 0.2, 0.1 + Math.random() * 0.15, 0.1 + Math.random() * 0.15]; + } + return fadeColor(color, 0.45); + + case 'green': // 绿色系(淡化) + if (distanceFromCenter < 0.3) { + color = [0.3, 0.8 + brightness * 0.2, 0.4]; + } else if (distanceFromCenter < 0.5) { + color = [0.2 + Math.random() * 0.1, 0.7 + Math.random() * 0.15, 0.3 + Math.random() * 0.1]; + } else { + color = [0.1 + Math.random() * 0.15, 0.6 + Math.random() * 0.2, 0.2 + Math.random() * 0.15]; + } + return fadeColor(color, 0.45); + + case 'orange': // 橙色系(淡化) + if (distanceFromCenter < 0.3) { + color = [0.9 + brightness * 0.1, 0.6 + brightness * 0.2, 0.2]; + } else if (distanceFromCenter < 0.5) { + color = [0.8 + Math.random() * 0.15, 0.5 + Math.random() * 0.15, 0.15 + Math.random() * 0.1]; + } else { + color = [0.7 + Math.random() * 0.2, 0.4 + Math.random() * 0.2, 0.1 + Math.random() * 0.15]; + } + return fadeColor(color, 0.45); + + case 'white': // 白色系(已经是淡色,稍微淡化) + if (distanceFromCenter < 0.3) { + color = [0.9 + brightness * 0.1, 0.9 + brightness * 0.1, 0.9 + brightness * 0.1]; + } else if (distanceFromCenter < 0.5) { + color = [0.8 + Math.random() * 0.1, 0.8 + Math.random() * 0.1, 0.8 + Math.random() * 0.1]; + } else { + color = [0.7 + Math.random() * 0.15, 0.7 + Math.random() * 0.15, 0.7 + Math.random() * 0.15]; + } + return fadeColor(color, 0.3); // 白色系淡化程度低一些 + + default: + color = [1, 1, 1]; + return color; + } + } + + for (let i = 0; i < starCount; i++) { + let x, y, z, brightness; + + // 根据概率分配 + const rand = Math.random(); + + if (rand < 0.15) { + // ==================== 银核(15%)==================== + // 非常明亮的圆球区域,大量星星密集分布 + + // 使用球形分布,中心密度最高 + const r = Math.pow(Math.random(), 0.2) * bulgeRadius; // 高度集中在中心 + const theta = Math.random() * Math.PI * 2; + const phi = Math.acos(2 * Math.random() - 1); // 均匀的球形分布 + + x = r * Math.sin(phi) * Math.cos(theta); + y = r * Math.cos(phi) * 0.5; // 稍微扁平一些 + z = r * Math.sin(phi) * Math.sin(theta); + + // 银核非常亮,中心最亮 + brightness = 0.85 + Math.pow(1 - r / bulgeRadius, 2) * 0.15; + sizes[i] = 3 + Math.random() * 4; // 大尺寸的明亮星星 + + } else if (rand < 0.85) { + // ==================== 旋臂(70%)==================== + // 星星严格分布在旋臂上 + + // 选择一条旋臂 + const armIndex = Math.floor(Math.random() * armCount); + const armBaseAngle = (armIndex / armCount) * Math.PI * 2; + + // 从银核向外延伸的距离 + const distance = bulgeRadius + Math.pow(Math.random(), 0.6) * (diskRadius - bulgeRadius); + + // 螺旋线角度 + const spiralTheta = armBaseAngle + spiralTightness * distance * Math.log(1 + distance / bulgeRadius); + + // 旋臂宽度(更粗壮) + const armWidth = 880 + distance * 0.12; + + // 高斯分布让星星集中在旋臂中心 + const gaussianRandom = (Math.random() + Math.random() + Math.random()) / 3 - 0.5; + const offset = gaussianRandom * armWidth * 0.5; + + // 计算位置 + const angle = spiralTheta; + x = distance * Math.cos(angle) + offset * Math.cos(angle + Math.PI / 2); + z = distance * Math.sin(angle) + offset * Math.sin(angle + Math.PI / 2); + + // 高度分布(扁平) + y = (Math.random() - 0.5) * 15 * (distance / diskRadius); + + // 旋臂较亮 + brightness = Math.max(0.6, 1 - distance / diskRadius * 0.3); + sizes[i] = 1.5 + Math.random() * 2; + + } else { + // ==================== 背景星星(30%)==================== + // 随机分布,但避开旋臂区域(相对稀疏) + + const distance = bulgeRadius + Math.random() * (diskRadius - bulgeRadius); + const theta = Math.random() * Math.PI * 2; + + x = distance * Math.cos(theta); + z = distance * Math.sin(theta); + y = (Math.random() - 0.5) * 50; // 扁平分布 + + // 背景较暗 + brightness = Math.max(0.2, 0.5 - distance / diskRadius * 0.3); + sizes[i] = 0.5 + Math.random() * 1; + } + + positions[i * 3] = x; + positions[i * 3 + 1] = y; + positions[i * 3 + 2] = z; + + // 设置颜色(根据配色方案) + const distanceFromCenter = Math.sqrt(x * x + y * y + z * z) / bulgeRadius; + const color = getColor(colorScheme, brightness, distanceFromCenter); + colors[i * 3] = color[0]; + colors[i * 3 + 1] = color[1]; + colors[i * 3 + 2] = color[2]; + + // 背景星星用冷色调(除了主银河系) + if (rand >= 0.70 && galaxyIndex !== 0) { + colors[i * 3] *= 0.5; + colors[i * 3 + 1] *= 0.5; + colors[i * 3 + 2] *= 0.5; + } + + sizes[i] *= brightness; + } + + starGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); + starGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3)); + + const starMaterial = new THREE.PointsMaterial({ + size: 2.5, + vertexColors: true, + transparent: true, + opacity: 0.9, + sizeAttenuation: true, + blending: THREE.AdditiveBlending, + depthWrite: false + }); + + const starField = new THREE.Points(starGeometry, starMaterial); + galaxyGroup.add(starField); + + // 添加银核发光球体 + addGalacticCore(galaxyGroup, scale); + + // 添加星云效果 + addNebulaEffect(galaxyGroup, scale); + } + + // 添加银核发光球体 + function addGalacticCore(galaxyGroup, scale = 1) { + // 创建一个明亮的发光球体作为银核 + const coreGeometry = new THREE.SphereGeometry(80, 32, 32); + const coreMaterial = new THREE.MeshBasicMaterial({ + color: 0xFFF5E1, // 暖白色 + transparent: true, + opacity: 0.3, + side: THREE.DoubleSide + }); + const coreMesh = new THREE.Mesh(coreGeometry, coreMaterial); + galaxyGroup.add(coreMesh); + + // 添加第二层发光(更亮更小) + const innerCoreGeometry = new THREE.SphereGeometry(40, 32, 32); + const innerCoreMaterial = new THREE.MeshBasicMaterial({ + color: 0xFFFBE6, // 更亮的白色 + transparent: true, + opacity: 0.5, + side: THREE.DoubleSide + }); + const innerCoreMesh = new THREE.Mesh(innerCoreGeometry, innerCoreMaterial); + galaxyGroup.add(innerCoreMesh); + + // 添加核心光点(最亮) + const coreLightGeometry = new THREE.SphereGeometry(15, 32, 32); + const coreLightMaterial = new THREE.MeshBasicMaterial({ + color: 0xFFFFF0, // 纯白偏黄 + transparent: true, + opacity: 0.7, + side: THREE.DoubleSide + }); + const coreLightMesh = new THREE.Mesh(coreLightGeometry, coreLightMaterial); + galaxyGroup.add(coreLightMesh); + } + + // 添加星云效果 + function addNebulaEffect(galaxyGroup, scale = 1) { + const nebulaCount = Math.floor(80 * scale); // 根据缩放调整数量 + const nebulaGeometry = new THREE.BufferGeometry(); + const nebulaPositions = new Float32Array(nebulaCount * 3); + const nebulaColors = new Float32Array(nebulaCount * 3); + const nebulaSizes = new Float32Array(nebulaCount); + + for (let i = 0; i < nebulaCount; i++) { + // 主要分布在旋臂区域 + const armIndex = Math.floor(Math.random() * 4); + const armAngle = (armIndex / 4) * Math.PI * 2; + const r = 150 + Math.pow(Math.random(), 0.3) * 500; + + const spiralTightness = 0.12; + const theta = armAngle + spiralTightness * r; + const offset = (Math.random() - 0.5) * 60; + + nebulaPositions[i * 3] = r * Math.cos(theta) + offset * Math.cos(theta + Math.PI / 2); + nebulaPositions[i * 3 + 1] = (Math.random() - 0.5) * 20; + nebulaPositions[i * 3 + 2] = r * Math.sin(theta) + offset * Math.sin(theta + Math.PI / 2); + + // 暖黄色星云(金黄色,与银河系协调) + nebulaColors[i * 3] = 0.9 + Math.random() * 0.1; + nebulaColors[i * 3 + 1] = 0.7 + Math.random() * 0.2; + nebulaColors[i * 3 + 2] = 0.3 + Math.random() * 0.2; + + nebulaSizes[i] = 15 + Math.random() * 25; + } + + nebulaGeometry.setAttribute('position', new THREE.BufferAttribute(nebulaPositions, 3)); + nebulaGeometry.setAttribute('color', new THREE.BufferAttribute(nebulaColors, 3)); + + const nebulaMaterial = new THREE.PointsMaterial({ + size: 25, + vertexColors: true, + transparent: true, + opacity: 0.35, // 提高透明度 + sizeAttenuation: true, + blending: THREE.AdditiveBlending, + depthWrite: false + }); + + const nebulaField = new THREE.Points(nebulaGeometry, nebulaMaterial); + galaxyGroup.add(nebulaField); + } + + // 绑定事件 + function bindEvents() { + // 鼠标按下 + galaxyCanvas.addEventListener('mousedown', onMouseDown); + + // 鼠标移动 + galaxyCanvas.addEventListener('mousemove', onMouseMove); + + // 鼠标释放 + galaxyCanvas.addEventListener('mouseup', onMouseUp); + + // 鼠标离开 + galaxyCanvas.addEventListener('mouseleave', onMouseLeave); + + // 滚轮缩放 + galaxyCanvas.addEventListener('wheel', onMouseWheel); + + // 点击星星 + galaxyCanvas.addEventListener('click', onStarClick); + + // 窗口大小变化 + window.addEventListener('resize', onWindowResize); + } + + // 鼠标按下 + function onMouseDown(e) { + mouse.isDragging = true; + mouse.x = e.clientX; + mouse.y = e.clientY; + } + + // 鼠标移动 + function onMouseMove(e) { + if (mouse.isDragging) { + // 计算旋转角度 + const deltaX = (e.clientX - mouse.x) * 0.005; + const deltaY = (e.clientY - mouse.y) * 0.005; + + targetRotation.y += deltaX; + targetRotation.x += deltaY; + + // 限制垂直旋转角度 + targetRotation.x = Math.max(-Math.PI / 3, Math.min(Math.PI / 3, targetRotation.x)); + + mouse.x = e.clientX; + mouse.y = e.clientY; + } + + // 更新鼠标位置(用于射线检测) + mousePosition.x = (e.clientX / window.innerWidth) * 2 - 1; + mousePosition.y = -(e.clientY / window.innerHeight) * 2 + 1; + + // 检测悬停的星星 + checkHoveredStar(e); + } + + // 鼠标释放 + function onMouseUp() { + mouse.isDragging = false; + } + + // 鼠标离开 + function onMouseLeave() { + mouse.isDragging = false; + } + + // 鼠标滚轮 + function onMouseWheel(e) { + e.preventDefault(); + + // 调整相机距离 + cameraTargetDistance += e.deltaY * 2; + cameraTargetDistance = Math.max(300, Math.min(3000, cameraTargetDistance)); + } + + // 点击星星 + function onStarClick(e) { + // 更新鼠标位置 + mousePosition.x = (e.clientX / window.innerWidth) * 2 - 1; + mousePosition.y = -(e.clientY / window.innerHeight) * 2 + 1; + + // 射线检测 + raycaster.setFromCamera(mousePosition, camera); + const intersects = raycaster.intersectObjects(stars); + + if (intersects.length > 0) { + const clickedStar = intersects[0].object; + + // 检查是否有照片数据 + if (clickedStar.userData && clickedStar.userData.photoSrc) { + openStarLightbox(clickedStar.userData); + } + } + } + + // 检测悬停的星星 + function checkHoveredStar(e) { + raycaster.setFromCamera(mousePosition, camera); + const intersects = raycaster.intersectObjects(stars); + + if (intersects.length > 0) { + const star = intersects[0].object; + + if (star.userData && star.userData.photoSrc) { + hoveredStar = star; + galaxyCanvas.style.cursor = 'pointer'; + + // 显示提示 + if (tooltip) { + tooltip.querySelector('img').src = star.userData.photoSrc; + tooltip.querySelector('p').textContent = `点击放大查看 #${star.userData.photoId}`; + tooltip.style.left = (e.clientX + 20) + 'px'; + tooltip.style.top = (e.clientY - 40) + 'px'; + tooltip.classList.add('visible'); + } + } + } else { + hoveredStar = null; + galaxyCanvas.style.cursor = 'grab'; + + // 隐藏提示 + if (tooltip) { + tooltip.classList.remove('visible'); + } + } + } + + // 打开星星放大查看器(使用瀑布模式的lightbox) + function openStarLightbox(starData) { + // 找到照片在数组中的索引 + const photoIndex = photoData.findIndex(p => p.id === starData.photoId); + if (photoIndex === -1) return; + + // 隐藏tooltip + if (tooltip) { + tooltip.classList.remove('visible'); + } + + // 调用瀑布模式的openPhotoLightbox函数 + if (window.openPhotoLightbox) { + window.openPhotoLightbox(photoIndex); + } + } + + // 窗口大小变化 + function onWindowResize() { + if (camera && renderer && galaxyCanvasWrapper) { + const containerRect = galaxyCanvasWrapper.getBoundingClientRect(); + camera.aspect = containerRect.width / containerRect.height; + camera.updateProjectionMatrix(); + renderer.setSize(containerRect.width, containerRect.height); + } + } + + // 动画循环 + function animate() { + if (!galaxyCanvasWrapper.classList.contains('active')) return; + + animationId = requestAnimationFrame(animate); + + // 平滑旋转(用户拖拽) + currentRotation.x += (targetRotation.x - currentRotation.x) * 0.05; + currentRotation.y += (targetRotation.y - currentRotation.y) * 0.05; + + // 银河系自旋(非常缓慢的旋转) + galaxyGroups.forEach((galaxyGroup, index) => { + galaxyRotations[index] += 0.0001 + index * 0.00005; // 不同的旋转速度 + galaxyGroup.rotation.y = galaxyRotations[index]; + }); + + // 更新相机位置 + const cameraX = cameraDistance * Math.sin(currentRotation.y) * Math.cos(currentRotation.x); + const cameraY = cameraDistance * Math.sin(currentRotation.x); + const cameraZ = cameraDistance * Math.cos(currentRotation.y) * Math.cos(currentRotation.x); + + camera.position.set(cameraX, cameraY, cameraZ); + camera.lookAt(0, 0, 0); + + // 平滑缩放 + cameraDistance += (cameraTargetDistance - cameraDistance) * 0.05; + + // 让照片星星面向相机 + stars.forEach(star => { + if (star.userData && star.userData.photoSrc) { + star.lookAt(camera.position); + } + }); + + // 渲染场景 + renderer.render(scene, camera); + } + + // 清理场景 + function cleanupScene() { + // 停止动画 + if (animationId) { + cancelAnimationFrame(animationId); + animationId = null; + } + + // 移除所有对象 + while (scene && scene.children.length > 0) { + const object = scene.children[0]; + scene.remove(object); + if (object.geometry) object.geometry.dispose(); + if (object.material) { + if (object.material.map) object.material.map.dispose(); + object.material.dispose(); + } + } + + // 清空数组 + stars = []; + galaxyGroups = []; + galaxyRotations = []; + + // 移除渲染器 + if (renderer) { + renderer.dispose(); + renderer = null; + } + + scene = null; + camera = null; + } + + // DOM 加载完成后初始化 + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } +})();