From 3ca28677ec48ce6d4d2e68a8727c09e2fe0eb068 Mon Sep 17 00:00:00 2001 From: Bartal Laearsson Date: Tue, 25 Aug 2026 21:58:46 +0100 Subject: [PATCH] add nicer theme, and next update in navbar --- static/app.js | 211 ++++++++++++++++++++++++++++++++++------------ static/index.html | 1 + static/style.css | 18 ++++ 3 files changed, 177 insertions(+), 53 deletions(-) diff --git a/static/app.js b/static/app.js index a4d3bec..7b1dcc3 100644 --- a/static/app.js +++ b/static/app.js @@ -26,18 +26,85 @@ function formatDKKShort(value) { return value.toString(); } +function getNextUpdateDate() { + const now = new Date(); + const currentMonth = now.getMonth(); + const currentYear = now.getFullYear(); + const today = now.getDate(); + + let updateMonth = currentMonth; + let updateYear = currentYear; + + if (today > 28) { + updateMonth++; + if (updateMonth > 11) { + updateMonth = 0; + updateYear++; + } + } + + const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + return `${monthNames[updateMonth]} ${updateYear}`; +} + +function updateNavbarUpdateDate() { + const container = document.getElementById('update-date-display'); + if (container) { + container.textContent = `Next update: ${getNextUpdateDate()} 28`; + } +} + function getThemeColors() { const theme = document.documentElement.getAttribute('data-theme'); const isDark = theme === 'macchiato'; + + if (isDark) { + return { + isDark: true, + textColor: '#cdd6f4', + subTextColor: '#bac2de', + gridColor: 'rgba(180, 185, 200, 0.12)', + accent: '#89b4fa', + secondary: '#94e2d5', + palette: [ + '#89b4fa', + '#fab387', + '#a6e3a1', + '#f9e2af', + '#f5c2e7', + '#cba6f7', + '#f38ba8', + '#eba0ac', + '#94e2d5', + '#89dceb', + ], + tooltipBg: 'rgba(30, 32, 48, 0.95)', + tooltipBorder: 'rgba(180, 185, 200, 0.2)', + bgTransparent: 'transparent', + }; + } + return { - isDark, - textColor: isDark ? '#e8f4ff' : '#1a1d22', - gridColor: isDark ? 'rgba(100, 220, 255, 0.1)' : 'rgba(26, 29, 34, 0.1)', - accent: isDark ? '#64dcff' : '#2c5a7a', - secondary: isDark ? '#2c5a7a' : '#64dcff', - palette: isDark - ? ['#64dcff', '#2c5a7a', '#a3b3cc', '#7886a0', '#d93d3d', '#dcad2b', '#4caf50', '#ff9800', '#9c27b0', '#00bcd4'] - : ['#2c5a7a', '#64dcff', '#595f68', '#9299a2', '#a03030', '#8a6a10', '#4caf50', '#ff9800', '#9c27b0', '#00bcd4'], + isDark: false, + textColor: '#45475a', + subTextColor: '#7f849c', + gridColor: 'rgba(73, 77, 113, 0.08)', + accent: '#89b4fa', + secondary: '#94e2d5', + palette: [ + '#89b4fa', + '#fab387', + '#a6e3a1', + '#f9e2af', + '#f5c2e7', + '#cba6f7', + '#f38ba8', + '#eba0ac', + '#94e2d5', + '#89dceb', + ], + tooltipBg: 'rgba(255, 255, 255, 0.98)', + tooltipBorder: 'rgba(73, 77, 113, 0.15)', bgTransparent: 'transparent', }; } @@ -77,34 +144,42 @@ function renderLineChart(months, massData, valueData) { textStyle: { fontFamily: 'Noto Sans', color: c.textColor }, tooltip: { trigger: 'axis', - axisPointer: { type: 'cross' } + axisPointer: { + type: 'cross', + lineStyle: { color: c.gridColor, type: 'dashed' }, + crossStyle: { color: c.gridColor }, + }, + backgroundColor: c.tooltipBg, + borderColor: c.tooltipBorder, + borderWidth: 1, + textStyle: { color: c.textColor, fontFamily: 'Noto Sans' }, }, legend: { data: ['Mass (kg)', 'Value (DKK)'], - textStyle: { color: c.textColor }, + textStyle: { color: c.textColor, fontFamily: 'Noto Sans' }, top: 0, left: 'center', - orient: 'horizontal' + orient: 'horizontal', }, grid: { left: '8%', right: '10%', bottom: 50, containLabel: true, - top: '18%' + top: '18%', }, xAxis: { type: 'category', data: xLabels, axisLine: { lineStyle: { color: c.gridColor } }, axisLabel: { - color: c.textColor, + color: c.subTextColor, interval: 0, rotate: 0, fontSize: 10, margin: 8, overflow: 'break', - width: 70 + width: 70, }, boundaryGap: false, }, @@ -115,12 +190,12 @@ function renderLineChart(months, massData, valueData) { position: 'left', splitLine: { lineStyle: { color: c.gridColor } }, axisLabel: { - color: c.accent, + color: c.subTextColor, formatter: (val) => (val >= 1e6 ? (val / 1e6).toFixed(1) + 'M' : val.toLocaleString()), - margin: 8 + margin: 8, }, - axisLine: { show: true, lineStyle: { color: c.accent } }, - nameTextStyle: { color: c.accent, padding: [0, 0, 0, 0] } + axisLine: { show: true, lineStyle: { color: c.gridColor } }, + nameTextStyle: { color: c.accent, padding: [0, 0, 0, 0] }, }, { type: 'value', @@ -128,13 +203,13 @@ function renderLineChart(months, massData, valueData) { position: 'right', splitLine: { show: false }, axisLabel: { - color: c.secondary, + color: c.subTextColor, formatter: (val) => (val >= 1e6 ? (val / 1e6).toFixed(1) + 'M' : val.toLocaleString()), - margin: 8 + margin: 8, }, - axisLine: { show: true, lineStyle: { color: c.secondary } }, - nameTextStyle: { color: c.secondary, padding: [0, 10, 0, 0] } - } + axisLine: { show: true, lineStyle: { color: c.gridColor } }, + nameTextStyle: { color: c.secondary, padding: [0, 10, 0, 0] }, + }, ], series: [ { @@ -143,9 +218,18 @@ function renderLineChart(months, massData, valueData) { yAxisIndex: 0, smooth: true, symbol: 'none', - lineStyle: { width: 2 }, + lineStyle: { width: 2, opacity: 0.9 }, itemStyle: { color: c.accent }, - areaStyle: { opacity: 0.05 }, + areaStyle: { + color: { + type: 'linear', + x: 0, y: 0, x2: 0, y2: 1, + colorStops: [ + { offset: 0, color: c.isDark ? 'rgba(137, 180, 250, 0.22)' : 'rgba(137, 180, 250, 0.18)' }, + { offset: 1, color: 'rgba(137, 180, 250, 0)' }, + ], + }, + }, data: massData, }, { @@ -154,9 +238,18 @@ function renderLineChart(months, massData, valueData) { yAxisIndex: 1, smooth: true, symbol: 'none', - lineStyle: { width: 2 }, + lineStyle: { width: 2, opacity: 0.9 }, itemStyle: { color: c.secondary }, - areaStyle: { opacity: 0.05 }, + areaStyle: { + color: { + type: 'linear', + x: 0, y: 0, x2: 0, y2: 1, + colorStops: [ + { offset: 0, color: c.isDark ? 'rgba(148, 226, 213, 0.22)' : 'rgba(148, 226, 213, 0.18)' }, + { offset: 1, color: 'rgba(148, 226, 213, 0)' }, + ], + }, + }, data: valueData, }, ], @@ -183,10 +276,14 @@ function renderTopSpeciesChart(data) { tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' }, + backgroundColor: c.tooltipBg, + borderColor: c.tooltipBorder, + borderWidth: 1, + textStyle: { color: c.textColor, fontFamily: 'Noto Sans' }, formatter: (params) => { const p = params[0]; return `${p.name}
${formatDKK(p.value)}`; - } + }, }, grid: { left: '5%', @@ -199,7 +296,7 @@ function renderTopSpeciesChart(data) { type: 'value', splitLine: { lineStyle: { color: c.gridColor, type: 'dashed' } }, axisLabel: { - color: c.textColor, + color: c.subTextColor, formatter: (val) => formatDKKShort(val), fontSize: 9, hideOverlap: true, @@ -224,25 +321,27 @@ function renderTopSpeciesChart(data) { series: [ { type: 'bar', - data: values, + data: values.map((v, i) => ({ + value: v, + itemStyle: { + color: c.palette[i], + borderRadius: [0, 4, 4, 0], + }, + })), barMaxWidth: 28, barGap: '30%', - itemStyle: { - borderRadius: [0, 4, 4, 0], - }, label: { show: true, position: 'right', formatter: (params) => formatDKKShort(params.value), - color: c.textColor, + color: c.subTextColor, fontSize: 10, distance: 8, overflow: 'break', }, emphasis: { itemStyle: { - shadowBlur: 10, - shadowColor: 'rgba(0,0,0,0.3)', + shadowBlur: 0, }, }, }, @@ -274,6 +373,10 @@ function renderBarChart(months, seriesData) { tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' }, + backgroundColor: c.tooltipBg, + borderColor: c.tooltipBorder, + borderWidth: 1, + textStyle: { color: c.textColor, fontFamily: 'Noto Sans' }, formatter: (params) => { const month = params[0]?.axisValue || ''; let html = `${month}
`; @@ -283,59 +386,63 @@ function renderBarChart(months, seriesData) { }); return html; }, - extraCssText: 'background: rgba(0,0,0,0.8); padding: 8px;' }, legend: { - textStyle: { color: c.textColor }, + textStyle: { color: c.textColor, fontFamily: 'Noto Sans' }, top: 0, left: 'center', orient: 'horizontal', type: 'scroll', maxItems: 5, padding: [0, 0, 0, 0], - itemGap: 10 + itemGap: 10, }, grid: { left: '8%', right: '5%', bottom: 65, containLabel: true, - top: '25%' + top: '25%', }, xAxis: { type: 'category', data: xLabels, axisLine: { lineStyle: { color: c.gridColor } }, axisLabel: { - color: c.textColor, + color: c.subTextColor, interval: 0, fontSize: 9, rotate: 0, margin: 8, overflow: 'break', - width: 60 + width: 60, }, - axisTick: { show: false } + axisTick: { show: false }, }, yAxis: { type: 'value', splitLine: { lineStyle: { color: c.gridColor, type: 'dashed' } }, axisLabel: { - color: c.textColor, + color: c.subTextColor, formatter: (val) => formatDKKShort(val), - margin: 8 + margin: 8, }, - nameTextStyle: { padding: [0, 0, 0, 10] } + nameTextStyle: { padding: [0, 0, 0, 10] }, }, series: seriesData.map((s, idx) => ({ ...s, barMaxWidth: barMaxWidth, itemStyle: { - color: c.palette[idx % c.palette.length] + color: c.palette[idx % c.palette.length], }, label: { - show: false - } + show: false, + }, + emphasis: { + itemStyle: { + shadowBlur: 0, + }, + }, })), }); } @@ -652,7 +759,6 @@ function hideEmptyState() { document.getElementById('apply-filters').addEventListener('click', updateCharts); -// Close dropdowns when clicking outside document.addEventListener('click', (e) => { if (!e.target.closest('.filter-group.multi-select')) { document.querySelectorAll('.checkbox-dropdown-list.open').forEach(el => { @@ -693,8 +799,8 @@ document.addEventListener('click', (e) => { document.addEventListener('DOMContentLoaded', () => { initCharts(); + updateNavbarUpdateDate(); - // Setup click handlers for dropdown toggles ['species', 'zone', 'gear'].forEach(id => { const container = document.getElementById(id); if (container) { @@ -705,8 +811,7 @@ document.addEventListener('DOMContentLoaded', () => { toggleDropdown(id); }); } - - // Listen for changes inside the dropdown list + const list = container.querySelector('.checkbox-dropdown-list'); if (list) { list.addEventListener('change', () => { diff --git a/static/index.html b/static/index.html index dc9a3a1..02dd0f8 100644 --- a/static/index.html +++ b/static/index.html @@ -27,6 +27,7 @@ FLĂ“ / HAGFISH +
diff --git a/static/style.css b/static/style.css index 4453e24..1b41a53 100644 --- a/static/style.css +++ b/static/style.css @@ -141,6 +141,21 @@ nav { } .nav-brand:hover { color: var(--accent); border-bottom: none; } +.update-date-display { + grid-column: 3; + justify-self: end; + font-family: 'Noto Sans', sans-serif; + font-weight: 400; + font-size: 0.72rem; + letter-spacing: 0.08em; + color: var(--ink-muted); + white-space: nowrap; + padding: 0.35rem 0.8rem; + border: 1px solid var(--border); + border-radius: 2px; + background: var(--input-bg); +} + .brand-sep { font-weight: 300; color: var(--ink-dim); } .brand-sub { font-weight: 400; @@ -481,6 +496,9 @@ footer { .dashboard { padding: 1.5rem; } + .update-date-display { + display: none; + } } @media (max-width: 460px) {