diff --git a/content/journey/2026-04-24-mirror-canyon.md b/content/journey/2026-04-24-mirror-canyon.md new file mode 100644 index 0000000..0b955bb --- /dev/null +++ b/content/journey/2026-04-24-mirror-canyon.md @@ -0,0 +1,29 @@ +--- +title: "镜谷慢降:雾海上的银鹿列车" +date: 2026-04-24T10:44:00+08:00 +draft: false +tags: ["奇幻梦境", "旅程", "镜谷", "电影感"] +logline: "一列银鹿牵引的无声列车穿过会呼吸的雾海,我在镜谷下沉,拾回被晨光遗忘的名字。" +--- + +凌晨四点二十七分,月亮像一枚尚未冷却的银币,贴在窗玻璃上。列车并不在轨道上行驶,它沿着雾海的脊背缓慢滑行,像一把被岁月磨薄的刀,切开夜与晨之间那层最柔软的皮肤。 + +车厢里没有灯,只有座位边缘一圈浅蓝色的磷光。每一次呼吸,光就轻轻亮一下,仿佛整列车都在和我共享同一只肺。我把掌心贴在车窗,玻璃另一侧是一整座漂浮的镜谷——峡壁由无数镜面叠成,反射出彼此,反射出我,反射出还没来得及发生的今天。 + +第一只银鹿从车头旁掠过时,我听见了铃铛。那声音不像金属,更像雪落进深井,清亮却很远。它们的角上悬着细小的星尘灯,每一步都在雾里点燃一粒微光,把前路缝成一条会闪烁的丝带。 + +我在第三节车厢下车。站台只是一块悬浮的黑曜石薄片,脚踩上去会泛起同心圆的光纹,像湖面记住了我的重量。远处有一口月井,井沿长满透明苔藓,风吹过时发出低低的和声,像某个古老教堂在云层背后练习黎明。 + +> 镜谷的规矩很简单:你看见什么,不一定存在;你感到什么,往往才是真的。 + +我沿着井边往下走,雾越来越厚,颜色从蓝紫慢慢沉入墨绿。峡壁上的镜面开始播放陌生片段:一张旧车票、一封没寄出的信、一句在喉咙里折返的道歉。画面没有声音,却比任何对白都锋利。每经过一面镜子,我都听见自己的脚步在更深处回响一次,像有人替我把迟到的答案念出来。 + +在谷底,银鹿列车正停在一片无风的水面上。车门敞开,车厢里摆着许多空白相框,框内悬浮着细小的光尘。我伸手触碰其中一框,它立刻显影——是一条被晨光照亮的街道,尽头站着一个曾经的我,正把某个名字悄悄塞进外套口袋,生怕被世界看见。 + +那一刻我忽然明白,所谓“遗忘”并不是消失,而是被梦境暂存。镜谷像一座巨大的后期剪辑室,把白天来不及处理的情绪,一帧一帧地修复,再在凌晨交还给我们。 + +返程时,雾海已经泛起金边。银鹿们并肩站在车头,角上的星尘灯一盏接一盏熄灭,像天幕缓慢收拢。我坐回窗边,看见镜谷在远处下沉,最终只剩一道细长的银线,嵌进晨色里。 + +列车抵达现实前,我在掌心写下今天的第一句话: + +**如果白昼太快,就把心事寄存在夜里;梦会替你把它们温柔地送回。** diff --git a/themes/dreamscape/assets/css/dreamscape.css b/themes/dreamscape/assets/css/dreamscape.css index 633e109..01669c9 100644 --- a/themes/dreamscape/assets/css/dreamscape.css +++ b/themes/dreamscape/assets/css/dreamscape.css @@ -30,6 +30,20 @@ body { overflow-x: hidden; } +body::before { + content: ''; + position: fixed; + inset: 0; + pointer-events: none; + z-index: 2; + opacity: 0.18; + background-image: + radial-gradient(circle at 20% 18%, rgba(255,255,255,0.10), transparent 40%), + radial-gradient(circle at 78% 72%, rgba(125, 211, 252, 0.12), transparent 44%), + linear-gradient(120deg, rgba(123,104,238,0.05), rgba(34,211,238,0.02)); + mix-blend-mode: screen; +} + /* ═══ 星空画布 ═══ */ #starfield { position: fixed; @@ -57,8 +71,9 @@ body { align-items: center; gap: 2rem; padding: 1.2rem; - background: linear-gradient(180deg, rgba(3,3,8,0.95) 0%, transparent 100%); + background: linear-gradient(180deg, rgba(3,3,8,0.85) 0%, rgba(3,3,8,0.28) 60%, transparent 100%); backdrop-filter: blur(8px); + transition: opacity 0.35s ease, transform 0.35s ease, background 0.35s ease; } .nav-logo { @@ -74,7 +89,7 @@ body { color: var(--text-dim); text-decoration: none; letter-spacing: 0.5px; - transition: color 0.3s, text-shadow 0.3s; + transition: color 0.3s, text-shadow 0.3s, opacity 0.3s; } .nav-link:hover { @@ -82,6 +97,26 @@ body { text-shadow: 0 0 20px var(--glow-cyan); } +/* 单篇旅程页:弱化“网页操作感” */ +body.immersive-single .nav { + justify-content: flex-start; + gap: 0.8rem; + padding: 0.7rem 0.9rem; + background: linear-gradient(180deg, rgba(3,3,8,0.45) 0%, rgba(3,3,8,0.10) 65%, transparent 100%); +} + +body.immersive-single .nav-link { + font-size: 0.72rem; + opacity: 0; + pointer-events: none; +} + +body.immersive-single .nav:hover .nav-link, +body.immersive-single .nav:focus-within .nav-link { + opacity: 0.75; + pointer-events: auto; +} + /* ═══ 主内容 ═══ */ main { flex: 1; @@ -91,6 +126,11 @@ main { width: 100%; } +body.immersive-single main { + max-width: min(980px, 100%); + padding-top: 3.6rem; +} + /* ═══ 首页英雄区 ═══ */ .dream-home { padding-top: 8vh; } @@ -353,6 +393,56 @@ body[data-scene="calling"] .scene-veil { transition: width 0.08s linear; } +.title-card { + position: fixed; + inset: 0; + z-index: 60; + display: grid; + place-items: center; + pointer-events: none; + opacity: 0; + background: radial-gradient(circle at center, rgba(3,3,8,0.55), rgba(3,3,8,0.88)); +} + +.title-card-inner { + text-align: center; + letter-spacing: 0.06em; + transform: translateY(10px) scale(0.98); + filter: blur(2px); +} + +.title-card-kicker { + display: block; + font-size: 0.72rem; + color: rgba(232,232,248,0.62); + margin-bottom: 0.9rem; + font-family: var(--font-sans); +} + +.title-card strong { + display: block; + font-size: clamp(1.5rem, 4.8vw, 2.8rem); + color: #f4f4ff; + text-shadow: 0 0 24px rgba(123,104,238,0.45); +} + +.title-card em { + display: block; + margin-top: 0.75rem; + font-style: normal; + font-size: 0.8rem; + color: rgba(232,232,248,0.62); +} + +body.immersive-single .title-card.show { + animation: titleCardIn 1.35s cubic-bezier(.19,.8,.25,1) forwards; +} + +body.immersive-single .title-card.fade { + animation: titleCardOut 0.8s ease forwards; +} + + /* 场景段落:像旅程节点 */ .article-body > p { position: relative; @@ -416,13 +506,17 @@ body.scene-shift .scene-veil { /* 降级策略:用户偏好减少动画 */ @media (prefers-reduced-motion: reduce) { + #starfield, + body::before, .story-orb, .story-progress-bar, .scene-veil, + .title-card, .reveal, .js-ready .article-body p { animation: none !important; transition: none !important; + transform: none !important; } } @@ -438,6 +532,27 @@ body.scene-shift .scene-veil { margin-bottom: 3rem; } +body.immersive-single .article-hero { + padding-top: 1.2rem; + margin-bottom: 1.6rem; + border-bottom-color: rgba(123,104,238,0.06); +} + +body.immersive-single .article-glyph, +body.immersive-single .article-title, +body.immersive-single .article-meta { + opacity: 0.14; + filter: blur(0.2px); + transition: opacity 0.4s ease; +} + +body.immersive-single .article-hero:hover .article-glyph, +body.immersive-single .article-hero:hover .article-title, +body.immersive-single .article-hero:hover .article-meta { + opacity: 0.75; +} + + .article-glyph { font-size: 2rem; color: var(--glow-purple); @@ -467,12 +582,19 @@ body.scene-shift .scene-veil { /* ═══ 文章正文 — 沉浸式排版 ═══ */ .article-body { - font-size: 1.1rem; - line-height: 2; - max-width: 680px; + font-size: clamp(1.06rem, 1.2vw, 1.2rem); + line-height: 2.08; + max-width: 700px; margin: 0 auto; + position: relative; } +body.immersive-single .article-body { + max-width: min(740px, 92vw); + letter-spacing: 0.018em; +} + + .article-body p { margin-bottom: 1.8rem; text-align: justify; @@ -572,6 +694,17 @@ body.scene-shift .scene-veil { font-size: 0.9rem; } +body.immersive-single .article-nav { + opacity: 0.2; + filter: blur(0.1px); + transition: opacity 0.3s ease; +} + +body.immersive-single .article-nav:hover { + opacity: 0.85; +} + + .article-nav a { color: var(--text-dim); text-decoration: none; @@ -627,6 +760,16 @@ body.scene-shift .scene-veil { border-top: 1px solid rgba(123,104,238,0.1); } +body.immersive-single .footer { + border-top-color: transparent; + color: rgba(136,136,170,0.35); + opacity: 0.45; +} + +body.immersive-single .footer:hover { + opacity: 0.85; +} + /* ═══ 动画 ═══ */ @keyframes pulse { 0%, 100% { opacity: 0.6; } @@ -653,6 +796,18 @@ body.scene-shift .scene-veil { to { opacity: 1; transform: translateY(0); } } +@keyframes titleCardIn { + 0% { opacity: 0; } + 30% { opacity: 1; } + 100% { opacity: 1; } +} + +@keyframes titleCardOut { + from { opacity: 1; } + to { opacity: 0; } +} + + .reveal { opacity: 1; transform: none; @@ -677,4 +832,17 @@ body.scene-shift .scene-veil { .hero-title { font-size: 2rem; } .article-body { font-size: 1rem; } .card-constellation { grid-template-columns: 1fr; } + + body.immersive-single .nav { + gap: 0.45rem; + padding: 0.45rem 0.65rem; + } + + body.immersive-single .nav-link { + font-size: 0.64rem; + } + + body.immersive-single main { + padding-top: 3rem; + } } diff --git a/themes/dreamscape/assets/js/dreamscape.js b/themes/dreamscape/assets/js/dreamscape.js index cf6e4f4..65abf9d 100644 --- a/themes/dreamscape/assets/js/dreamscape.js +++ b/themes/dreamscape/assets/js/dreamscape.js @@ -1,23 +1,22 @@ /* ═══════════════════════════════════════════════════════════════ - VAL'S DREAMSCAPE — 星空粒子 + 滚动动画 + VAL'S DREAMSCAPE — 星空粒子 + 旅程镜头系统 ═══════════════════════════════════════════════════════════════ */ (function() { 'use strict'; - // ═══ 星空粒子系统 ═══ + const bodyEl = document.body; + const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; const canvas = document.getElementById('starfield'); - if (!canvas) return; - const ctx = canvas.getContext('2d'); - let width, height, stars, nebulas; - let animationId; - function init() { - resize(); - createStars(); - createNebulas(); - animate(); - } + if (!canvas) return; + + const ctx = canvas.getContext('2d'); + let width = 0; + let height = 0; + let stars = []; + let nebulas = []; + let animationId = null; function resize() { width = canvas.width = window.innerWidth; @@ -25,37 +24,35 @@ } function createStars() { - const count = Math.floor((width * height) / 8000); + const density = reduceMotion ? 14000 : 8500; + const count = Math.floor((width * height) / density); + stars = []; for (let i = 0; i < count; i++) { stars.push({ x: Math.random() * width, y: Math.random() * height, - size: Math.random() * 1.5 + 0.5, - speed: Math.random() * 0.3 + 0.1, - opacity: Math.random() * 0.8 + 0.2, + size: Math.random() * 1.35 + 0.45, + speed: reduceMotion ? 0.03 : Math.random() * 0.24 + 0.08, + opacity: Math.random() * 0.76 + 0.2, twinkle: Math.random() * Math.PI * 2, - color: Math.random() > 0.7 ? - `hsl(${220 + Math.random() * 60}, 70%, 80%)` : - '#ffffff' + color: Math.random() > 0.68 + ? `hsl(${205 + Math.random() * 70}, 72%, 82%)` + : '#ffffff' }); } } function createNebulas() { nebulas = [ - { x: width * 0.2, y: height * 0.3, r: 200, color: 'rgba(123,104,238,0.03)' }, - { x: width * 0.8, y: height * 0.7, r: 250, color: 'rgba(34,211,238,0.02)' }, - { x: width * 0.5, y: height * 0.5, r: 300, color: 'rgba(74,144,217,0.02)' } + { x: width * 0.18, y: height * 0.25, r: Math.max(width * 0.22, 160), color: 'rgba(123,104,238,0.035)' }, + { x: width * 0.78, y: height * 0.68, r: Math.max(width * 0.26, 180), color: 'rgba(34,211,238,0.03)' }, + { x: width * 0.48, y: height * 0.52, r: Math.max(width * 0.31, 220), color: 'rgba(74,144,217,0.026)' } ]; } - function animate() { - ctx.fillStyle = 'rgba(3,3,8,0.1)'; - ctx.fillRect(0, 0, width, height); - - // 绘制星云 - nebulas.forEach(n => { + function drawNebulas() { + nebulas.forEach((n) => { const gradient = ctx.createRadialGradient(n.x, n.y, 0, n.x, n.y, n.r); gradient.addColorStop(0, n.color); gradient.addColorStop(1, 'transparent'); @@ -64,11 +61,12 @@ ctx.arc(n.x, n.y, n.r, 0, Math.PI * 2); ctx.fill(); }); + } - // 绘制星星 - stars.forEach(star => { - star.twinkle += 0.02; - const twinkleFactor = 0.5 + Math.sin(star.twinkle) * 0.5; + function drawStars() { + stars.forEach((star) => { + if (!reduceMotion) star.twinkle += 0.02; + const twinkleFactor = reduceMotion ? 0.72 : (0.5 + Math.sin(star.twinkle) * 0.5); const currentOpacity = star.opacity * twinkleFactor; ctx.beginPath(); @@ -78,63 +76,109 @@ ctx.fill(); ctx.globalAlpha = 1; - // 缓慢漂移 star.y += star.speed; if (star.y > height + 10) { star.y = -10; star.x = Math.random() * width; } }); + } + + function animate() { + ctx.fillStyle = reduceMotion ? 'rgba(3,3,8,0.88)' : 'rgba(3,3,8,0.12)'; + ctx.fillRect(0, 0, width, height); + + drawNebulas(); + drawStars(); animationId = requestAnimationFrame(animate); } - // ═══ 滚动显示动画 ═══ - function setupReveal() { - const reveals = document.querySelectorAll('.reveal'); - - const observer = new IntersectionObserver((entries) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - entry.target.classList.add('visible'); - } - }); - }, { - threshold: 0.1, - rootMargin: '0px 0px -50px 0px' - }); - - reveals.forEach(el => observer.observe(el)); + function startSky() { + resize(); + createStars(); + createNebulas(); + if (!animationId) animate(); + } + + function stopSky() { + if (animationId) { + cancelAnimationFrame(animationId); + animationId = null; + } + } + + function setupReveal() { + const reveals = document.querySelectorAll('.reveal'); + if (!reveals.length) return; + + const observer = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) entry.target.classList.add('visible'); + }); + }, { + threshold: 0.12, + rootMargin: '0px 0px -52px 0px' + }); + + reveals.forEach((el) => observer.observe(el)); } - // ═══ 视差效果 ═══ function setupParallax() { const hero = document.querySelector('.hero'); - if (!hero) return; + if (!hero || reduceMotion) return; + + const onScroll = () => { + const scrollY = window.scrollY; + const opacity = Math.max(0, 1 - scrollY / 620); + hero.style.opacity = opacity; + hero.style.transform = `translateY(${scrollY * 0.28}px)`; + }; + + window.addEventListener('scroll', onScroll, { passive: true }); + onScroll(); + } + + function setupGlow() { + if (reduceMotion) return; + + const cards = document.querySelectorAll('.dream-card'); + cards.forEach((card) => { + card.addEventListener('mousemove', (e) => { + const rect = card.getBoundingClientRect(); + card.style.setProperty('--mouse-x', `${e.clientX - rect.left}px`); + card.style.setProperty('--mouse-y', `${e.clientY - rect.top}px`); + }); + }); + } + + function setupImmersiveNav() { + if (!bodyEl.classList.contains('immersive-single')) return; + + const nav = document.querySelector('.nav'); + if (!nav) return; + + let lastY = window.scrollY; + let ticking = false; + + const updateNav = () => { + const currentY = window.scrollY; + const delta = currentY - lastY; + const hidden = currentY > 120 && delta > 4; + + nav.style.transform = hidden ? 'translateY(-110%)' : 'translateY(0)'; + nav.style.opacity = hidden ? '0.12' : '1'; + lastY = currentY; + ticking = false; + }; window.addEventListener('scroll', () => { - const scrollY = window.scrollY; - const opacity = Math.max(0, 1 - scrollY / 600); - hero.style.opacity = opacity; - hero.style.transform = `translateY(${scrollY * 0.3}px)`; + if (ticking) return; + ticking = true; + requestAnimationFrame(updateNav); }, { passive: true }); } - // ═══ 鼠标光效 ═══ - function setupGlow() { - const cards = document.querySelectorAll('.dream-card'); - cards.forEach(card => { - card.addEventListener('mousemove', (e) => { - const rect = card.getBoundingClientRect(); - const x = e.clientX - rect.left; - const y = e.clientY - rect.top; - card.style.setProperty('--mouse-x', `${x}px`); - card.style.setProperty('--mouse-y', `${y}px`); - }); - }); - } - - // ═══ 场景滚动进度 + 旅程节点高亮 + 语义场景切换 ═══ function setupStoryJourney() { const body = document.getElementById('storyBody'); const progress = document.getElementById('storyProgress'); @@ -145,15 +189,15 @@ if (!scenes.length) return; const sceneProfiles = [ - { key: 'starsea', keywords: ['星海', '星辰', '光河', '漂浮', '夜晚'] }, - { key: 'crystal', keywords: ['水晶', '冰晶', '棱镜', '森林'] }, - { key: 'calling', keywords: ['使命', '记录', '召唤', '欢迎'] }, - { key: 'void', keywords: ['深渊', '平静', '对话'] } + { key: 'starsea', keywords: ['星海', '光河', '星辰', '月亮', '星尘', '夜', '晨色'] }, + { key: 'crystal', keywords: ['水晶', '冰晶', '镜面', '镜谷', '峡壁', '黑曜石', '井'] }, + { key: 'calling', keywords: ['使命', '记录', '交还', '送回', '名字', '返程', '现实'] }, + { key: 'void', keywords: ['深渊', '平静', '雾', '无声', '遗忘', '道歉', '空白'] } ]; const detectScene = (text) => { let matched = 'void'; - let best = 0; + let best = -1; for (const profile of sceneProfiles) { let score = 0; for (const kw of profile.keywords) { @@ -169,8 +213,9 @@ let currentScene = ''; let shiftTimer; + let ticking = false; - const onScroll = () => { + const onScrollFrame = () => { const rect = body.getBoundingClientRect(); const total = Math.max(body.scrollHeight - window.innerHeight, 1); const scrolled = Math.min(Math.max(-rect.top, 0), total); @@ -179,67 +224,117 @@ 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; + const distance = Math.abs(r.top - window.innerHeight * 0.36); + if (distance < minDistance) { + minDistance = distance; activeIndex = idx; } }); + 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'); + bodyEl.dataset.scene = scene; + bodyEl.classList.add('scene-shift'); clearTimeout(shiftTimer); - shiftTimer = setTimeout(() => document.body.classList.remove('scene-shift'), 420); + shiftTimer = setTimeout(() => bodyEl.classList.remove('scene-shift'), 420); } - if (article) { - const camY = scrolled * 0.018; + if (article && !reduceMotion) { + const camY = scrolled * 0.015; article.style.transform = `translate3d(0, ${camY}px, 0)`; } + + ticking = false; }; - window.addEventListener('scroll', onScroll, { passive: true }); - onScroll(); + window.addEventListener('scroll', () => { + if (ticking) return; + ticking = true; + requestAnimationFrame(onScrollFrame); + }, { passive: true }); + + onScrollFrame(); } - // ═══ 漂浮光团轻微视差 ═══ function setupOrbsParallax() { + if (reduceMotion) return; + const orbs = document.querySelectorAll('.story-orb'); if (!orbs.length) return; - window.addEventListener('scroll', () => { + let ticking = false; + + const update = () => { const y = window.scrollY; orbs.forEach((orb, i) => { - const factor = (i + 1) * 0.04; - const xShift = Math.sin(y * 0.002 + i) * 10; + const factor = (i + 1) * 0.034; + const xShift = Math.sin(y * 0.0018 + i) * 8; const yShift = y * factor; orb.style.transform = `translate(${xShift}px, ${yShift}px)`; }); + ticking = false; + }; + + window.addEventListener('scroll', () => { + if (ticking) return; + ticking = true; + requestAnimationFrame(update); }, { passive: true }); + + update(); } - // ═══ 初始化 ═══ - window.addEventListener('resize', () => { - resize(); - createStars(); - createNebulas(); - }); + function setupTitleCard() { + if (!bodyEl.classList.contains('immersive-single')) return; + if (reduceMotion) return; + + const card = document.getElementById('titleCard'); + if (!card) return; + + card.classList.add('show'); + setTimeout(() => { + card.classList.add('fade'); + }, 1200); + } + + function setupLifecycle() { + window.addEventListener('resize', () => { + resize(); + createStars(); + createNebulas(); + }); + + document.addEventListener('visibilitychange', () => { + if (document.hidden) stopSky(); + else startSky(); + }); + } document.addEventListener('DOMContentLoaded', () => { - document.body.classList.add('js-ready'); - init(); + const storyRoot = document.querySelector('.story-journey'); + if (storyRoot) { + const slug = storyRoot.getAttribute('data-story-slug') || ''; + if (slug === 'first-light') { + bodyEl.classList.add('immersive-single'); + } + } + + bodyEl.classList.add('js-ready'); + startSky(); + setupLifecycle(); setupReveal(); setupParallax(); setupGlow(); + setupImmersiveNav(); setupStoryJourney(); setupOrbsParallax(); + setupTitleCard(); }); - })(); diff --git a/themes/dreamscape/layouts/_default/baseof.html b/themes/dreamscape/layouts/_default/baseof.html index 5d9e2fd..46ce44f 100644 --- a/themes/dreamscape/layouts/_default/baseof.html +++ b/themes/dreamscape/layouts/_default/baseof.html @@ -10,11 +10,11 @@ -
+