diff --git a/ui/src/index.css b/ui/src/index.css index 86280ca..2d76230 100644 --- a/ui/src/index.css +++ b/ui/src/index.css @@ -13,17 +13,20 @@ --accent-text: #1d4ed8; } +/* Dark palette tuned to Perspective's "Pro Dark" theme: + bg #242526, tooltip #2a2c2f, gridline #3b3f46, inactive #61656e, + inactive border #4c505b, active #2770a9, legend #c5c9d0. */ .dark { - --bg-primary: #111827; - --bg-secondary: #1f2937; - --bg-tertiary: #374151; - --text-primary: #f9fafb; - --text-secondary: #e5e7eb; - --text-muted: #6b7280; - --border-color: #374151; - --border-light: #1f2937; - --accent-bg: #1e3a5f; - --accent-text: #60a5fa; + --bg-primary: #242526; + --bg-secondary: #2a2c2f; + --bg-tertiary: #3b3f46; + --text-primary: #ffffff; + --text-secondary: #c5c9d0; + --text-muted: #61656e; + --border-color: #4c505b; + --border-light: #3b3f46; + --accent-bg: rgba(39, 113, 170, 0.32); + --accent-text: #4778c2; } body { margin: 0; background-color: var(--bg-primary); color: var(--text-primary); } @@ -47,19 +50,29 @@ body { margin: 0; background-color: var(--bg-primary); color: var(--text-primary .dark .text-blue-700 { color: var(--accent-text); } .dark .border-blue-300 { border-color: var(--accent-text); } .dark .hover\:bg-blue-50:hover { background-color: var(--accent-bg); } -.dark .bg-green-50 { background-color: #064e3b; } -.dark .text-green-600 { color: #34d399; } -.dark .text-green-700 { color: #34d399; } -.dark .text-green-400 { color: #34d399; } -.dark .bg-yellow-50 { background-color: #451a03; } -.dark .text-yellow-700 { color: #fbbf24; } -.dark .bg-purple-50 { background-color: #1e1b4b; } -.dark .text-purple-700 { color: #a78bfa; } -.dark .bg-red-50 { background-color: #450a0a; } -.dark .text-red-700 { color: #f87171; } + +/* Status accents — desaturated to sit on Pro Dark's neutral background */ +.dark .bg-green-50 { background-color: #1a3d2c; } +.dark .text-green-600 { color: #6ee7b7; } +.dark .text-green-700 { color: #6ee7b7; } +.dark .text-green-400 { color: #6ee7b7; } +.dark .bg-yellow-50 { background-color: #3a2e14; } +.dark .text-yellow-700 { color: #f5c66f; } +.dark .bg-amber-50 { background-color: #3a2e14; } +.dark .border-amber-200 { border-color: #5a4a26; } +.dark .text-amber-700 { color: #f5c66f; } +.dark .text-amber-800 { color: #f5c66f; } +.dark .bg-purple-50 { background-color: #2a1f3d; } +.dark .text-purple-600 { color: #c4a8e8; } +.dark .text-purple-700 { color: #c4a8e8; } +.dark .bg-red-50 { background-color: #3d1f1f; } +.dark .text-red-600 { color: #ff9485; } +.dark .text-red-700 { color: #ff9485; } + .dark .border-gray-100 { border-color: var(--border-light); } .dark .border-gray-200 { border-color: var(--border-color); } .dark .border-gray-300 { border-color: var(--border-color); } +.dark .border-blue-100 { border-color: var(--border-color); } .dark .border-b { border-color: var(--border-color); } .dark .border-t { border-color: var(--border-color); } .dark .border-r { border-color: var(--border-color); } @@ -73,8 +86,8 @@ body { margin: 0; background-color: var(--bg-primary); color: var(--text-primary .dark .hover\:border-gray-300:hover { border-color: var(--border-color); } .dark .hover\:border-gray-400:hover { border-color: var(--border-color); } .dark .focus\:border-gray-300:focus { border-color: var(--border-color); } -.dark ::selection { background-color: var(--accent-bg); color: var(--accent-text); } -.dark input { background-color: var(--bg-secondary); color: var(--text-primary); } -.dark select { background-color: var(--bg-secondary); color: var(--text-primary); } -.dark textarea { background-color: var(--bg-secondary); color: var(--text-primary); } +.dark ::selection { background-color: var(--accent-bg); color: var(--text-primary); } +.dark input { background-color: var(--bg-secondary); color: var(--text-primary); border-color: var(--border-color); } +.dark select { background-color: var(--bg-secondary); color: var(--text-primary); border-color: var(--border-color); } +.dark textarea { background-color: var(--bg-secondary); color: var(--text-primary); border-color: var(--border-color); } .dark .bg-transparent { background-color: transparent; } diff --git a/ui/src/views/Forecast.jsx b/ui/src/views/Forecast.jsx index e9f649f..3017291 100644 --- a/ui/src/views/Forecast.jsx +++ b/ui/src/views/Forecast.jsx @@ -1,4 +1,5 @@ import { useState, useEffect, useRef } from 'react' +import useTheme from '../theme.jsx' const LAYOUT_KEY = (vid) => `pf_layout_v${vid}` // last-used layout (auto restore) const LAYOUTS_KEY = (vid) => `pf_layouts_v${vid}` // named layout list @@ -29,6 +30,7 @@ function cleanLayout(cfg, validCols) { } export default function Forecast() { + const { dark } = useTheme() const [sources, setSources] = useState([]) const [sourceId, setSourceId] = useState('') const [versions, setVersions] = useState([]) @@ -105,6 +107,12 @@ export default function Forecast() { initViewer(versionId, sourceId) }, [versionId, sourceId]) + useEffect(() => { + if (viewerRef.current) { + viewerRef.current.setAttribute('theme', dark ? 'Pro Dark' : 'Pro Light') + } + }, [dark, versionId]) + useEffect(() => { const blank = Object.fromEntries(Object.keys(slice).map(k => [k, ''])) setRecodeSet(blank) @@ -196,6 +204,7 @@ export default function Forecast() { : await worker.table([], { name: tableName, index: 'pf_id' }) await viewer.load(worker) + viewer.setAttribute('theme', dark ? 'Pro Dark' : 'Pro Light') // restore last-used layout or build default const saved = localStorage.getItem(LAYOUT_KEY(vid))