feat: story-journey immersive layer (progress, scene focus, floating orbs)
All checks were successful
Deploy / deploy (push) Successful in 2s
All checks were successful
Deploy / deploy (push) Successful in 2s
This commit is contained in:
@@ -253,6 +253,121 @@ main {
|
|||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ═══ 故事沉浸层(文章页) ═══ */
|
||||||
|
.story-journey {
|
||||||
|
position: relative;
|
||||||
|
isolation: isolate;
|
||||||
|
}
|
||||||
|
|
||||||
|
.story-orb {
|
||||||
|
position: fixed;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: -1;
|
||||||
|
border-radius: 50%;
|
||||||
|
filter: blur(36px);
|
||||||
|
opacity: 0.35;
|
||||||
|
mix-blend-mode: screen;
|
||||||
|
transition: transform 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orb-1 {
|
||||||
|
width: 280px;
|
||||||
|
height: 280px;
|
||||||
|
top: 14%;
|
||||||
|
left: -80px;
|
||||||
|
background: radial-gradient(circle, rgba(123,104,238,0.42), transparent 70%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.orb-2 {
|
||||||
|
width: 220px;
|
||||||
|
height: 220px;
|
||||||
|
top: 48%;
|
||||||
|
right: -60px;
|
||||||
|
background: radial-gradient(circle, rgba(34,211,238,0.34), transparent 72%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.orb-3 {
|
||||||
|
width: 320px;
|
||||||
|
height: 320px;
|
||||||
|
bottom: 6%;
|
||||||
|
left: 24%;
|
||||||
|
background: radial-gradient(circle, rgba(74,144,217,0.30), transparent 72%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.story-progress {
|
||||||
|
position: sticky;
|
||||||
|
top: 62px;
|
||||||
|
height: 4px;
|
||||||
|
background: rgba(123,104,238,0.18);
|
||||||
|
border-radius: 999px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: -1rem 0 2rem;
|
||||||
|
z-index: 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
.story-progress-bar {
|
||||||
|
height: 100%;
|
||||||
|
width: 0%;
|
||||||
|
background: linear-gradient(90deg, var(--glow-purple), var(--glow-cyan));
|
||||||
|
box-shadow: 0 0 18px rgba(123,104,238,0.6);
|
||||||
|
transition: width 0.08s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 场景段落:像旅程节点 */
|
||||||
|
.article-body > p {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-body > p::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: -0.15rem;
|
||||||
|
top: 0.85rem;
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--glow-purple);
|
||||||
|
box-shadow: 0 0 12px rgba(123,104,238,0.8);
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-body > p.active-scene::before {
|
||||||
|
background: var(--glow-cyan);
|
||||||
|
box-shadow: 0 0 16px rgba(34,211,238,0.9);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-body > p.active-scene {
|
||||||
|
text-shadow: 0 0 18px rgba(123,104,238,0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 手机端沉浸优化 */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.story-progress {
|
||||||
|
top: 54px;
|
||||||
|
margin: -0.5rem 0 1.2rem;
|
||||||
|
}
|
||||||
|
.story-orb {
|
||||||
|
filter: blur(26px);
|
||||||
|
opacity: 0.28;
|
||||||
|
}
|
||||||
|
.orb-1 { width: 180px; height: 180px; left: -60px; }
|
||||||
|
.orb-2 { width: 150px; height: 150px; right: -45px; }
|
||||||
|
.orb-3 { width: 190px; height: 190px; left: 20%; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 降级策略:用户偏好减少动画 */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.story-orb,
|
||||||
|
.story-progress-bar,
|
||||||
|
.reveal,
|
||||||
|
.js-ready .article-body p {
|
||||||
|
animation: none !important;
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ═══ 文章页 ═══ */
|
/* ═══ 文章页 ═══ */
|
||||||
.dream-article {
|
.dream-article {
|
||||||
padding-top: 2rem;
|
padding-top: 2rem;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
// ═══ 星空粒子系统 ═══
|
// ═══ 星空粒子系统 ═══
|
||||||
const canvas = document.getElementById('starfield');
|
const canvas = document.getElementById('starfield');
|
||||||
|
if (!canvas) return;
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
let width, height, stars, nebulas;
|
let width, height, stars, nebulas;
|
||||||
let animationId;
|
let animationId;
|
||||||
@@ -133,6 +134,55 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ═══ 场景滚动进度 + 旅程节点高亮 ═══
|
||||||
|
function setupStoryJourney() {
|
||||||
|
const body = document.getElementById('storyBody');
|
||||||
|
const progress = document.getElementById('storyProgress');
|
||||||
|
if (!body || !progress) return;
|
||||||
|
|
||||||
|
const scenes = Array.from(body.querySelectorAll(':scope > p'));
|
||||||
|
if (!scenes.length) return;
|
||||||
|
|
||||||
|
const onScroll = () => {
|
||||||
|
const rect = body.getBoundingClientRect();
|
||||||
|
const total = Math.max(body.scrollHeight - window.innerHeight, 1);
|
||||||
|
const scrolled = Math.min(Math.max(-rect.top, 0), total);
|
||||||
|
const ratio = scrolled / total;
|
||||||
|
progress.style.width = `${(ratio * 100).toFixed(2)}%`;
|
||||||
|
|
||||||
|
let activeIndex = 0;
|
||||||
|
let minDistance = Infinity;
|
||||||
|
scenes.forEach((p, idx) => {
|
||||||
|
const r = p.getBoundingClientRect();
|
||||||
|
const d = Math.abs(r.top - window.innerHeight * 0.35);
|
||||||
|
if (d < minDistance) {
|
||||||
|
minDistance = d;
|
||||||
|
activeIndex = idx;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
scenes.forEach((p, idx) => p.classList.toggle('active-scene', idx === activeIndex));
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('scroll', onScroll, { passive: true });
|
||||||
|
onScroll();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══ 漂浮光团轻微视差 ═══
|
||||||
|
function setupOrbsParallax() {
|
||||||
|
const orbs = document.querySelectorAll('.story-orb');
|
||||||
|
if (!orbs.length) return;
|
||||||
|
|
||||||
|
window.addEventListener('scroll', () => {
|
||||||
|
const y = window.scrollY;
|
||||||
|
orbs.forEach((orb, i) => {
|
||||||
|
const factor = (i + 1) * 0.04;
|
||||||
|
const xShift = Math.sin(y * 0.002 + i) * 10;
|
||||||
|
const yShift = y * factor;
|
||||||
|
orb.style.transform = `translate(${xShift}px, ${yShift}px)`;
|
||||||
|
});
|
||||||
|
}, { passive: true });
|
||||||
|
}
|
||||||
|
|
||||||
// ═══ 初始化 ═══
|
// ═══ 初始化 ═══
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
resize();
|
resize();
|
||||||
@@ -146,6 +196,8 @@
|
|||||||
setupReveal();
|
setupReveal();
|
||||||
setupParallax();
|
setupParallax();
|
||||||
setupGlow();
|
setupGlow();
|
||||||
|
setupStoryJourney();
|
||||||
|
setupOrbsParallax();
|
||||||
});
|
});
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<link rel="stylesheet" href="{{ $style.RelPermalink }}">
|
<link rel="stylesheet" href="{{ $style.RelPermalink }}">
|
||||||
<link rel="alternate" type="application/rss+xml" href='{{ "feed.xml" | relURL }}'>
|
<link rel="alternate" type="application/rss+xml" href='{{ "feed.xml" | relURL }}'>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="kind-{{ .Kind }}{{ with .Section }} section-{{ . }}{{ end }}">
|
||||||
<canvas id="starfield"></canvas>
|
<canvas id="starfield"></canvas>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<nav class="nav">
|
<nav class="nav">
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
{{ define "main" }}
|
{{ define "main" }}
|
||||||
<article class="dream-article">
|
<article class="dream-article story-journey" data-story="journey">
|
||||||
|
<div class="story-orb orb-1"></div>
|
||||||
|
<div class="story-orb orb-2"></div>
|
||||||
|
<div class="story-orb orb-3"></div>
|
||||||
|
|
||||||
<div class="article-hero reveal">
|
<div class="article-hero reveal">
|
||||||
<div class="article-glyph">◇</div>
|
<div class="article-glyph">◇</div>
|
||||||
<h1 class="article-title">{{ .Title }}</h1>
|
<h1 class="article-title">{{ .Title }}</h1>
|
||||||
@@ -11,7 +15,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="article-body">
|
<div class="story-progress">
|
||||||
|
<div class="story-progress-bar" id="storyProgress"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="article-body" id="storyBody">
|
||||||
{{ .Content }}
|
{{ .Content }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user