11"use client" ;
22
3- import { useState , useEffect , useCallback , useRef } from "react" ;
3+ import { useState , useCallback } from "react" ;
44import { usePathname } from "next/navigation" ;
55import Image from "next/image" ;
6+ import { motion , AnimatePresence } from "motion/react" ;
67import { activityEventsConfig } from "@/app/types/event" ;
78import { cn } from "@/lib/utils" ;
89import { X , ChevronUp , ExternalLink , Play } from "lucide-react" ;
@@ -33,42 +34,6 @@ export function FloatWindow() {
3334 // 仅在首页 (/) 可见
3435 const isHomePage = pathname === "/" ;
3536
36- const [ position , setPosition ] = useState < { x : number ; y : number } | null > (
37- null ,
38- ) ;
39- const [ isDragging , setIsDragging ] = useState ( false ) ;
40- const dragOffset = useRef < { x : number ; y : number } > ( { x : 0 , y : 0 } ) ;
41-
42- const handlePointerDown = ( e : React . PointerEvent ) => {
43- if ( e . button !== 0 ) return ;
44- const el = e . currentTarget as HTMLElement ;
45- const rect = el . getBoundingClientRect ( ) ;
46- setIsDragging ( true ) ;
47- dragOffset . current = {
48- x : e . clientX - rect . left ,
49- y : e . clientY - rect . top ,
50- } ;
51- if ( ! position ) setPosition ( { x : rect . left , y : rect . top } ) ;
52- el . setPointerCapture ( e . pointerId ) ;
53- } ;
54-
55- useEffect ( ( ) => {
56- if ( ! isDragging ) return ;
57- const handlePointerMove = ( e : PointerEvent ) => {
58- setPosition ( {
59- x : e . clientX - dragOffset . current . x ,
60- y : e . clientY - dragOffset . current . y ,
61- } ) ;
62- } ;
63- const handlePointerUp = ( ) => setIsDragging ( false ) ;
64- window . addEventListener ( "pointermove" , handlePointerMove ) ;
65- window . addEventListener ( "pointerup" , handlePointerUp ) ;
66- return ( ) => {
67- window . removeEventListener ( "pointermove" , handlePointerMove ) ;
68- window . removeEventListener ( "pointerup" , handlePointerUp ) ;
69- } ;
70- } , [ isDragging ] ) ;
71-
7237 const handleDismiss = useCallback ( ( ) => setIsDismissed ( true ) , [ ] ) ;
7338 const handleToggle = useCallback ( ( ) => setIsCollapsed ( ( prev ) => ! prev ) , [ ] ) ;
7439
@@ -78,147 +43,156 @@ export function FloatWindow() {
7843 const currentEvent = latestEvent ;
7944
8045 return (
81- < div
82- ref = { ( node ) => {
83- if ( node ) node . style . touchAction = "none" ;
84- } }
46+ < motion . div
47+ layout
48+ drag
49+ dragMomentum = { false }
50+ whileDrag = { { cursor : "grabbing" } }
51+ initial = { { opacity : 0 , y : 20 , scale : 0.95 } }
52+ animate = { { opacity : 1 , y : 0 , scale : 1 } }
53+ exit = { { opacity : 0 , scale : 0.95 } }
8554 className = { cn (
86- "fixed z-50" ,
87- ! position && "bottom-6 right-6" ,
88- ! isDragging && "transition-all duration-300 ease-out" ,
55+ "fixed z-50 bottom-6 right-6" ,
8956 isCollapsed ? "w-auto" : "w-[280px]" ,
90- isDragging ? "cursor-grabbing" : " cursor-grab" ,
57+ "cursor-grab active: cursor-grabbing" , // tailwind fallback cursor
9158 ) }
92- style = { position ? { left : position . x , top : position . y } : undefined }
93- onPointerDown = { handlePointerDown }
59+ onPointerDown = { ( e ) => e . stopPropagation ( ) } // Prevent conflicts
9460 >
95- { /* 极简折叠状态 */ }
96- { isCollapsed ? (
97- < button
98- onClick = { handleToggle }
99- onPointerDown = { ( e ) => e . stopPropagation ( ) }
100- className = { cn (
101- "group flex items-center gap-2 px-4 py-2" ,
102- // Newsprint 风格:锐利边角,纯黑实线边框
103- "bg-[#111111] text-[#F9F9F7] border border-[#111111]" ,
104- "hover:bg-[#F9F9F7] hover:text-[#111111]" , // 悬停时反色
105- "font-mono text-xs uppercase tracking-widest" ,
106- "transition-colors duration-200" ,
107- "shadow-[4px_4px_0px_0px_#111111] hover:translate-x-[-1px] hover:translate-y-[-1px]" ,
108- ) }
109- >
110- < span className = "w-2 h-2 bg-[#CC0000] animate-pulse" />
111- < span className = "font-bold" > Latest</ span >
112- < ChevronUp className = "w-4 h-4 rotate-180 group-hover:-translate-y-0.5 transition-transform" />
113- </ button >
114- ) : (
115- /* 展开状态 - 报纸卡片 */
116- < div
117- className = { cn (
118- styles . newsprintTexture ,
119- styles . hardShadowHover ,
120- "relative border border-[#111111] dark:border-[#F9F9F7]" , // 显式边框
121- "flex flex-col" ,
122- ) }
123- >
124- { /* Header Bar */ }
125- < div className = "flex items-center justify-between px-3 py-2 border-b border-[#111111] dark:border-[#F9F9F7] bg-[#111111] dark:bg-[#F9F9F7]" >
126- < span className = "font-mono text-[10px] uppercase tracking-widest font-bold text-[#F9F9F7] dark:text-[#111111]" >
127- The Daily Feed
128- </ span >
129- < div className = "flex items-center gap-2" >
130- < button
131- onClick = { handleToggle }
132- onPointerDown = { ( e ) => e . stopPropagation ( ) }
133- className = "text-[#F9F9F7] dark:text-[#111111] hover:text-[#CC0000] transition-colors"
134- aria-label = "Minimize"
135- >
136- < ChevronUp className = "w-3.5 h-3.5" />
137- </ button >
138- < button
139- onClick = { handleDismiss }
140- onPointerDown = { ( e ) => e . stopPropagation ( ) }
141- className = "text-[#F9F9F7] dark:text-[#111111] hover:text-[#CC0000] transition-colors"
142- aria-label = "Close"
143- >
144- < X className = "w-3.5 h-3.5" />
145- </ button >
146- </ div >
147- </ div >
148-
149- { /* Content */ }
150- < div className = "p-0" >
151- { /* 图片区域 - 默认为灰度(暗黑模式除外) */ }
152- < div className = "relative aspect-[16/9] border-b border-[#111111] dark:border-[#F9F9F7] overflow-hidden group" >
153- < Image
154- src = { currentEvent . coverUrl }
155- alt = { currentEvent . name }
156- fill
157- className = "object-cover grayscale dark:grayscale-0 group-hover:grayscale-0 group-hover:sepia transition-all duration-500"
158- sizes = "280px"
159- draggable = { false }
160- />
161- { /* 突发新闻徽章 */ }
162- { ! currentEvent . deprecated && (
163- < div className = "absolute top-2 left-2 bg-[#CC0000] text-white px-2 py-0.5 text-[9px] font-mono tracking-widest uppercase font-bold" >
164- Breaking
165- </ div >
166- ) }
167- </ div >
168-
169- { /* 标题与描述 */ }
170- < div className = "p-4 bg-transparent" >
171- < h4 className = "font-serif text-lg font-bold leading-tight text-[#111111] dark:text-[#F9F9F7] mb-2" >
172- { currentEvent . name }
173- </ h4 >
174- < p className = "font-serif text-sm leading-relaxed text-[#111111]/80 dark:text-[#F9F9F7]/80 line-clamp-3 mb-4 text-justify" >
175- { currentEvent . deprecated
176- ? "This event has concluded. View the archives for full coverage."
177- : "Join us for this significant community event. Detailed coverage inside." }
178- </ p >
179-
180- { /* 操作按钮 */ }
181- { currentEvent . deprecated && currentEvent . playback ? (
182- < a
183- href = { currentEvent . playback }
184- target = "_blank"
185- rel = "noopener noreferrer"
61+ < AnimatePresence mode = "wait" >
62+ { isCollapsed ? (
63+ < motion . button
64+ key = "collapsed"
65+ initial = { { opacity : 0 } }
66+ animate = { { opacity : 1 } }
67+ exit = { { opacity : 0 } }
68+ onClick = { handleToggle }
69+ className = { cn (
70+ "group flex items-center gap-2 px-4 py-2" ,
71+ // Newsprint 风格:锐利边角,纯黑实线边框
72+ "bg-[#111111] text-[#F9F9F7] border border-[#111111]" ,
73+ "hover:bg-[#F9F9F7] hover:text-[#111111]" , // 悬停时反色
74+ "font-mono text-xs uppercase tracking-widest" ,
75+ "transition-colors duration-200" ,
76+ "shadow-[4px_4px_0px_0px_#111111] hover:translate-x-[-1px] hover:translate-y-[-1px]" ,
77+ ) }
78+ >
79+ < span className = "w-2 h-2 bg-[#CC0000] animate-pulse" />
80+ < span className = "font-bold" > Latest</ span >
81+ < ChevronUp className = "w-4 h-4 rotate-180 group-hover:-translate-y-0.5 transition-transform" />
82+ </ motion . button >
83+ ) : (
84+ /* 展开状态 - 报纸卡片 */
85+ < motion . div
86+ key = "expanded"
87+ initial = { { opacity : 0 } }
88+ animate = { { opacity : 1 } }
89+ exit = { { opacity : 0 } }
90+ className = { cn (
91+ styles . newsprintTexture ,
92+ styles . hardShadowHover ,
93+ "relative border border-[#111111] dark:border-[#F9F9F7]" , // 显式边框
94+ "flex flex-col" ,
95+ ) }
96+ >
97+ { /* Header Bar */ }
98+ < div className = "flex items-center justify-between px-3 py-2 border-b border-[#111111] dark:border-[#F9F9F7] bg-[#111111] dark:bg-[#F9F9F7]" >
99+ < span className = "font-mono text-[10px] uppercase tracking-widest font-bold text-[#F9F9F7] dark:text-[#111111]" >
100+ The Daily Feed
101+ </ span >
102+ < div className = "flex items-center gap-2" >
103+ < button
104+ onClick = { handleToggle }
186105 onPointerDown = { ( e ) => e . stopPropagation ( ) }
187- className = { cn (
188- "flex items-center justify-center gap-2 w-full px-4 py-2" ,
189- "border border-[#111111] dark:border-[#F9F9F7]" ,
190- "bg-transparent hover:bg-[#111111] hover:text-[#F9F9F7]" ,
191- "dark:hover:bg-[#F9F9F7] dark:hover:text-[#111111]" ,
192- "font-mono text-xs uppercase tracking-widest font-bold" ,
193- "transition-colors duration-200" ,
194- ) }
106+ className = "text-[#F9F9F7] dark:text-[#111111] hover:text-[#CC0000] transition-colors"
107+ aria-label = "Minimize"
195108 >
196- < Play className = "w-3 h-3" />
197- < span > Watch Replay</ span >
198- </ a >
199- ) : (
200- < a
201- href = { currentEvent . discord }
202- target = "_blank"
203- rel = "noopener noreferrer"
109+ < ChevronUp className = "w-3.5 h-3.5" />
110+ </ button >
111+ < button
112+ onClick = { handleDismiss }
204113 onPointerDown = { ( e ) => e . stopPropagation ( ) }
205- className = { cn (
206- "flex items-center justify-center gap-2 w-full px-4 py-2" ,
207- "bg-[#111111] text-[#F9F9F7]" ,
208- "hover:bg-[#CC0000] hover:border-[#CC0000]" ,
209- "font-mono text-xs uppercase tracking-widest font-bold" ,
210- "transition-colors duration-200" ,
211- ) }
114+ className = "text-[#F9F9F7] dark:text-[#111111] hover:text-[#CC0000] transition-colors"
115+ aria-label = "Close"
212116 >
213- < span > Read More</ span >
214- < ExternalLink className = "w-3 h-3" />
215- </ a >
216- ) }
117+ < X className = "w-3.5 h-3.5" />
118+ </ button >
119+ </ div >
217120 </ div >
218- </ div >
219- </ div >
220- ) }
221- </ div >
121+
122+ { /* Content */ }
123+ < div className = "p-0" >
124+ { /* 图片区域 - 默认为灰度(暗黑模式除外) */ }
125+ < div className = "relative aspect-[16/9] border-b border-[#111111] dark:border-[#F9F9F7] overflow-hidden group" >
126+ < Image
127+ src = { currentEvent . coverUrl }
128+ alt = { currentEvent . name }
129+ fill
130+ className = "object-cover grayscale dark:grayscale-0 group-hover:grayscale-0 group-hover:sepia transition-all duration-500"
131+ sizes = "280px"
132+ draggable = { false }
133+ />
134+ { /* 突发新闻徽章 */ }
135+ { ! currentEvent . deprecated && (
136+ < div className = "absolute top-2 left-2 bg-[#CC0000] text-white px-2 py-0.5 text-[9px] font-mono tracking-widest uppercase font-bold" >
137+ Breaking
138+ </ div >
139+ ) }
140+ </ div >
141+
142+ { /* 标题与描述 */ }
143+ < div className = "p-4 bg-transparent" >
144+ < h4 className = "font-serif text-lg font-bold leading-tight text-[#111111] dark:text-[#F9F9F7] mb-2" >
145+ { currentEvent . name }
146+ </ h4 >
147+ < p className = "font-serif text-sm leading-relaxed text-[#111111]/80 dark:text-[#F9F9F7]/80 line-clamp-3 mb-4 text-justify" >
148+ { currentEvent . deprecated
149+ ? "This event has concluded. View the archives for full coverage."
150+ : "Join us for this significant community event. Detailed coverage inside." }
151+ </ p >
152+
153+ { /* 操作按钮 */ }
154+ { currentEvent . deprecated && currentEvent . playback ? (
155+ < a
156+ href = { currentEvent . playback }
157+ target = "_blank"
158+ rel = "noopener noreferrer"
159+ onPointerDown = { ( e ) => e . stopPropagation ( ) }
160+ className = { cn (
161+ "flex items-center justify-center gap-2 w-full px-4 py-2" ,
162+ "border border-[#111111] dark:border-[#F9F9F7]" ,
163+ "bg-transparent hover:bg-[#111111] hover:text-[#F9F9F7]" ,
164+ "dark:hover:bg-[#F9F9F7] dark:hover:text-[#111111]" ,
165+ "font-mono text-xs uppercase tracking-widest font-bold" ,
166+ "transition-colors duration-200" ,
167+ ) }
168+ >
169+ < Play className = "w-3 h-3" />
170+ < span > Watch Replay</ span >
171+ </ a >
172+ ) : (
173+ < a
174+ href = { currentEvent . discord }
175+ target = "_blank"
176+ rel = "noopener noreferrer"
177+ onPointerDown = { ( e ) => e . stopPropagation ( ) }
178+ className = { cn (
179+ "flex items-center justify-center gap-2 w-full px-4 py-2" ,
180+ "bg-[#111111] text-[#F9F9F7]" ,
181+ "hover:bg-[#CC0000] hover:border-[#CC0000]" ,
182+ "font-mono text-xs uppercase tracking-widest font-bold" ,
183+ "transition-colors duration-200" ,
184+ ) }
185+ >
186+ < span > Read More</ span >
187+ < ExternalLink className = "w-3 h-3" />
188+ </ a >
189+ ) }
190+ </ div >
191+ </ div >
192+ </ motion . div >
193+ ) }
194+ </ AnimatePresence >
195+ </ motion . div >
222196 ) ;
223197}
224198
0 commit comments