feat: phase2 immersive journey (semantic scene transitions + camera feel)
All checks were successful
Deploy / deploy (push) Successful in 1s
All checks were successful
Deploy / deploy (push) Successful in 1s
This commit is contained in:
@@ -257,6 +257,46 @@ main {
|
|||||||
.story-journey {
|
.story-journey {
|
||||||
position: relative;
|
position: relative;
|
||||||
isolation: isolate;
|
isolation: isolate;
|
||||||
|
--scene-accent: rgba(123,104,238,0.30);
|
||||||
|
--scene-accent-soft: rgba(34,211,238,0.16);
|
||||||
|
transition: transform 0.12s linear, filter 0.4s ease;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scene-veil {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: -2;
|
||||||
|
pointer-events: none;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 20% 20%, var(--scene-accent), transparent 45%),
|
||||||
|
radial-gradient(circle at 80% 70%, var(--scene-accent-soft), transparent 55%);
|
||||||
|
opacity: 0.45;
|
||||||
|
transition: opacity 0.5s ease, filter 0.5s ease, background 0.6s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-scene="void"] .scene-veil {
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 18% 24%, rgba(99,102,241,0.30), transparent 46%),
|
||||||
|
radial-gradient(circle at 74% 75%, rgba(56,189,248,0.14), transparent 56%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-scene="starsea"] .scene-veil {
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 28% 18%, rgba(34,211,238,0.30), transparent 44%),
|
||||||
|
radial-gradient(circle at 76% 70%, rgba(147,197,253,0.18), transparent 56%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-scene="crystal"] .scene-veil {
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 18% 32%, rgba(167,139,250,0.32), transparent 44%),
|
||||||
|
radial-gradient(circle at 82% 70%, rgba(96,165,250,0.20), transparent 56%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-scene="calling"] .scene-veil {
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 22% 24%, rgba(45,212,191,0.32), transparent 44%),
|
||||||
|
radial-gradient(circle at 76% 74%, rgba(59,130,246,0.18), transparent 56%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.story-orb {
|
.story-orb {
|
||||||
@@ -342,6 +382,23 @@ main {
|
|||||||
text-shadow: 0 0 18px rgba(123,104,238,0.22);
|
text-shadow: 0 0 18px rgba(123,104,238,0.22);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.article-body > p.active-scene::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: -0.4rem;
|
||||||
|
top: 0.35rem;
|
||||||
|
width: 2px;
|
||||||
|
height: calc(100% - 0.6rem);
|
||||||
|
border-radius: 2px;
|
||||||
|
background: linear-gradient(180deg, rgba(34,211,238,0.7), transparent);
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.scene-shift .scene-veil {
|
||||||
|
opacity: 0.7;
|
||||||
|
filter: saturate(1.25) brightness(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
/* 手机端沉浸优化 */
|
/* 手机端沉浸优化 */
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.story-progress {
|
.story-progress {
|
||||||
@@ -361,6 +418,7 @@ main {
|
|||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.story-orb,
|
.story-orb,
|
||||||
.story-progress-bar,
|
.story-progress-bar,
|
||||||
|
.scene-veil,
|
||||||
.reveal,
|
.reveal,
|
||||||
.js-ready .article-body p {
|
.js-ready .article-body p {
|
||||||
animation: none !important;
|
animation: none !important;
|
||||||
|
|||||||
@@ -134,15 +134,42 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══ 场景滚动进度 + 旅程节点高亮 ═══
|
// ═══ 场景滚动进度 + 旅程节点高亮 + 语义场景切换 ═══
|
||||||
function setupStoryJourney() {
|
function setupStoryJourney() {
|
||||||
const body = document.getElementById('storyBody');
|
const body = document.getElementById('storyBody');
|
||||||
const progress = document.getElementById('storyProgress');
|
const progress = document.getElementById('storyProgress');
|
||||||
|
const article = document.querySelector('.story-journey');
|
||||||
if (!body || !progress) return;
|
if (!body || !progress) return;
|
||||||
|
|
||||||
const scenes = Array.from(body.querySelectorAll(':scope > p'));
|
const scenes = Array.from(body.querySelectorAll(':scope > p'));
|
||||||
if (!scenes.length) return;
|
if (!scenes.length) return;
|
||||||
|
|
||||||
|
const sceneProfiles = [
|
||||||
|
{ key: 'starsea', keywords: ['星海', '星辰', '光河', '漂浮', '夜晚'] },
|
||||||
|
{ key: 'crystal', keywords: ['水晶', '冰晶', '棱镜', '森林'] },
|
||||||
|
{ key: 'calling', keywords: ['使命', '记录', '召唤', '欢迎'] },
|
||||||
|
{ key: 'void', keywords: ['深渊', '平静', '对话'] }
|
||||||
|
];
|
||||||
|
|
||||||
|
const detectScene = (text) => {
|
||||||
|
let matched = 'void';
|
||||||
|
let best = 0;
|
||||||
|
for (const profile of sceneProfiles) {
|
||||||
|
let score = 0;
|
||||||
|
for (const kw of profile.keywords) {
|
||||||
|
if (text.includes(kw)) score++;
|
||||||
|
}
|
||||||
|
if (score > best) {
|
||||||
|
best = score;
|
||||||
|
matched = profile.key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matched;
|
||||||
|
};
|
||||||
|
|
||||||
|
let currentScene = '';
|
||||||
|
let shiftTimer;
|
||||||
|
|
||||||
const onScroll = () => {
|
const onScroll = () => {
|
||||||
const rect = body.getBoundingClientRect();
|
const rect = body.getBoundingClientRect();
|
||||||
const total = Math.max(body.scrollHeight - window.innerHeight, 1);
|
const total = Math.max(body.scrollHeight - window.innerHeight, 1);
|
||||||
@@ -161,6 +188,21 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
scenes.forEach((p, idx) => p.classList.toggle('active-scene', idx === activeIndex));
|
scenes.forEach((p, idx) => p.classList.toggle('active-scene', idx === activeIndex));
|
||||||
|
|
||||||
|
const activeText = scenes[activeIndex]?.innerText || '';
|
||||||
|
const scene = detectScene(activeText);
|
||||||
|
if (scene !== currentScene) {
|
||||||
|
currentScene = scene;
|
||||||
|
document.body.dataset.scene = scene;
|
||||||
|
document.body.classList.add('scene-shift');
|
||||||
|
clearTimeout(shiftTimer);
|
||||||
|
shiftTimer = setTimeout(() => document.body.classList.remove('scene-shift'), 420);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (article) {
|
||||||
|
const camY = scrolled * 0.018;
|
||||||
|
article.style.transform = `translate3d(0, ${camY}px, 0)`;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('scroll', onScroll, { passive: true });
|
window.addEventListener('scroll', onScroll, { passive: true });
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<div class="story-orb orb-1"></div>
|
<div class="story-orb orb-1"></div>
|
||||||
<div class="story-orb orb-2"></div>
|
<div class="story-orb orb-2"></div>
|
||||||
<div class="story-orb orb-3"></div>
|
<div class="story-orb orb-3"></div>
|
||||||
|
<div id="sceneVeil" class="scene-veil" aria-hidden="true"></div>
|
||||||
|
|
||||||
<div class="article-hero reveal">
|
<div class="article-hero reveal">
|
||||||
<div class="article-glyph">◇</div>
|
<div class="article-glyph">◇</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user