@@ -3,6 +3,48 @@ import { flatNavItems } from '../nav';
33import { useOutline } from './OutlineContext' ;
44import { OutlineProvider } from './OutlineProvider' ;
55
6+ function BackToTop ( ) {
7+ const [ visible , setVisible ] = useState ( false ) ;
8+
9+ useEffect ( ( ) => {
10+ const main = document . querySelector ( 'main' ) ;
11+ if ( ! main ) return ;
12+
13+ const handleScroll = ( ) => {
14+ setVisible ( main . scrollTop > 400 ) ;
15+ } ;
16+
17+ main . addEventListener ( 'scroll' , handleScroll ) ;
18+ return ( ) => main . removeEventListener ( 'scroll' , handleScroll ) ;
19+ } , [ ] ) ;
20+
21+ const scrollToTop = useCallback ( ( ) => {
22+ const main = document . querySelector ( 'main' ) ;
23+ if ( main ) {
24+ main . scrollTo ( { top : 0 , behavior : 'smooth' } ) ;
25+ }
26+ } , [ ] ) ;
27+
28+ if ( ! visible ) return null ;
29+
30+ return (
31+ < button
32+ onClick = { scrollToTop }
33+ className = "fixed bottom-6 right-6 z-50 flex items-center justify-center w-10 h-10 rounded-lg bg-[var(--bg-panel)] border border-[var(--border-subtle)] text-[var(--text-secondary)] hover:border-[var(--terminal-green)] hover:text-[var(--terminal-green)] hover:shadow-[0_0_15px_var(--terminal-green-glow)] transition-all duration-200 group"
34+ title = "回到顶部"
35+ >
36+ < svg
37+ className = "w-5 h-5 transition-transform group-hover:-translate-y-0.5"
38+ fill = "none"
39+ stroke = "currentColor"
40+ viewBox = "0 0 24 24"
41+ >
42+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M5 10l7-7m0 0l7 7m-7-7v18" />
43+ </ svg >
44+ </ button >
45+ ) ;
46+ }
47+
648function ShareButtons ( { activeSectionId } : { activeSectionId : string | null } ) {
749 const [ copied , setCopied ] = useState < 'page' | 'section' | null > ( null ) ;
850
@@ -42,38 +84,38 @@ function ShareButtons({ activeSectionId }: { activeSectionId: string | null }) {
4284 < div className = "flex items-center gap-2 mb-4" >
4385 < button
4486 onClick = { copyPageLink }
45- className = "flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-lg bg-gray-900/30 border border-gray-700 text-gray-300 hover:bg-gray-800/40 hover:border-gray-600 hover:text-cyan-300 transition-colors "
87+ className = "flex items-center gap-1.5 px-3 py-1.5 text-xs font-mono rounded-md bg-[var(--bg-panel)] border border-[var(--border-subtle)] text-[var(--text-secondary)] hover:bg-[var(--bg-elevated)] hover:border-[var(--terminal-green-dim)] hover:text-[var(--terminal-green)] transition-all duration-200 "
4688 >
4789 { copied === 'page' ? (
4890 < >
49- < span className = "text-green-400 " > ✓</ span >
50- < span > 已复制 </ span >
91+ < span className = "text-[var(--terminal-green)] " > ✓</ span >
92+ < span className = "text-[var(--terminal-green)]" > copied! </ span >
5193 </ >
5294 ) : (
5395 < >
54- < span > 🔗 </ span >
55- < span > 复制本页链接 </ span >
96+ < span className = "opacity-60" > $ </ span >
97+ < span > cp link </ span >
5698 </ >
5799 ) }
58100 </ button >
59101 < button
60102 onClick = { copySectionLink }
61103 disabled = { ! activeSectionId }
62- className = { `flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-lg border transition-colors ${
104+ className = { `flex items-center gap-1.5 px-3 py-1.5 text-xs font-mono rounded-md border transition-all duration-200 ${
63105 activeSectionId
64- ? 'bg-gray-900/30 border-gray-700 text-gray-300 hover:bg-gray-800/40 hover:border-gray-600 hover:text-cyan-300 '
65- : 'bg-gray-900/10 border-gray-800 text-gray-600 cursor-not-allowed'
106+ ? 'bg-[var(--bg-panel)] border-[var(--border-subtle)] text-[var(--text-secondary)] hover:bg-[var(--bg-elevated)] hover:border-[var(--amber-dim)] hover:text-[var(--amber)] '
107+ : 'bg-[var(--bg-void)] border-[var(--border-subtle)] text-[var(--text-muted)] cursor-not-allowed opacity-50 '
66108 } `}
67109 >
68110 { copied === 'section' ? (
69111 < >
70- < span className = "text-green-400 " > ✓</ span >
71- < span > 已复制 </ span >
112+ < span className = "text-[var(--terminal-green)] " > ✓</ span >
113+ < span className = "text-[var(--terminal-green)]" > copied! </ span >
72114 </ >
73115 ) : (
74116 < >
75- < span > #</ span >
76- < span > 复制当前章节链接 </ span >
117+ < span className = "opacity-60" > #</ span >
118+ < span > cp section </ span >
77119 </ >
78120 ) }
79121 </ button >
@@ -98,26 +140,37 @@ function PageOutline({
98140 if ( items . length === 0 ) return null ;
99141
100142 return (
101- < div className = "bg-gray-900/30 border border-gray-700 rounded-xl p-4 mb-6" >
102- < div className = "flex items-center justify-between gap-3 mb-3" >
103- < div className = "text-sm font-semibold text-cyan-300" > 本页目录</ div >
104- < div className = "text-xs text-gray-500" > 点击快速跳转</ div >
143+ < div className = "bg-[var(--bg-panel)] border border-[var(--border-subtle)] rounded-lg p-4 mb-6 relative overflow-hidden" >
144+ { /* Decorative corner accent */ }
145+ < div className = "absolute top-0 left-0 w-16 h-16 opacity-20" >
146+ < div className = "absolute top-0 left-0 w-full h-[1px] bg-gradient-to-r from-[var(--terminal-green)] to-transparent" />
147+ < div className = "absolute top-0 left-0 w-[1px] h-full bg-gradient-to-b from-[var(--terminal-green)] to-transparent" />
148+ </ div >
149+
150+ < div className = "flex items-center justify-between gap-3 mb-4" >
151+ < div className = "text-sm font-mono text-[var(--terminal-green)] flex items-center gap-2" >
152+ < span className = "opacity-60" > ▸</ span >
153+ < span > 目录索引</ span >
154+ </ div >
155+ < div className = "text-xs text-[var(--text-muted)] font-mono" > // 点击跳转</ div >
105156 </ div >
106157 < div className = "flex flex-wrap gap-2" >
107- { items . map ( ( s ) => (
158+ { items . map ( ( s , index ) => (
108159 < a
109160 key = { s . id }
110161 href = { `#${ s . id } ` }
111162 onClick = { ( e ) => {
112163 e . preventDefault ( ) ;
113164 onSelectSection ( s . id ) ;
114165 } }
115- className = { `px-3 py-1.5 rounded-lg text-sm border transition-colors ${
166+ className = { `px-3 py-1.5 rounded-md text-sm font-mono border transition-all duration-200 ${
116167 activeSectionId === s . id
117- ? 'bg-cyan-500 /10 border-cyan-500 /40 text-cyan-200 '
118- : 'bg-gray-950/40 border-gray-700 text-gray-200 hover:bg-gray-800/40 hover:border-gray-600 '
168+ ? 'bg-[var(--terminal-green)] /10 border-[var(--terminal-green)] /40 text-[var(--terminal-green)] shadow-[0_0_10px_var(--terminal-green-glow)] '
169+ : 'bg-[var(--bg-void)] border-[var(--border-subtle)] text-[var(--text-secondary)] hover:bg-[var(--bg-elevated)] hover:border-[var(--border-default)] hover:text-[var(--text-primary)] '
119170 } `}
171+ style = { { animationDelay : `${ index * 30 } ms` } }
120172 >
173+ < span className = "opacity-50 mr-1" > { String ( index + 1 ) . padStart ( 2 , '0' ) } .</ span >
121174 { s . title }
122175 </ a >
123176 ) ) }
@@ -181,36 +234,45 @@ export function PageLayout({
181234
182235 return (
183236 < OutlineProvider key = { activeTab } >
237+ < BackToTop />
184238 < OutlineHeader
185239 activeSectionId = { activeSectionId }
186240 setActiveSectionId = { setActiveSectionId }
187241 onSelectSection = { onSelectSection }
188242 />
189243 { children }
190244
191- < div className = "mt-10 flex items-center justify-between gap-4" >
192- < button
193- disabled = { ! prev }
194- onClick = { ( ) => prev && onNavigate ( prev . id ) }
195- className = { `px-4 py-2 rounded-lg text-sm border transition-colors ${
196- prev
197- ? 'bg-gray-900/30 border-gray-700 text-gray-200 hover:bg-gray-800/40 hover:border-gray-600'
198- : 'bg-gray-900/10 border-gray-800 text-gray-600 cursor-not-allowed'
199- } `}
200- >
201- { prev ? `← ${ prev . label } ` : '← 没有上一页' }
202- </ button >
203- < button
204- disabled = { ! next }
205- onClick = { ( ) => next && onNavigate ( next . id ) }
206- className = { `px-4 py-2 rounded-lg text-sm border transition-colors ${
207- next
208- ? 'bg-gray-900/30 border-gray-700 text-gray-200 hover:bg-gray-800/40 hover:border-gray-600'
209- : 'bg-gray-900/10 border-gray-800 text-gray-600 cursor-not-allowed'
210- } `}
211- >
212- { next ? `${ next . label } →` : '没有下一页 →' }
213- </ button >
245+ { /* Navigation Footer */ }
246+ < div className = "mt-12 pt-6 border-t border-[var(--border-subtle)]" >
247+ < div className = "flex items-center justify-between gap-4" >
248+ < button
249+ disabled = { ! prev }
250+ onClick = { ( ) => prev && onNavigate ( prev . id ) }
251+ className = { `group flex items-center gap-2 px-4 py-2.5 rounded-md text-sm font-mono border transition-all duration-200 ${
252+ prev
253+ ? 'bg-[var(--bg-panel)] border-[var(--border-subtle)] text-[var(--text-secondary)] hover:bg-[var(--bg-elevated)] hover:border-[var(--cyber-blue-dim)] hover:text-[var(--cyber-blue)]'
254+ : 'bg-[var(--bg-void)] border-[var(--border-subtle)] text-[var(--text-muted)] cursor-not-allowed opacity-50'
255+ } `}
256+ >
257+ < span className = { `transition-transform duration-200 ${ prev ? 'group-hover:-translate-x-1' : '' } ` } > ←</ span >
258+ < span className = "max-w-[120px] truncate" > { prev ? prev . label : 'EOF' } </ span >
259+ </ button >
260+ < div className = "text-xs text-[var(--text-muted)] font-mono hidden sm:block" >
261+ // navigate
262+ </ div >
263+ < button
264+ disabled = { ! next }
265+ onClick = { ( ) => next && onNavigate ( next . id ) }
266+ className = { `group flex items-center gap-2 px-4 py-2.5 rounded-md text-sm font-mono border transition-all duration-200 ${
267+ next
268+ ? 'bg-[var(--bg-panel)] border-[var(--border-subtle)] text-[var(--text-secondary)] hover:bg-[var(--bg-elevated)] hover:border-[var(--terminal-green-dim)] hover:text-[var(--terminal-green)]'
269+ : 'bg-[var(--bg-void)] border-[var(--border-subtle)] text-[var(--text-muted)] cursor-not-allowed opacity-50'
270+ } `}
271+ >
272+ < span className = "max-w-[120px] truncate" > { next ? next . label : 'EOF' } </ span >
273+ < span className = { `transition-transform duration-200 ${ next ? 'group-hover:translate-x-1' : '' } ` } > →</ span >
274+ </ button >
275+ </ div >
214276 </ div >
215277 </ OutlineProvider >
216278 ) ;
0 commit comments