From f9fb197cdb54f9fd08703b80a36487255e73701c Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Fri, 22 May 2026 04:07:22 +0200 Subject: [PATCH] style(frontend): prettier charts, drop redundant frame, format net rates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Sparkline: multi-stop gradient fill, soft drop-shadow under the line, dashed grid, glowing pulse on the latest-point marker, pill-shaped tooltip with dashed crosshair - XrayMetricsModal: glow + pulse on the observatory alive dot, monospace stamps/listen text - SystemHistoryModal: keep just the modal's frame around the chart (the inner wrapper I'd added stacked a second border on top); strip the decimal from Net Up/Down (25.63 KB/s → 25 KB/s) only on this chart's formatter --- frontend/src/components/Sparkline.css | 24 ++++-- frontend/src/components/Sparkline.tsx | 80 +++++++++++++++---- .../src/pages/index/SystemHistoryModal.css | 24 +++++- .../src/pages/index/SystemHistoryModal.tsx | 2 +- frontend/src/pages/index/XrayMetricsModal.css | 16 ++++ 5 files changed, 123 insertions(+), 23 deletions(-) diff --git a/frontend/src/components/Sparkline.css b/frontend/src/components/Sparkline.css index 598924b3..ad0e012c 100644 --- a/frontend/src/components/Sparkline.css +++ b/frontend/src/components/Sparkline.css @@ -5,16 +5,30 @@ .sparkline-svg .cpu-grid-y-text, .sparkline-svg .cpu-grid-x-text { - fill: rgba(0, 0, 0, 0.65); + fill: rgba(0, 0, 0, 0.55); + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + letter-spacing: 0.2px; } .sparkline-svg .cpu-grid-text { fill: rgba(0, 0, 0, 0.88); } +.sparkline-svg .cpu-grid-line { + stroke: rgba(0, 0, 0, 0.08); +} + +.sparkline-svg .cpu-tooltip-text { + pointer-events: none; +} + +.sparkline-svg .cpu-tooltip-pill { + filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.18)); +} + body.dark .sparkline-svg .cpu-grid-y-text, body.dark .sparkline-svg .cpu-grid-x-text { - fill: rgba(255, 255, 255, 0.85); + fill: rgba(255, 255, 255, 0.7); } body.dark .sparkline-svg .cpu-grid-text { @@ -22,9 +36,9 @@ body.dark .sparkline-svg .cpu-grid-text { } body.dark .sparkline-svg .cpu-grid-line { - stroke: rgba(255, 255, 255, 0.12); + stroke: rgba(255, 255, 255, 0.10); } -body.dark .sparkline-svg .cpu-grid-h-line { - stroke: rgba(255, 255, 255, 0.35); +body.dark .sparkline-svg .cpu-tooltip-pill { + filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.6)); } diff --git a/frontend/src/components/Sparkline.tsx b/frontend/src/components/Sparkline.tsx index 4f0d8616..c49db262 100644 --- a/frontend/src/components/Sparkline.tsx +++ b/frontend/src/components/Sparkline.tsx @@ -38,10 +38,10 @@ export default function Sparkline({ strokeWidth = 2, maxPoints = 120, showGrid = true, - gridColor = 'rgba(0,0,0,0.1)', - fillOpacity = 0.15, + gridColor = 'rgba(0,0,0,0.08)', + fillOpacity = 0.22, showMarker = true, - markerRadius = 2.8, + markerRadius = 3, showAxes = false, yTickStep = 25, tickCountX = 4, @@ -60,7 +60,10 @@ export default function Sparkline({ const [hoverIdx, setHoverIdx] = useState(-1); const reactId = useId(); - const gradId = `spkGrad-${reactId.replace(/[^a-zA-Z0-9]/g, '')}`; + const safeId = reactId.replace(/[^a-zA-Z0-9]/g, ''); + const gradId = `spkGrad-${safeId}`; + const shadowId = `spkShadow-${safeId}`; + const glowId = `spkGlow-${safeId}`; useEffect(() => { const el = svgRef.current; @@ -211,6 +214,15 @@ export default function Sparkline({ return `${val}${lab ? ' • ' + lab : ''}`; }, [hoverIdx, dataSlice, labelsSlice, tooltipFormatter, yFormatter]); + const tooltipPillWidth = Math.max(48, hoverText.length * 6.2 + 14); + const hoverPoint = hoverIdx >= 0 ? pointsArr[hoverIdx] : null; + const tooltipX = hoverPoint + ? Math.max( + paddingLeft + 2, + Math.min(effectiveVbWidth - paddingRight - tooltipPillWidth - 2, hoverPoint[0] - tooltipPillWidth / 2), + ) + : 0; + return ( - + + + + + + + + + + + + + + + + + {showGrid && ( @@ -240,6 +268,7 @@ export default function Sparkline({ y2={g.y2} stroke={gridColor} strokeWidth={1} + strokeDasharray="3 5" className="cpu-grid-line" /> ))} @@ -252,10 +281,10 @@ export default function Sparkline({ {tk.label} @@ -267,7 +296,7 @@ export default function Sparkline({ x={tk.x} y={paddingTop + drawHeight + 14} textAnchor="middle" - fontSize={10} + fontSize={10.5} > {tk.label} @@ -283,9 +312,16 @@ export default function Sparkline({ strokeWidth={strokeWidth} strokeLinecap="round" strokeLinejoin="round" + filter={`url(#${shadowId})`} /> {showMarker && lastPoint && ( - + <> + + + + + + )} {showTooltip && hoverIdx >= 0 && pointsArr[hoverIdx] && ( @@ -296,16 +332,32 @@ export default function Sparkline({ x2={pointsArr[hoverIdx][0]} y1={paddingTop} y2={paddingTop + drawHeight} - stroke="rgba(0,0,0,0.2)" + stroke={stroke} + strokeOpacity={0.45} strokeWidth={1} + strokeDasharray="3 4" + /> + + + - {hoverText} diff --git a/frontend/src/pages/index/SystemHistoryModal.css b/frontend/src/pages/index/SystemHistoryModal.css index 1e39011e..fc5a11e1 100644 --- a/frontend/src/pages/index/SystemHistoryModal.css +++ b/frontend/src/pages/index/SystemHistoryModal.css @@ -8,11 +8,29 @@ } .cpu-chart-wrap { - padding: 8px 16px 16px; + margin: 8px 8px 16px; + padding: 16px 18px 18px; + border-radius: 14px; + background: linear-gradient(180deg, rgba(99, 102, 241, 0.05), rgba(99, 102, 241, 0)); + border: 1px solid rgba(99, 102, 241, 0.12); + box-shadow: 0 2px 12px rgba(99, 102, 241, 0.06); +} + +body.dark .cpu-chart-wrap { + background: linear-gradient(180deg, rgba(129, 140, 248, 0.08), rgba(129, 140, 248, 0)); + border-color: rgba(129, 140, 248, 0.16); + box-shadow: 0 2px 16px rgba(0, 0, 0, 0.25); +} + +html[data-theme='ultra-dark'] .cpu-chart-wrap { + background: linear-gradient(180deg, rgba(129, 140, 248, 0.05), rgba(129, 140, 248, 0)); + border-color: rgba(129, 140, 248, 0.10); } .cpu-chart-meta { - margin-bottom: 10px; - font-size: 11px; + margin-bottom: 12px; + font-size: 11.5px; opacity: 0.65; + letter-spacing: 0.3px; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; } diff --git a/frontend/src/pages/index/SystemHistoryModal.tsx b/frontend/src/pages/index/SystemHistoryModal.tsx index 4c33b315..84b00643 100644 --- a/frontend/src/pages/index/SystemHistoryModal.tsx +++ b/frontend/src/pages/index/SystemHistoryModal.tsx @@ -35,7 +35,7 @@ const METRICS: MetricDef[] = [ function unitFormatter(unit: string, activeKey: string): (v: number) => string { if (unit === 'B/s') { - return (v) => `${SizeFormatter.sizeFormat(Math.max(0, Number(v) || 0))}/s`; + return (v) => `${SizeFormatter.sizeFormat(Math.max(0, Number(v) || 0)).replace(/\.\d+/, '')}/s`; } if (unit === '%') { return (v) => `${Number(v).toFixed(1)}%`; diff --git a/frontend/src/pages/index/XrayMetricsModal.css b/frontend/src/pages/index/XrayMetricsModal.css index 3ee310ca..8b95c8da 100644 --- a/frontend/src/pages/index/XrayMetricsModal.css +++ b/frontend/src/pages/index/XrayMetricsModal.css @@ -29,6 +29,8 @@ .obs-stamp { opacity: 0.7; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + font-size: 11.5px; } .obs-dot { @@ -38,16 +40,30 @@ border-radius: 50%; margin-right: 6px; vertical-align: middle; + box-shadow: 0 0 0 3px rgba(82, 196, 26, 0.18); } .obs-dot.is-alive { background: #52c41a; + box-shadow: 0 0 0 3px rgba(82, 196, 26, 0.22); + animation: obs-dot-pulse 2.2s ease-in-out infinite; } .obs-dot.is-dead { background: #f5222d; + box-shadow: 0 0 0 3px rgba(245, 34, 45, 0.22); +} + +@keyframes obs-dot-pulse { + 0%, 100% { box-shadow: 0 0 0 3px rgba(82, 196, 26, 0.22); } + 50% { box-shadow: 0 0 0 6px rgba(82, 196, 26, 0.06); } +} + +@media (prefers-reduced-motion: reduce) { + .obs-dot.is-alive { animation: none; } } .listen-tag { opacity: 0.7; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }