+
+
{t('延迟对比', 'Latency Comparison')}
+
+ {t('对比两个不同目标的全球延迟表现', 'Compare global latency performance between two targets')}
+
+
+
+
+
+
+
+
+
+ {hasResults && (
+ <>
+
+
+
+
+ | {t('节点', 'Node')} |
+ {targetA || 'A'} |
+ {targetB || 'B'} |
+ {t('差值', 'Diff')} |
+
+
+
+ {TEST_NODES.map(node => {
+ const rA = resultsA.get(node.id)
+ const rB = resultsB.get(node.id)
+ const lA = rA?.status === 'success' ? rA.latency : null
+ const lB = rB?.status === 'success' ? rB.latency : null
+ const diff = getDiff(lA, lB)
+
+ const getLatencyClass = (mine: number | null, other: number | null, status?: string) => {
+ if (status === 'testing') return 'latency-cell testing'
+ if (mine === null || other === null) return 'latency-cell'
+ if (mine < other) return 'latency-cell better'
+ if (mine > other) return 'latency-cell worse'
+ return 'latency-cell'
+ }
+
+ const formatLatency = (latency: number | null, status?: string) => {
+ if (status === 'testing' || status === 'pending') return '...'
+ if (latency === null) return '-'
+ return `${latency.toFixed(0)} ms`
+ }
+
+ return (
+
+ |
+
+ {node.city || node.name}
+ {node.country}
+
+ |
+
+ {formatLatency(lA, rA?.status)}
+ |
+
+ {formatLatency(lB, rB?.status)}
+ |
+
+ {getDiffDisplay(diff)}
+ |
+
+ )
+ })}
+
+
+
+
+ {!testing && (summary.winsA > 0 || summary.winsB > 0) && (
+
+
+ {t('A 胜出节点', 'A Wins')}
+ summary.winsB ? 'winner-a' : ''}`}>
+ {summary.winsA}
+
+
+
+ {t('B 胜出节点', 'B Wins')}
+ summary.winsA ? 'winner-b' : ''}`}>
+ {summary.winsB}
+
+
+
+ {t('平均差值', 'Avg Diff')}
+
+ {summary.avgDiff > 0 ? '+' : ''}{summary.avgDiff.toFixed(0)} ms
+
+
+
+ )}
+ >
+ )}
+
+ )
+}
diff --git a/src/client/components/FloatingHeader.css b/src/client/components/FloatingHeader.css
index 80dde68..9057b4b 100644
--- a/src/client/components/FloatingHeader.css
+++ b/src/client/components/FloatingHeader.css
@@ -30,19 +30,43 @@
opacity: 1;
}
-.header-title {
+/* Brand Section - Premium Style */
+.header-brand {
display: flex;
align-items: center;
- gap: 0.6rem;
- font-weight: 700;
- font-size: 1.15rem;
+ gap: 10px;
+ text-decoration: none;
+ font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', sans-serif;
+ font-weight: 600;
+ font-size: 1.1rem;
+ letter-spacing: -0.02em;
color: var(--text-color);
- white-space: nowrap;
+ transition: all 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
}
-.header-title .title-icon {
+.header-brand:hover {
+ opacity: 0.85;
+}
+
+.header-brand .title-icon {
font-size: 1.4rem;
- filter: drop-shadow(0 0 8px var(--accent-glow));
+ line-height: 1;
+ filter: drop-shadow(0 0 10px rgba(60, 130, 250, 0.35));
+ transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
+}
+
+.header-brand:hover .title-icon {
+ transform: rotate(8deg) scale(1.08);
+ filter: drop-shadow(0 0 16px rgba(60, 130, 250, 0.5));
+}
+
+.header-brand .brand-text {
+ color: var(--text-color);
+}
+
+.header-nav {
+ margin-left: 1.5rem;
+ margin-right: auto;
}
.header-controls {
diff --git a/src/client/components/FloatingHeader.tsx b/src/client/components/FloatingHeader.tsx
index 7fdea83..0e48410 100644
--- a/src/client/components/FloatingHeader.tsx
+++ b/src/client/components/FloatingHeader.tsx
@@ -1,12 +1,21 @@
import { useState, useEffect } from 'react'
+import { Link, useLocation, useNavigate } from 'react-router-dom'
import ThemeSwitcher from './ThemeSwitcher'
import LanguageSwitcher from './LanguageSwitcher'
+import LiquidGlassMenu from './LiquidGlassMenu'
import { useLanguage } from '../contexts/LanguageContext'
import './FloatingHeader.css'
export default function FloatingHeader() {
const [isScrolled, setIsScrolled] = useState(false)
const { t } = useLanguage()
+ const location = useLocation()
+ const navigate = useNavigate()
+
+ const menuItems = [
+ { key: '/', label: t('首页', 'Home') },
+ { key: '/compare', label: t('延迟对比', 'Compare') }
+ ]
useEffect(() => {
const handleScroll = () => {
@@ -21,10 +30,18 @@ export default function FloatingHeader() {
return (