diff --git a/src/pages/AnnualReportWindow.scss b/src/pages/AnnualReportWindow.scss index bc38030..34b48bd 100644 --- a/src/pages/AnnualReportWindow.scss +++ b/src/pages/AnnualReportWindow.scss @@ -299,6 +299,12 @@ opacity: 0.05; box-shadow: none; filter: blur(80px); + animation: coreBreathing 6s ease-in-out infinite; + } + + @keyframes coreBreathing { + 0%, 100% { opacity: 0.03; transform: translate(-50%, -50%) scale(0.95); } + 50% { opacity: 0.06; transform: translate(-50%, -50%) scale(1.05); } } /* S9: LEXICON (大气) */ @@ -643,199 +649,160 @@ } #scene-8 { - align-items: flex-start; - justify-content: flex-start; - padding: 0 6vw; + align-items: center; + justify-content: center; + padding: 0; + overflow: hidden; } - #scene-8 .s8-layout { + /* V2 Background: Cinematic Aura */ + #scene-8 .s8-bg-layer { position: absolute; - top: 18vh; - left: 50%; - transform: translateX(-50%); - width: min(1240px, 86vw); - display: grid; - grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr); - column-gap: clamp(34px, 4.8vw, 84px); - align-items: start; + inset: -10%; + z-index: 1; + opacity: 0; + transition: opacity 2s 0.2s var(--ease-out); + filter: blur(120px) contrast(1.1) brightness(0.6); + pointer-events: none; + + .bg-avatar { + width: 100%; + height: 100%; + object-fit: cover; + transform: scale(1.2); + } } - #scene-8 .s8-left { + .scene.active #scene-8 .s8-bg-layer { + opacity: 0.18; + } + + #scene-8 .s8-floating-layout { + position: relative; + width: 100vw; + height: 100vh; + z-index: 2; + display: grid; + grid-template-columns: repeat(12, 1fr); + grid-template-rows: repeat(12, 1fr); + padding: 10vh 8vw; + } + + /* The Central Pivot: Name & Meta */ + #scene-8 .s8-hero-unit { + grid-column: 2 / 8; + grid-row: 4 / 7; display: flex; flex-direction: column; - gap: clamp(2.5vh, 3.2vh, 4vh); - padding-top: clamp(8vh, 9vh, 11vh); + justify-content: center; + + .s8-name { + font-size: clamp(4.5rem, 10vw, 8.5rem); + font-weight: 700; + color: var(--c-text-bright); + letter-spacing: 0.08em; + line-height: 1; + margin-bottom: 2vh; + background: linear-gradient(135deg, var(--c-gold-strong), var(--c-text-bright), var(--c-gold-strong)); + background-size: 200% auto; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + animation: shine 8s linear infinite; + text-shadow: 0 0 40px rgba(var(--c-gold-rgb), 0.2); + } + + .s8-meta { + font-family: 'SpaceMonoLocal'; + font-size: clamp(0.7rem, 0.85vw, 0.9rem); + color: var(--c-gold-strong); + letter-spacing: 0.4em; + text-transform: uppercase; + display: flex; + align-items: center; + gap: 1.5vw; + + &::after { + content: ''; + flex: 1; + height: 1px; + background: linear-gradient(to right, rgba(var(--c-gold-rgb), 0.6), transparent); + } + } } - #scene-8 .s8-name-wrap, - #scene-8 .s8-summary-wrap, - #scene-8 .s8-quote-wrap, - #scene-8 .s8-letter-wrap { - display: block; - width: 100%; - } - - #scene-8 .s8-name { - font-size: clamp(3.2rem, 7.4vw, 5.6rem); - color: rgba(var(--c-gold-rgb), 0.88); - letter-spacing: 0.08em; - line-height: 1.05; - } - - #scene-8 .s8-summary { - max-width: 34ch; - font-size: clamp(1.06rem, 1.35vw, 1.35rem); - color: var(--c-text-soft); - line-height: 1.95; - letter-spacing: 0.02em; - } - - #scene-8 .s8-summary-count { - margin: 0 8px; - font-size: clamp(1.35rem, 2vw, 1.75rem); - color: var(--c-gold-strong); - white-space: nowrap; - } - - #scene-8 .s8-quote { - max-width: 32ch; - font-size: clamp(0.98rem, 1.12vw, 1.1rem); - color: var(--c-text-muted); - line-height: 1.9; - } - - #scene-8 .s8-letter-wrap { - margin-top: clamp(3vh, 4vh, 5.5vh); - } - - #scene-8 .s8-letter { - position: relative; - padding: clamp(24px, 3.2vh, 38px) clamp(20px, 2.6vw, 34px) clamp(24px, 3.2vh, 38px) clamp(30px, 3.2vw, 44px); - border-radius: 18px; - border: 1px solid rgba(var(--c-gold-rgb), 0.34); - background: linear-gradient(135deg, rgba(var(--c-gold-rgb), 0.16), rgba(var(--c-gold-rgb), 0.04)); - font-size: clamp(0.95rem, 1.05vw, 1.08rem); - line-height: 2; - color: var(--c-text-soft); - text-align: left; - text-shadow: 0 4px 16px rgba(0, 0, 0, 0.22); - } - - #scene-8 .s8-letter::before { - content: ''; + /* Fragmented Storytelling */ + #scene-8 .s8-fragments { position: absolute; - top: 20px; - left: 14px; - width: 2px; - height: calc(100% - 40px); - border-radius: 2px; - background: linear-gradient(to bottom, rgba(var(--c-gold-rgb), 0.7), rgba(var(--c-gold-rgb), 0.08)); + inset: 0; + pointer-events: none; + } + + #scene-8 .fragment { + position: absolute; + max-width: 24ch; + font-size: clamp(0.95rem, 1.1vw, 1.15rem); + line-height: 2.1; + color: var(--c-text-muted); + font-weight: 300; + + &.f1 { + top: 25vh; + right: 12vw; + text-align: right; + color: var(--c-text-soft); + font-style: italic; + } + + &.f2 { + bottom: 20vh; + left: 15vw; + max-width: 38ch; + } + + &.f3 { + bottom: 12vh; + right: 10vw; + text-align: right; + opacity: 0.6; + font-size: 0.85rem; + letter-spacing: 0.05em; + } + } + + @keyframes shine { + to { background-position: 200% center; } } #scene-8 .s8-empty-wrap { - display: block; - width: min(760px, 78vw); - margin-top: 24vh; + grid-column: 4 / 10; + grid-row: 5 / 8; text-align: center; - } - - #scene-8 .s8-empty-text { - color: var(--c-text); - line-height: 2; - } - - @media (max-width: 1280px) { - #scene-8 .s8-layout { - width: min(1120px, 88vw); - grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr); - column-gap: clamp(28px, 4vw, 56px); - } - - #scene-8 .s8-left { - padding-top: clamp(6vh, 8vh, 9vh); + .s8-empty-text { + font-size: 1.6rem; + line-height: 2.5; + color: var(--c-text-soft); + font-weight: 200; } } @media (max-width: 1024px) { - #scene-8 .s8-layout { - top: 16vh; - width: min(900px, 90vw); - grid-template-columns: 1fr; - row-gap: clamp(3vh, 3.5vh, 4.5vh); + #scene-8 .s8-hero-unit { + grid-column: 2 / 12; + grid-row: 2 / 5; } - - #scene-8 .s8-left { - padding-top: 0; - gap: clamp(1.6vh, 2.2vh, 2.8vh); + #scene-8 .fragment { + position: relative; + inset: auto !important; + max-width: 100%; + text-align: left !important; + margin-top: 4vh; } - - #scene-8 .s8-name { - font-size: clamp(2.4rem, 8.4vw, 4.2rem); - letter-spacing: 0.06em; - } - - #scene-8 .s8-summary, - #scene-8 .s8-quote { - max-width: none; - } - - #scene-8 .s8-letter-wrap { - margin-top: 0; - } - - #scene-8 .s8-letter { - font-size: clamp(0.9rem, 1.9vw, 1rem); - line-height: 1.95; - } - } - - @media (max-width: 760px) { - #scene-8 .s8-layout { - top: 14.5vh; - width: 92vw; - row-gap: clamp(2.2vh, 3vh, 3.8vh); - } - - #scene-8 .s8-name { - font-size: clamp(2rem, 10vw, 3rem); - } - - #scene-8 .s8-summary { - font-size: clamp(0.92rem, 3.9vw, 1rem); - line-height: 1.85; - } - - #scene-8 .s8-summary-count { - margin: 0 6px; - font-size: clamp(1.1rem, 4.8vw, 1.35rem); - } - - #scene-8 .s8-quote { - font-size: clamp(0.86rem, 3.5vw, 0.95rem); - line-height: 1.8; - } - - #scene-8 .s8-letter { - border-radius: 14px; - padding: 16px 16px 16px 24px; - font-size: clamp(0.82rem, 3.4vw, 0.9rem); - line-height: 1.82; - } - - #scene-8 .s8-letter::before { - top: 16px; - left: 11px; - height: calc(100% - 32px); - } - - #scene-8 .s8-empty-wrap { - width: 88vw; - margin-top: 23vh; - } - - #scene-8 .s8-empty-text { - font-size: 1rem; - line-height: 1.9; + #scene-8 .s8-fragments { + position: relative; + grid-column: 2 / 12; + grid-row: 6 / 12; + display: flex; + flex-direction: column; } } diff --git a/src/pages/AnnualReportWindow.tsx b/src/pages/AnnualReportWindow.tsx index 5274bb7..ae49222 100644 --- a/src/pages/AnnualReportWindow.tsx +++ b/src/pages/AnnualReportWindow.tsx @@ -93,7 +93,7 @@ const DecodeText = ({ if (i < iter) return strVal[i] return chars[Math.floor(Math.random() * chars.length)] }).join('')) - + if (iter >= strVal.length) { clearInterval(inv) setDisplay(strVal) @@ -123,7 +123,7 @@ function AnnualReportWindow() { const s3LayoutRef = useRef(null) const s3ListRef = useRef(null) const [s3LineVars, setS3LineVars] = useState({}) - + // 提取长图逻辑变量 const [buttonText, setButtonText] = useState('EXTRACT RECORD') const [isExtracting, setIsExtracting] = useState(false) @@ -202,7 +202,7 @@ function AnnualReportWindow() { setIsAnimating(true) setCurrentScene(index) - + setTimeout(() => { setIsAnimating(false) }, 1500) @@ -217,7 +217,7 @@ function AnnualReportWindow() { const handleWheel = (e: WheelEvent) => { const now = Date.now() if (now - lastWheelTime < 1000) return // Throttle wheel events - + if (Math.abs(e.deltaY) > 30) { lastWheelTime = now goToScene(e.deltaY > 0 ? currentScene + 1 : currentScene - 1) @@ -564,21 +564,21 @@ function AnnualReportWindow() {
- +
- +
{Array.from({ length: TOTAL_SCENES }).map((_, i) => ( -
goToScene(i)} /> ))}
- +
向下滑动以继续
{/* S0: THE ARCHIVE */} @@ -590,7 +590,7 @@ function AnnualReportWindow() {
{yearTitle}
-
那些被岁月悄悄掩埋的对话
原来都在这里,等待一个春天。
+
那些被岁月悄悄掩埋的对话
原来都在这里,等待一个春天。
@@ -606,7 +606,7 @@ function AnnualReportWindow() {
- 这一年,你说出了 {reportData.totalMessages.toLocaleString()} 句话。
无数个日夜的碎碎念,都是为了在茫茫人海中,刻下彼此来过的痕迹。 + 这一年,你说出了 {reportData.totalMessages.toLocaleString()} 句话。
无数个日夜的碎碎念,都是为了在茫茫人海中,刻下彼此来过的痕迹。
@@ -618,20 +618,20 @@ function AnnualReportWindow() {
- {reportData.midnightKing ? reportData.midnightKing.displayName : '00:00'} + {reportData.midnightKing ? reportData.midnightKing.displayName : '00:00'}
-
+
在深夜陪你聊天最多的人
- 梦境之外,你与{reportData.midnightKing ? reportData.midnightKing.displayName : '00:00'}共同醒着度过了许多个夜晚
+ 梦境之外,你与{reportData.midnightKing ? reportData.midnightKing.displayName : '00:00'}共同醒着度过了许多个夜晚
“曾有 - + 条消息在那些无人知晓的夜里,代替星光照亮了彼此”
@@ -689,56 +689,56 @@ function AnnualReportWindow() { {reportData.monthlyTopFriends.length > 0 ? (
{reportData.monthlyTopFriends.map((m, i) => { - const leftPos = (i / 11) * 100; // 0% to 100% - const isTop = i % 2 === 0; // Alternate up and down to prevent crowding - const isRightSide = i >= 6; // Center-focus alignment logic - - // Pseudo-random organic height variation for audio-wave feel (from 8vh to 18vh) - const heightVariation = 12 + (Math.sin(i * 1.5) * 6); - - const alignStyle = isRightSide ? { right: '10px', alignItems: 'flex-end', textAlign: 'right' as const } : { left: '10px', alignItems: 'flex-start', textAlign: 'left' as const }; + const leftPos = (i / 11) * 100; // 0% to 100% + const isTop = i % 2 === 0; // Alternate up and down to prevent crowding + const isRightSide = i >= 6; // Center-focus alignment logic - return ( -
- - {/* The connecting thread (gradient fades away from center line) */} -
+ // Pseudo-random organic height variation for audio-wave feel (from 8vh to 18vh) + const heightVariation = 12 + (Math.sin(i * 1.5) * 6); - {/* Center Glowing Dot */} -
+ const alignStyle = isRightSide ? { right: '10px', alignItems: 'flex-end', textAlign: 'right' as const } : { left: '10px', alignItems: 'flex-start', textAlign: 'left' as const }; - {/* Text Payload */} -
-
- {m.month.toString().padStart(2, '0')} -
-
- {m.displayName} -
-
- {m.messageCount.toLocaleString()} M -
+ return ( +
+ + {/* The connecting thread (gradient fades away from center line) */} +
+ + {/* Center Glowing Dot */} +
+ + {/* Text Payload */} +
+
+ {m.month.toString().padStart(2, '0')}
+
+ {m.displayName} +
+
+ {m.messageCount.toLocaleString()} M +
+
-
- ); +
+ ); })}
) : ( @@ -757,29 +757,29 @@ function AnnualReportWindow() { <>
- {reportData.mutualFriend.displayName} + {reportData.mutualFriend.displayName}
- +
-
发出
-
+
发出
+
-
收到
-
+
收到
+
你们之间收发的消息高达 {reportData.mutualFriend.ratio} 的平衡率 -
- “你抛出的每一句话,都落在了对方的心里。
所谓重逢,就是我走向你的时候,你也在走向我。”
+
+ “你抛出的每一句话,都落在了对方的心里。
所谓重逢,就是我走向你的时候,你也在走向我。”
) : ( -
今年似乎独自咽下了很多话。
请相信,分别和孤独总会迎来终结,你终会遇到那个懂你的TA。
+
今年似乎独自咽下了很多话。
请相信,分别和孤独总会迎来终结,你终会遇到那个懂你的TA。
)}
@@ -790,45 +790,45 @@ function AnnualReportWindow() {
{reportData.socialInitiative || reportData.responseSpeed ? (
- {reportData.socialInitiative && ( -
-
我的主动性
-
- {reportData.socialInitiative.initiativeRate}% + {reportData.socialInitiative && ( +
+
我的主动性
+
+ {reportData.socialInitiative.initiativeRate}% +
+
+
+ 你的聊天开场大多由你发起。
-
-
- 你的聊天开场大多由你发起。 + {reportData.socialInitiative.topInitiatedFriend && (reportData.socialInitiative.topInitiatedCount || 0) > 0 ? ( +
+ 其中{reportData.socialInitiative.topInitiatedFriend}是你最常联系的人, + 有{(reportData.socialInitiative.topInitiatedCount || 0).toLocaleString()}次,是你先忍不住敲响了对方的门
- {reportData.socialInitiative.topInitiatedFriend && (reportData.socialInitiative.topInitiatedCount || 0) > 0 ? ( -
- 其中{reportData.socialInitiative.topInitiatedFriend}是你最常联系的人, - 有{(reportData.socialInitiative.topInitiatedCount || 0).toLocaleString()}次,是你先忍不住敲响了对方的门 -
- ) : ( -
- 你主动发起了{reportData.socialInitiative.initiatedChats.toLocaleString()}次联络。 -
- )} - 想见一个人的心,总是走在时间的前面。 -
+ ) : ( +
+ 你主动发起了{reportData.socialInitiative.initiatedChats.toLocaleString()}次联络。 +
+ )} + 想见一个人的心,总是走在时间的前面。
- )} - {reportData.responseSpeed && ( -
-
回应速度
-
- S -
-
- {reportData.responseSpeed.fastestFriend} 回你的消息总是很快。
- 这世上最让人安心的默契,莫过于一句 "我在"。 -
+
+ )} + {reportData.responseSpeed && ( +
+
回应速度
+
+ S
- )} +
+ {reportData.responseSpeed.fastestFriend} 回你的消息总是很快。
+ 这世上最让人安心的默契,莫过于一句 "我在"。 +
+
+ )}
) : ( -
暂无数据。
+
暂无数据。
)}
@@ -837,33 +837,33 @@ function AnnualReportWindow() {
聊天火花
- + {reportData.longestStreak ? ( -
-
最长连续聊天
-
- {reportData.longestStreak.friendName} -
-
- 你们曾连续 天,聊到忘记了时间,
那些舍不得说再见的日夜,连成了最漫长的春天。 -
-
+
+
最长连续聊天
+
+ {reportData.longestStreak.friendName} +
+
+ 你们曾连续 天,聊到忘记了时间,
那些舍不得说再见的日夜,连成了最漫长的春天。 +
+
) : null} {reportData.peakDay ? ( -
-
最热烈的一天
-
- {reportData.peakDay.date} -
-
- “这一天,你们留下了 {reportData.peakDay.messageCount} 句话。
好像要把积攒了很久的想念,一天全都说完。” -
-
+
+
最热烈的一天
+
+ {reportData.peakDay.date} +
+
+ “这一天,你们留下了 {reportData.peakDay.messageCount} 句话。
好像要把积攒了很久的想念,一天全都说完。” +
+
) : null} - + {!reportData.longestStreak && !reportData.peakDay && ( -
没有激起过火花。
+
没有激起过火花。
)}
@@ -872,45 +872,69 @@ function AnnualReportWindow() {
曾经的好友
+ + {reportData.lostFriend && ( +
+ +
+ )} + + {reportData.lostFriend ? ( -
-
-
-
+
+
+
+
{reportData.lostFriend.displayName}
-
-
- 后来,你们的交集停留在{reportData.lostFriend.periodDesc}这短短的 - +
+
+ {reportData.lostFriend.periodDesc} / + - 句话里。 -
-
-
-
- “我一直相信我们能够再次相见,相信分别的日子总会迎来终结。” + MESSAGES
-
-
- 所有的离散,或许都只是一场漫长的越冬。飞鸟要越过一万座雪山,才能带来春天的第一行回信;树木要褪去一万次枯叶,才能记住风的形状。如果时间注定要把我们推向不同的象限,那就在记忆的最深处建一座灯塔。哪怕要熬过几千个无法见面的黄昏,也要相信,总有一次日出的晨光,是为了照亮我们重逢的归途。 + +
+
+
+ “我一直相信我们能够再次相见,
相信分别的日子总会迎来终结。” +
+
+ +
+
+ 所有的离散,或许都只是一场漫长的越冬。
+ 飞鸟要越过一万座雪山,才能带来春天的第一行回信;
+ 树木要褪去一万次枯叶,才能记住风的形状。 +
+
+ +
+
+ 哪怕要熬过几千个无法见面的黄昏,也要相信,
+ 总有一次日出的晨光,是为了照亮我们重逢的归途。 +
) : ( -
-
- 缘分温柔地眷顾着你。
- 这一年,所有重要的人都在,没有一次无疾而终的告别。
+
+
+
+ 缘分温柔地眷顾着你。
+ 这一年,所有重要的人都在,没有一次无疾而终的告别。 +
)}
+ {/* S9: LEXICON & ARCHIVE */}
@@ -936,16 +960,16 @@ function AnnualReportWindow() { const st = demoStyles[i]; return ( -
{phrase.phrase} @@ -953,7 +977,7 @@ function AnnualReportWindow() { ) })} {(!reportData.topPhrases || reportData.topPhrases.length === 0) && ( -
词汇量太少,无法形成星云。
+
词汇量太少,无法形成星云。
)}
@@ -962,7 +986,7 @@ function AnnualReportWindow() {
旅程的终点
- + {/* The Final Summary Receipt / Dashboard */}
@@ -972,7 +996,7 @@ function AnnualReportWindow() {
TRANSMISSION COMPLETE
- + {/* Core Stats Row */}
@@ -988,9 +1012,9 @@ function AnnualReportWindow() {
“{endingTopPhrase}”
- +
- “故事的最后,我们把这一切悄悄还给岁月
只要这些文字还在,所有的离别,就都只是一场短暂的缺席。” + “故事的最后,我们把这一切悄悄还给岁月
只要这些文字还在,所有的离别,就都只是一场短暂的缺席。”
@@ -1009,15 +1033,15 @@ function AnnualReportWindow() { fontWeight: 500 }} > - 数据数得清一万句落笔的寒暄,却度量不出一个默契的眼神。
在这片由数字构建的大海里,热烈的回应未必是感情的全部轮廓。
真正的爱与羁绊,从来都不在跳动的屏幕里,而在无法被量化的现实。 + 数据数得清一万句落笔的寒暄,却度量不出一个默契的眼神。
在这片由数字构建的大海里,热烈的回应未必是感情的全部轮廓。
真正的爱与羁绊,从来都不在跳动的屏幕里,而在无法被量化的现实。
-