diff --git a/src/App.css b/src/App.css index 991c1ee..53e7b79 100644 --- a/src/App.css +++ b/src/App.css @@ -48,9 +48,9 @@ --bg-tertiary: var(--border); } -/* Dark mode */ +/* OS dark preference — only when no manual theme is set */ @media (prefers-color-scheme: dark) { - :root:not([data-theme="light"]) { + :root:not([data-theme]) { --bg: #0f172a; --surface: #1e293b; --border: #334155; @@ -80,7 +80,7 @@ } } -/* Manual theme override */ +/* Explicit dark theme (user preference overrides OS) */ :root[data-theme="dark"] { --bg: #0f172a; --surface: #1e293b; @@ -2322,416 +2322,6 @@ tr.editing td { } } -/* ========== Slides Builder ========== */ -.slides-builder { - padding: 24px; - max-width: 1400px; - margin: 0 auto; -} - -.slides-toolbar { - display: flex; - gap: 12px; - margin-bottom: 24px; - flex-wrap: wrap; -} - -.btn-primary, .btn-secondary { - display: inline-flex; - align-items: center; - gap: 8px; - padding: 10px 18px; - border-radius: 8px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: all 0.15s ease; - border: none; -} - -.btn-primary { - background: var(--accent); - color: var(--text-inverse); -} - -.btn-primary:hover { - background: var(--accent); -} - -.btn-secondary { - background: var(--bg-secondary); - color: var(--text-primary); - border: 1px solid var(--border); -} - -.btn-secondary:hover { - background: var(--bg-tertiary); -} - -.slides-workspace { - display: grid; - grid-template-columns: 280px 1fr; - gap: 24px; - min-height: 600px; -} - -.slides-list { - background: var(--bg-secondary); - border-radius: 12px; - padding: 16px; - border: 1px solid var(--border); -} - -.slides-list h3 { - font-size: 14px; - color: var(--text-secondary); - margin-bottom: 16px; - font-weight: 500; -} - -.empty-slides { - text-align: center; - padding: 40px 20px; - color: var(--text-secondary); -} - -.empty-slides button { - margin-top: 16px; - padding: 8px 16px; - background: var(--accent); - color: var(--text-inverse); - border: none; - border-radius: 6px; - cursor: pointer; -} - -.slides-thumbnails { - display: flex; - flex-direction: column; - gap: 8px; -} - -.slide-thumbnail { - display: grid; - grid-template-columns: 32px 32px 1fr auto; - align-items: center; - gap: 8px; - padding: 12px; - background: var(--bg-primary); - border-radius: 8px; - cursor: pointer; - transition: all 0.15s ease; - border: 2px solid transparent; -} - -.slide-thumbnail:hover { - border-color: var(--border); -} - -.slide-thumbnail.active { - border-color: var(--accent); - background: rgba(59, 130, 246, 0.05); -} - -.slide-thumbnail .slide-number { - width: 24px; - height: 24px; - background: var(--bg-tertiary); - border-radius: 6px; - display: flex; - align-items: center; - justify-content: center; - font-size: 12px; - font-weight: 600; - color: var(--text-secondary); -} - -.slide-thumbnail .slide-icon { - font-size: 18px; -} - -.slide-thumbnail .slide-title-preview { - font-size: 13px; - font-weight: 500; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.slide-thumbnail .slide-actions { - display: flex; - gap: 4px; - opacity: 0; - transition: opacity 0.15s ease; -} - -.slide-thumbnail:hover .slide-actions { - opacity: 1; -} - -.slide-actions button { - width: 24px; - height: 24px; - border: none; - background: var(--bg-tertiary); - border-radius: 4px; - cursor: pointer; - font-size: 12px; - color: var(--text-secondary); -} - -.slide-actions button:hover { - background: var(--border); -} - -.slide-actions button.delete:hover { - background: var(--danger-light); - color: var(--danger); -} - -/* Slide Editor */ -.slide-editor { - background: var(--bg-secondary); - border-radius: 12px; - padding: 24px; - border: 1px solid var(--border); -} - -.editor-section { - margin-bottom: 20px; -} - -.editor-section label { - display: block; - font-size: 13px; - font-weight: 500; - color: var(--text-secondary); - margin-bottom: 8px; -} - -.editor-section input[type="text"], -.editor-section input[type="date"], -.editor-section select { - width: 100%; - padding: 10px 12px; - border: 1px solid var(--border); - border-radius: 8px; - font-size: 14px; - background: var(--bg-primary); - color: var(--text-primary); -} - -.editor-section input:focus, -.editor-section select:focus { - outline: 2px solid var(--accent); - outline-offset: -1px; - border-color: var(--accent); -} - -.editor-row { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 16px; -} - -.chart-type-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 8px; -} - -.chart-type-btn { - display: flex; - flex-direction: column; - align-items: center; - gap: 6px; - padding: 16px 12px; - border: 2px solid var(--border); - border-radius: 10px; - background: var(--bg-primary); - cursor: pointer; - transition: all 0.15s ease; -} - -.chart-type-btn:hover { - border-color: var(--accent); -} - -.chart-type-btn.active { - border-color: var(--accent); - background: rgba(59, 130, 246, 0.05); -} - -.chart-type-btn .chart-icon { - font-size: 24px; -} - -.chart-type-btn span:last-child { - font-size: 12px; - font-weight: 500; - color: var(--text-secondary); -} - -.slide-preview-box { - margin-top: 24px; - padding-top: 24px; - border-top: 1px solid var(--border); -} - -.slide-preview-box h4 { - font-size: 13px; - font-weight: 500; - color: var(--text-secondary); - margin-bottom: 16px; -} - -.preview-chart { - background: var(--bg-primary); - border-radius: 8px; - padding: 16px; - height: 200px; -} - -.preview-kpis { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 12px; -} - -.preview-kpi { - background: var(--bg-primary); - border-radius: 8px; - padding: 16px; - text-align: center; -} - -.preview-kpi .kpi-value { - font-size: 1.5rem; - font-weight: 700; - color: var(--accent); -} - -.preview-kpi .kpi-label { - font-size: 12px; - color: var(--text-secondary); - margin-top: 4px; -} - -/* Preview Fullscreen */ -.preview-fullscreen { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: linear-gradient(135deg, var(--text-primary) 0%, var(--dark-surface) 100%); - z-index: 1000; - display: flex; - flex-direction: column; -} - -.preview-slide { - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 60px; -} - -.preview-title { - color: var(--bg); - font-size: 2.5rem; - font-weight: 600; - margin-bottom: 40px; - text-align: center; -} - -.preview-content { - width: 100%; - max-width: 900px; -} - -.preview-content .preview-chart { - height: 350px; - background: rgba(255, 255, 255, 0.03); - border-radius: 16px; - padding: 30px; -} - -.preview-content .preview-kpis .preview-kpi { - background: rgba(255, 255, 255, 0.05); - padding: 30px; -} - -.preview-content .preview-kpis .kpi-value { - font-size: 2.5rem; -} - -.preview-content .preview-kpis .kpi-label { - color: var(--dark-muted); -} - -.preview-footer { - color: var(--text-muted); - font-size: 14px; - margin-top: 40px; -} - -.preview-controls { - display: flex; - justify-content: center; - gap: 12px; - padding: 20px; - background: rgba(0, 0, 0, 0.3); -} - -.preview-controls button { - padding: 10px 24px; - background: rgba(255, 255, 255, 0.1); - color: var(--text-inverse); - border: none; - border-radius: 8px; - cursor: pointer; - font-size: 14px; -} - -.preview-controls button:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.2); -} - -.preview-controls button:disabled { - opacity: 0.3; - cursor: not-allowed; -} - -@media (max-width: 768px) { - .slides-workspace { - grid-template-columns: 1fr; - } - - .slides-list { - max-height: 200px; - overflow-y: auto; - } - - .editor-row { - grid-template-columns: 1fr; - } - - .chart-type-grid { - grid-template-columns: repeat(4, 1fr); - } - - .preview-slide { - padding: 30px; - } - - .preview-title { - font-size: 1.5rem; - } -} - /* Chart Export Button & Header */ .exportable-chart-wrapper { position: relative; diff --git a/src/App.tsx b/src/App.tsx index b866c86..21e89da 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -250,6 +250,11 @@ function App() { {t('app.offline') || 'Offline'} + {cacheInfo && ( + + {` (cached ${new Date(cacheInfo.timestamp || '').toLocaleString()})`} + + )} )} + shift(L.dir==='rtl' ? 1 : -1)} disabled={L.dir==='rtl' ? year>=maxY : year<=minY} className="alt-yr-btn"> @@ -262,7 +262,7 @@ function PeriodCard({ role, hint, start, end, variant, onChange, availableYears, {periodNameL(start, end, L)} {dateRangeTextL(start, end, L)} - setOpen(v => !v)} aria-expanded={open}> + setOpen(v => !v)} aria-expanded={open} aria-controls="period-picker-panel"> {open ? L.close : L.changePeriod} @@ -443,7 +443,10 @@ export default function PeriodSelectorDemo({ data, seasons, includeVAT, allowedM }, [data, prevData, currData, prevStart, prevEnd, currStart, currEnd, metric, getVal]); const baseOpts = useMemo(() => createBaseOptions(false), []); - const chartOpts: any = { ...baseOpts, plugins:{ ...baseOpts.plugins, legend:{ position:'top', align:'end', labels:{ boxWidth:12, padding:12 } } } }; + const { chartOpts } = useMemo(() => { + const chartOpts: any = { ...baseOpts, plugins:{ ...baseOpts.plugins, legend:{ position:'top', align:'end', labels:{ boxWidth:12, padding:12 } } } }; + return { chartOpts }; + }, [baseOpts]); const metricOpts = [ { value:'revenue', label:L.revenue }, { value:'visitors', label:L.visitors }, @@ -477,7 +480,7 @@ export default function PeriodSelectorDemo({ data, seasons, includeVAT, allowedM