Compare commits

...

3 Commits

Author SHA1 Message Date
DelLevin-Home
1668c84436 添加星空相册 2026-06-05 02:13:42 +08:00
DelLevin-Home
a664c423fa 1 2026-06-05 00:08:46 +08:00
DelLevin-Home
9523db458c 222 2026-05-19 00:38:31 +08:00
22 changed files with 1648 additions and 27 deletions

11
.claude/launch.json Normal file
View File

@@ -0,0 +1,11 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "local",
"runtimeExecutable": "npx",
"runtimeArgs": ["http-server", ".", "-p", "3000", "-c-1"],
"port": 3000
}
]
}

View File

@@ -0,0 +1,7 @@
{
"permissions": {
"allow": [
"mcp__Claude_Preview__preview_start"
]
}
}

View File

@@ -28,6 +28,8 @@
<!-- <script defer src="https://umami.iletter.top/anyjs" data-website-id="ae6cd64c-5900-49c9-9c22-95cedc24a508"></script> -->
<!-- ECharts 库 -->
<script src="./static/js/echarts/echarts.min.js"></script>
<!-- Three.js 用于星空预览 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<!-- 多语言 -->
<script src="./static/js/language/zh.js"></script>
<script src="./static/js/language/en.js"></script>
@@ -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");
</style>
<title data-i18n="page_title">白荼 - BAITU</title>
</head>
@@ -171,10 +174,23 @@
<div class="full-modal-content">
<div class="guestbook-header">
<h2 data-i18n="photo_album_title">旅途剪影</h2>
<button id="photo-album-close" class="guestbook-close" title="关闭">&times;</button>
<div class="header-controls">
<div class="mode-switch-container">
<button id="mode-switch" class="mode-switch-btn" title="切换模式">
<span class="mode-label active" data-mode="galaxy">星空模式</span>
<span class="mode-label" data-mode="waterfall">瀑布模式</span>
</button>
</div>
<button id="photo-album-close" class="guestbook-close" title="关闭">&times;</button>
</div>
</div>
<div class="guestbook-body photo-album-body">
<!-- 照片网格将在这里动态加载 -->
<!-- 照片网格将在这里动态加载(瀑布模式) -->
</div>
<!-- 星空模式画布 -->
<div id="galaxy-canvas-wrapper" class="galaxy-canvas-wrapper">
<canvas id="galaxy-canvas"></canvas>
<div class="galaxy-tip">滚轮缩放 | 拖拽旋转 | 点击星星查看照片</div>
</div>
</div>
</div>
@@ -508,6 +524,7 @@
<script src="./static/js/guestbook.js"></script>
<script src="./static/js/blog_posts.js"></script>
<script src="./static/js/photo_album.js"></script>
<script src="./static/js/galaxy.js"></script>
<script src="./static/js/tabchange.js"></script>
</body>

618
static/css/galaxy.css Normal file
View File

@@ -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%;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 490 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 722 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 473 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 345 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

View File

@@ -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"

948
static/js/galaxy.js Normal file
View File

@@ -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 = `
<img src="" alt="star-preview">
<p></p>
`;
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();
}
})();

View File

@@ -13,14 +13,15 @@ const translationsEN = {
greeting: "",
my_name: "BaiTu",
about_me_p1:
"My name is <strong>BaiTu</strong>, from Hengshui, Hebei, a native northerner. I graduated from an obscure second-tier university with a major in Information Management and Information Systems. I self-studied development technologies like Java, Vue, Linux, MySQL, Python, and some software reverse engineering techniques. I also have basic skills in PS, PR, and AE. I have experience with systems, networking, PLCs, microcontrollers, server maintenance, and managing public accounts and websites. In short, I know a little bit about many things. Others might think I'm an expert, but actually, I'm just a rookie. However, I won't give up and will strive to become an expert. As Liu Lian from NARAKA said, \"The most important skill in the world is perseverance.\"",
"My name is <strong>BaiTu</strong>, from Hengshui, Hebei, a native northerner. I graduated from a second-tier university with a major in Information Management and Information Systems. I self-taught development languages like Java, Vue ecosystem, Python, as well as operations and database technologies like Linux, MySQL, Redis, and Nginx. I also dabble in software reverse engineering, PS, PR, AE production, and other fancy stuff. I've built systems, crimped network cables, worked with PLCs, played with microcontrollers, repaired servers (physically 🤭), operated public accounts, and maintained and managed websites. You could say I know a bit of everything, but just a tiny bit. Others may think I'm a pro, but I'm actually just a noob. Still, I won't give up and will keep striving to become a pro. As Liu Lian from NARAKA said, \"The most important skill in the world is perseverance.\"",
about_me_p2:
'My hobbies usually involve playing games, reading books, watching anime, and I prefer staying indoors. I hate repetitive and simple work, so I often write small tools to make life easier because laziness drives my productivity. I frequently lurk on various forums and blogs, shouting "666" or "Amazing" under tech bloggers. My own blog updates sporadically, mainly recording daily life notes and technical challenges encountered.',
'My hobbies are playing games, reading books, and watching anime — a complete homebody who hates going out. I hate repetitive and simple work, so I often write small tools to slack off. After all, laziness is my primary productivity. I frequently lurk on various forums and blogs, shouting "666" or "Amazing" under tech bloggers. My own blog updates sporadically, mainly recording daily life notes and technical challenges encountered.',
about_me_p3:
'This site follows a minimalist style, using pure HTML. Umami tracks UV/PV at the bottom. The Memos section uses the Memos API. It also employs Baota Cloud WAF protection and nginx caching for static resources. If you are interested, you can directly download the source code from <a href="https://github.com/dellevin/iletter-homepage" target="_blank">https://github.com/dellevin/iletter-homepage </a> and use it, or through F12 view website source code download.',
'This site follows a minimalist letter-style design, using pure HTML. Umami tracks UV/PV at the bottom. The Memos section uses the Memos API. The guestbook is self-built, and blog posts use the Typecho Restful plugin (customized by me). It also employs Baota Cloud WAF protection and nginx caching for static resources. If you are interested, you can directly download the source code from <a href="https://github.com/dellevin/iletter-homepage" target="_blank">https://github.com/dellevin/iletter-homepage </a> and use it, or download by viewing the website source code through F12.',
about_me_p4:
"As a 99er programmer, I am glad to still have great passion for technology after starting work! If you share the same interest in technology, I would love to meet you! Let's progress and grow together!",
// 联系方式
// 联系方式
contact_intro: "Feel free to reach out via the following methods:",
email_label: "Email:",
wechat_label: "WeChat:",

View File

@@ -13,14 +13,13 @@ const translationsZH = {
greeting: "见字如面,展信舒颜。",
my_name: "白荼",
about_me_p1:
"我叫<strong>白荼</strong>,河北衡水人,土生土长的北方汉子,毕业于一所不知名的二本院校。信息管理与信息系统专业。自学java、vue、linux、mysql、python等开发技术和一些软件逆向技术同时还略懂些PS图片、PR视频、AE特效制作。做系统,剪网线,搞过plc,耍过单片机,维护修理过服务器,运营过公众号网站等等。可谓是会,但只会一点点。别人都觉得我是个大佬,其实我就是个小菜鸡。但我不会气馁,努力成为一个大佬的。正如永劫无间刘炼的一句话“天下万般之绝学,莫过于恒心。”",
"我叫<strong>白荼</strong>,河北衡水人,土生土长的北方汉子,毕业于一所二本院校。信息管理与信息系统专业。自学java、vue全家桶、python等开发语言还有linux、mysql、redis、nginx等运维和数据库的技术,同时还略懂些软件逆向,PS、PR、AE制作等其他花里胡哨的东西。做系统,剪网线,搞过PLC,耍过单片机,修过服务器(物理层面🤭),运营过公众号,维护管理过网站等等。可谓是会,但只会一点点。别人都觉得我是个大佬,其实我就是个小菜鸡。但我不会气馁,努力成为一个大佬的。正如永劫无间刘炼的一句话“天下万般之绝学,莫过于恒心。”",
about_me_p2:
'平时的爱好就是玩玩游戏,看看书,刷刷动漫,不爱出门肥宅一个。讨厌重复简单的工作,会想办法偷懒写写自己的顺手的小工具什么的,毕竟懒惰是我的生产力。我也经常混迹于各大网络论坛博客并在各个技术博主下面直呼"大佬666"、"大佬牛牛牛"。自己的博客也在断断续续中更新。主要是记录生活随笔和碰到的技术难题。',
'平时的爱好就是玩玩游戏,看看书,刷刷动漫,一个彻头彻尾的不爱出门肥宅。讨厌重复简单的工作,会想办法偷懒写一下小工具什么的,毕竟懒惰是我的第一生产力。我也经常混迹于各大网络论坛博客并在各个技术博主下面直呼"大佬666"、"大佬牛牛牛"。自己的博客也在断断续续中更新。主要是记录生活随笔和碰到的技术难题。',
about_me_p3:
'本站秉承简约风格采用纯HTMLUmami做网站底部的uv/pv 闲言碎语模块使用memos接口。同时使用堡塔云WAF防护nginx缓存加速网站静态资源。如果您有兴趣可以直接下载本站的 <a href="https://github.com/dellevin/iletter-homepage" target="_blank">https://github.com/dellevin/iletter-homepage </a> 并使用或者通过F12查看网站源代码下载。',
'本站秉承书信形式的简约风格采用纯HTMLUmami做网站底部的uv/pv 闲言碎语模块使用memos接口留言板自己写的博客文章使用的是typecho的Restful插件自己魔改了一下。同时使用堡塔云WAF防护nginx缓存加速网站静态资源。如果您有兴趣可以直接下载本站的 <a href="https://github.com/dellevin/iletter-homepage" target="_blank">https://github.com/dellevin/iletter-homepage </a> 并使用或者通过F12查看网站源代码进行下载。',
about_me_p4:
"作为一个99年的码农很庆幸我在工作之后还对技术有着极大的热情如果你也对技术有着同样的兴趣也很希望认识你一同进步共同成长",
// 联系方式
contact_intro: "欢迎通过以下方式与我联系:",
email_label: "邮箱:",

View File

@@ -168,9 +168,9 @@ const toolsData = [
desc_en: "Who writes a diary, seriously?!",
},
{
name_zh: "Ollama",
desc_zh: "本地部署对话AI微调模型32b是4070遭不住",
name_en: "Ollama",
name_zh: "Ollama / llama.cpp",
desc_zh: "本地部署对话AI微调模型",
name_en: "Ollama / llama.cpp",
desc_en:
"Locally deployed conversational AI, fine-tuning models. 32b is too much for a 4070.",
},
@@ -207,6 +207,39 @@ const toolsData = [
name_en: "QtScrcpy",
desc_en: "LAN phone control tool, remote adb is awesome.",
},
{
name_zh: "Snipaste",
desc_zh: "很厉害的强大且好用的截屏工具",
name_en: "Snipaste",
desc_en: "Very powerful and useful screenshot tool.",
},
{
name_zh: "Redis Desktop Manager",
desc_zh: "超级好用的Redis数据库管理工具",
name_en: "Redis Desktop Manager",
desc_en: "Redis database management tool.",
},
{
name_zh: "Snipaste",
desc_zh: "很厉害的强大且好用的截屏工具",
name_en: "Snipaste",
desc_en: "Very powerful and useful screenshot tool.",
},{
name_zh: "Everything",
desc_zh: "windows搜索文件利器毫秒搜索快速定位",
name_en: "Everything",
desc_en: "windows search file利器millisecond search, quickly locate files",
},{
name_zh: "RaiDrive",
desc_zh: "好用爱用的网盘挂载",
name_en: "RaiDrive",
desc_en: "Good and loved netdisk mount",
},{
name_zh: "Ultimate Vocal Remover",
desc_zh: "音频转换提取,十分好用",
name_en: "Ultimate Vocal Remover",
desc_en: "Audio conversion and extraction, very useful.",
}
];
// ========== 渲染工具列表的函数 ==========