3.0.2.3
Improved day tab selection and icons Tweaked at a glance panel
This commit is contained in:
+5
-23
@@ -103,33 +103,15 @@
|
|||||||
border-left: none;
|
border-left: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The selected-tab background is a different gradient (radial vs linear), and
|
.utci-day-tab:hover:not(.active):not(.locked) {
|
||||||
CSS can't tween between gradients — so we paint the active gradient as an
|
filter: brightness(1.14) !important;
|
||||||
overlay pseudo-element behind the tab content and fade its opacity on hover.
|
|
||||||
z-index:-1 keeps it above the tab's own background but below the labels. */
|
|
||||||
.utci-day-tab::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
background: var(--w-bg-active);
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.2s ease;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* On hover, a non-active tab fades in the selected gradient + filter so it
|
|
||||||
previews the selected shade (!important to beat the inline filter). */
|
|
||||||
.utci-day-tab:hover:not(.active)::before {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
.utci-day-tab:hover:not(.active) {
|
|
||||||
filter: saturate(1.1) brightness(1.04) !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.utci-day-tab.active {
|
.utci-day-tab.active {
|
||||||
color: #c8922a;
|
color: #c8922a;
|
||||||
border-bottom-color: #c8922a;
|
outline: 3px solid #c8922a;
|
||||||
|
outline-offset: -2px;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The "Mon 12 May" date underneath each tab name */
|
/* The "Mon 12 May" date underneath each tab name */
|
||||||
|
|||||||
+14
-11
@@ -1514,20 +1514,23 @@ export function UTCIForecast() {
|
|||||||
${((glanceSummary && glanceSummary.length > 0) || eventGlanceItems.length > 0) && html`
|
${((glanceSummary && glanceSummary.length > 0) || eventGlanceItems.length > 0) && html`
|
||||||
<div class="insight-panel insight-panel--glance">
|
<div class="insight-panel insight-panel--glance">
|
||||||
<div class="insight-panel-title">At a glance${glanceDate ? ` · ${glanceDate}` : ''}</div>
|
<div class="insight-panel-title">At a glance${glanceDate ? ` · ${glanceDate}` : ''}</div>
|
||||||
${glanceSummary.map(({ icon, label, value, alert }) => html`
|
${(() => {
|
||||||
<div class=${`insight-row${alert ? ' insight-row--alert' : ''}`} key=${label}>
|
const heatEvents = eventGlanceItems.filter(e => /heat spike|heat warning|extreme heat/i.test(e.label)).map(e => ({ ...e, alert: true }));
|
||||||
|
const otherEvents = eventGlanceItems.filter(e => !/heat spike|heat warning|extreme heat/i.test(e.label));
|
||||||
|
const renderRow = ({ icon, label, value, alert }, keyPrefix = '') => html`
|
||||||
|
<div class=${`insight-row${alert ? ' insight-row--alert' : ''}`} key=${`${keyPrefix}${label}`}>
|
||||||
<span class="insight-icon">${icon}</span>
|
<span class="insight-icon">${icon}</span>
|
||||||
<span class="insight-label">${label}</span>
|
<span class="insight-label">${label}</span>
|
||||||
<span class="insight-value">${value}</span>
|
<span class="insight-value">${value}</span>
|
||||||
</div>
|
</div>`;
|
||||||
`)}
|
return [
|
||||||
${eventGlanceItems.map(({ icon, label, value, alert }) => html`
|
...glanceSummary.flatMap(item => [
|
||||||
<div class=${`insight-row${alert ? ' insight-row--alert' : ''}`} key=${`ev-${label}`}>
|
renderRow(item),
|
||||||
<span class="insight-icon">${icon}</span>
|
...(item.label === 'Peak felt temp' ? heatEvents.map(e => renderRow(e, 'ev-')) : []),
|
||||||
<span class="insight-label">${label}</span>
|
]),
|
||||||
<span class="insight-value">${value}</span>
|
...otherEvents.map(e => renderRow(e, 'ev-')),
|
||||||
</div>
|
];
|
||||||
`)}
|
})()}
|
||||||
</div>`}
|
</div>`}
|
||||||
${visible.length > 0 && html`
|
${visible.length > 0 && html`
|
||||||
<div class="export-panel">
|
<div class="export-panel">
|
||||||
|
|||||||
@@ -69,6 +69,15 @@ function weatherGradient(r, g, b) {
|
|||||||
// colour, drawn as a radial gradient whose strong colour sits at the outer
|
// colour, drawn as a radial gradient whose strong colour sits at the outer
|
||||||
// edge and softens toward a lighter centre — so the hue reads as radiating
|
// edge and softens toward a lighter centre — so the hue reads as radiating
|
||||||
// inward from the outside of the tab.
|
// inward from the outside of the tab.
|
||||||
|
function weatherGradientNeutral(r, g, b) {
|
||||||
|
const blend = (c) => Math.round(c + (255 - c) * 0.58);
|
||||||
|
const [mr, mg, mb] = [blend(r), blend(g), blend(b)];
|
||||||
|
const lighten = (c) => Math.min(255, Math.round(c + (255 - c) * 0.38));
|
||||||
|
const center = `rgb(${lighten(mr)},${lighten(mg)},${lighten(mb)})`;
|
||||||
|
const edge = `rgb(${mr},${mg},${mb})`;
|
||||||
|
return `radial-gradient(circle at 50% 50%, ${center} 0%, ${center} 28%, ${edge} 100%)`;
|
||||||
|
}
|
||||||
|
|
||||||
function weatherGradientActive(r, g, b) {
|
function weatherGradientActive(r, g, b) {
|
||||||
const blend = (c) => Math.round(c + (255 - c) * 0.42);
|
const blend = (c) => Math.round(c + (255 - c) * 0.42);
|
||||||
const [mr, mg, mb] = [blend(r), blend(g), blend(b)];
|
const [mr, mg, mb] = [blend(r), blend(g), blend(b)];
|
||||||
@@ -295,17 +304,20 @@ export function DayTabs({
|
|||||||
const dayHi = utcipVals.length ? Math.round(Math.max(...utcipVals)) : null;
|
const dayHi = utcipVals.length ? Math.round(Math.max(...utcipVals)) : null;
|
||||||
const dayLo = utcipVals.length ? Math.round(Math.min(...utcipVals)) : null;
|
const dayLo = utcipVals.length ? Math.round(Math.min(...utcipVals)) : null;
|
||||||
|
|
||||||
// Day-tab weather icon - use daytime rows where available
|
// Day-tab weather icon - use core daylight rows (elev > 10°) where
|
||||||
const dayRows = d.rows.filter(r => r.elev > 0);
|
// available, falling back to any above-horizon rows, then all rows.
|
||||||
const repRows = dayRows.length > 0 ? dayRows : d.rows;
|
const coreRows = d.rows.filter(r => r.elev > 10);
|
||||||
|
const aboveRows = d.rows.filter(r => r.elev > 0);
|
||||||
|
const repRows = coreRows.length > 0 ? coreRows : aboveRows.length > 0 ? aboveRows : d.rows;
|
||||||
const dayPrecip = repRows.reduce((s, r) => s + (r.precip || 0), 0);
|
const dayPrecip = repRows.reduce((s, r) => s + (r.precip || 0), 0);
|
||||||
const daySnow = repRows.reduce((s, r) => s + (r.snow || 0), 0);
|
const daySnow = repRows.reduce((s, r) => s + (r.snow || 0), 0);
|
||||||
const catCounts = {};
|
const catCounts = {};
|
||||||
repRows.forEach(r => { if (r.cloudCat) catCounts[r.cloudCat] = (catCounts[r.cloudCat] || 0) + 1; });
|
repRows.forEach(r => { if (r.cloudCat) catCounts[r.cloudCat] = (catCounts[r.cloudCat] || 0) + 1; });
|
||||||
const modalCloudCat = Object.keys(catCounts).sort((a, b) => catCounts[b] - catCounts[a])[0] || 'clear';
|
const modalCloudCat = Object.keys(catCounts).sort((a, b) => catCounts[b] - catCounts[a])[0] || 'clear';
|
||||||
const midRow = repRows[Math.floor(repRows.length / 2)];
|
// Use solar-noon row (highest elevation) as the representative row
|
||||||
const repElev = midRow ? midRow.elev : 45;
|
const noonRow = repRows.reduce((best, r) => (r.elev > (best?.elev ?? -Infinity) ? r : best), null);
|
||||||
const repDt = midRow ? midRow.dt : dDate;
|
const repElev = noonRow ? noonRow.elev : 45;
|
||||||
|
const repDt = noonRow ? noonRow.dt : dDate;
|
||||||
const showPrecip = dayPrecip >= 0.3 || daySnow >= 0.1;
|
const showPrecip = dayPrecip >= 0.3 || daySnow >= 0.1;
|
||||||
|
|
||||||
// Base RGB for each weather condition — pastelised by weatherGradient()
|
// Base RGB for each weather condition — pastelised by weatherGradient()
|
||||||
@@ -320,6 +332,7 @@ export function DayTabs({
|
|||||||
: modalCloudCat === 'wispy' ? [200, 180, 130]
|
: modalCloudCat === 'wispy' ? [200, 180, 130]
|
||||||
: [220, 190, 50];
|
: [220, 190, 50];
|
||||||
const wBg = weatherGradient(wR, wG, wB);
|
const wBg = weatherGradient(wR, wG, wB);
|
||||||
|
const wBgNeutral = weatherGradientNeutral(wR, wG, wB);
|
||||||
const wBgActive = weatherGradientActive(wR, wG, wB);
|
const wBgActive = weatherGradientActive(wR, wG, wB);
|
||||||
const wText = '#2a1d10';
|
const wText = '#2a1d10';
|
||||||
|
|
||||||
@@ -340,8 +353,7 @@ export function DayTabs({
|
|||||||
? `${band.label} · SunScope Extra unlocks day ${i + 1}`
|
? `${band.label} · SunScope Extra unlocks day ${i + 1}`
|
||||||
: `${band.label} · day ${i + 1} of 14`}
|
: `${band.label} · day ${i + 1} of 14`}
|
||||||
style=${{
|
style=${{
|
||||||
background: isActive ? wBgActive : wBg,
|
background: isActive ? wBgActive : wBgNeutral,
|
||||||
'--w-bg-active': wBgActive,
|
|
||||||
color: wText,
|
color: wText,
|
||||||
borderTop: '1px solid #c9b08a',
|
borderTop: '1px solid #c9b08a',
|
||||||
borderRight: 'none',
|
borderRight: 'none',
|
||||||
@@ -350,7 +362,6 @@ export function DayTabs({
|
|||||||
opacity: locked ? 0.5 : 1,
|
opacity: locked ? 0.5 : 1,
|
||||||
cursor: locked ? 'not-allowed' : 'pointer',
|
cursor: locked ? 'not-allowed' : 'pointer',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
filter: isActive ? 'saturate(1.1) brightness(1.04)' : 'none',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
${locked && html`
|
${locked && html`
|
||||||
|
|||||||
@@ -385,7 +385,9 @@ export function computeGlanceSummary(todayRows, profile, variant, skinType, cols
|
|||||||
const peakRow = (field) => todayRows.reduce((best, r) =>
|
const peakRow = (field) => todayRows.reduce((best, r) =>
|
||||||
(r[field] != null && (best == null || r[field] > best[field])) ? r : best, null);
|
(r[field] != null && (best == null || r[field] > best[field])) ? r : best, null);
|
||||||
|
|
||||||
const maxRainProb = Math.max(0, ...todayRows.map(r => r.precipProb ?? 0));
|
const peakRainRow = todayRows.reduce((best, r) =>
|
||||||
|
(r.precipProb != null && (best == null || r.precipProb > best.precipProb)) ? r : best, null);
|
||||||
|
const maxRainProb = peakRainRow ? (peakRainRow.precipProb ?? 0) : 0;
|
||||||
|
|
||||||
// Longest run of rows that satisfy a predicate.
|
// Longest run of rows that satisfy a predicate.
|
||||||
const longestWindow = (rows, cond) => {
|
const longestWindow = (rows, cond) => {
|
||||||
@@ -428,7 +430,9 @@ export function computeGlanceSummary(todayRows, profile, variant, skinType, cols
|
|||||||
const rainItem = {
|
const rainItem = {
|
||||||
icon: '🌧',
|
icon: '🌧',
|
||||||
label: 'Rain risk',
|
label: 'Rain risk',
|
||||||
value: `${Math.round(maxRainProb)}%`,
|
value: maxRainProb > 0 && peakRainRow?.iso
|
||||||
|
? `${Math.round(maxRainProb)}% at ${hhmm(peakRainRow.iso)}`
|
||||||
|
: `${Math.round(maxRainProb)}%`,
|
||||||
alert: maxRainProb >= 60,
|
alert: maxRainProb >= 60,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -92,5 +92,9 @@
|
|||||||
"vehicle": 2,
|
"vehicle": 2,
|
||||||
"farming": 5
|
"farming": 5
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"2026-06-23": {
|
||||||
|
"visits": 7,
|
||||||
|
"profiles": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user