New table temperature colour gradients
This commit is contained in:
Fraxle
2026-05-24 19:19:15 +01:00
parent 69788f1069
commit 5574690887
5 changed files with 91 additions and 88 deletions
+26 -27
View File
@@ -433,7 +433,7 @@
} }
.utci-table tbody tr { .utci-table tbody tr {
border-bottom: 1px solid #ede4cc; border-bottom: 1px solid #ffffff;
transition: background 0.1s; transition: background 0.1s;
} }
@@ -460,7 +460,7 @@
container border. */ container border. */
.utci-table th:not(:last-child), .utci-table th:not(:last-child),
.utci-table td:not(:last-child) { .utci-table td:not(:last-child) {
border-right: 1px solid #ede4cc; border-right: 1px solid #ffffff;
} }
/* The Dir column is centered on both desktop and mobile so the /* The Dir column is centered on both desktop and mobile so the
@@ -715,23 +715,23 @@
The hover state darkens slightly. The col-unit underneath inherits The hover state darkens slightly. The col-unit underneath inherits
a matching accent colour. */ a matching accent colour. */
.utci-table th.grp-felt { background: #dff3ec; color: #075c3a; } .utci-table th.grp-felt { background: rgba(223,243,236,0.50); color: #075c3a; }
.utci-table th.grp-surface { background: #ddeaf8; color: #0b3e72; } .utci-table th.grp-surface { background: rgba(221,234,248,0.50); color: #0b3e72; }
.utci-table th.grp-ambient { background: #eceae3; color: #3c3a35; } .utci-table th.grp-ambient { background: rgba(236,234,227,0.50); color: #3c3a35; }
.utci-table th.grp-precip { background: #f7e4ee; color: #6b1e3a; } .utci-table th.grp-precip { background: rgba(247,228,238,0.50); color: #6b1e3a; }
.utci-table th.grp-sky { background: #eae8fc; color: #352e7a; } .utci-table th.grp-sky { background: rgba(234,232,252,0.50); color: #352e7a; }
.utci-table th.grp-wind { background: #fae6e0; color: #6a2210; } .utci-table th.grp-wind { background: rgba(250,230,224,0.50); color: #6a2210; }
.utci-table th.grp-airqual { background: #e5f0d8; color: #244408; } .utci-table th.grp-airqual { background: rgba(229,240,216,0.50); color: #244408; }
.utci-table th.grp-solar { background: #faeacc; color: #5a3004; } .utci-table th.grp-solar { background: rgba(250,234,204,0.50); color: #5a3004; }
.utci-table th.grp-felt.col-info-th:hover { background: #c8ead9; } .utci-table th.grp-felt.col-info-th:hover { background: rgba(213,233,226,0.80); }
.utci-table th.grp-surface.col-info-th:hover { background: #c5d9f0; } .utci-table th.grp-surface.col-info-th:hover { background: rgba(211,224,238,0.80); }
.utci-table th.grp-ambient.col-info-th:hover { background: #d8d6ce; } .utci-table th.grp-ambient.col-info-th:hover { background: rgba(226,224,217,0.80); }
.utci-table th.grp-precip.col-info-th:hover { background: #eecfdf; } .utci-table th.grp-precip.col-info-th:hover { background: rgba(237,218,228,0.80); }
.utci-table th.grp-sky.col-info-th:hover { background: #d4d0f5; } .utci-table th.grp-sky.col-info-th:hover { background: rgba(224,222,242,0.80); }
.utci-table th.grp-wind.col-info-th:hover { background: #f0cfc5; } .utci-table th.grp-wind.col-info-th:hover { background: rgba(240,220,214,0.80); }
.utci-table th.grp-airqual.col-info-th:hover { background: #d0e4bc; } .utci-table th.grp-airqual.col-info-th:hover { background: rgba(219,230,206,0.80); }
.utci-table th.grp-solar.col-info-th:hover { background: #f0d5a8; } .utci-table th.grp-solar.col-info-th:hover { background: rgba(240,224,194,0.80); }
.utci-table th.grp-felt .col-unit { color: #0f9e63; } .utci-table th.grp-felt .col-unit { color: #0f9e63; }
.utci-table th.grp-surface .col-unit { color: #2672c8; } .utci-table th.grp-surface .col-unit { color: #2672c8; }
@@ -758,15 +758,14 @@
border-bottom: 1px solid rgba(0,0,0,0.06); border-bottom: 1px solid rgba(0,0,0,0.06);
} }
.grp-label-hour { background: #f0e4c4; } .utci-table .grp-label-row th.grp-felt { background: rgba(223,243,236,0.50); border-left: 2px solid rgba(74,50,24,0.18); border-bottom: 1px solid rgba(74,50,24,0.18);}
.grp-label-felt { background: #dff3ec; color: #075c3a; border-left: 2px solid rgba(74,50,24,0.18); } .utci-table .grp-label-row th.grp-surface { background: rgba(221,234,248,0.50); border-left: 2px solid rgba(74,50,24,0.18); border-bottom: 1px solid rgba(74,50,24,0.18);}
.grp-label-surface { background: #ddeaf8; color: #0b3e72; border-left: 2px solid rgba(74,50,24,0.18); } .utci-table .grp-label-row th.grp-ambient { background: rgba(236,234,227,0.50); border-left: 2px solid rgba(74,50,24,0.18); border-bottom: 1px solid rgba(74,50,24,0.18);}
.grp-label-ambient { background: #eceae3; color: #3c3a35; border-left: 2px solid rgba(74,50,24,0.18); } .utci-table .grp-label-row th.grp-precip { background: rgba(247,228,238,0.50); border-left: 2px solid rgba(74,50,24,0.18); border-bottom: 1px solid rgba(74,50,24,0.18);}
.grp-label-precip { background: #f7e4ee; color: #6b1e3a; border-left: 2px solid rgba(74,50,24,0.18); } .utci-table .grp-label-row th.grp-sky { background: rgba(234,232,252,0.50); border-left: 2px solid rgba(74,50,24,0.18); border-bottom: 1px solid rgba(74,50,24,0.18);}
.grp-label-sky { background: #eae8fc; color: #352e7a; border-left: 2px solid rgba(74,50,24,0.18); } .utci-table .grp-label-row th.grp-wind { background: rgba(250,230,224,0.50); border-left: 2px solid rgba(74,50,24,0.18); border-bottom: 1px solid rgba(74,50,24,0.18);}
.grp-label-wind { background: #fae6e0; color: #6a2210; border-left: 2px solid rgba(74,50,24,0.18); } .utci-table .grp-label-row th.grp-airqual { background: rgba(229,240,216,0.50); border-left: 2px solid rgba(74,50,24,0.18); border-bottom: 1px solid rgba(74,50,24,0.18);}
.grp-label-airqual { background: #e5f0d8; color: #244408; border-left: 2px solid rgba(74,50,24,0.18); } .utci-table .grp-label-row th.grp-solar { background: rgba(250,234,204,0.50); border-left: 2px solid rgba(74,50,24,0.18); border-bottom: 1px solid rgba(74,50,24,0.18);}
.grp-label-solar { background: #faeacc; color: #5a3004; border-left: 2px solid rgba(74,50,24,0.18); }
/* ── FILTER PROFILES + COLUMN TOGGLES — mobile fixes ───────────────── */ /* ── FILTER PROFILES + COLUMN TOGGLES — mobile fixes ───────────────── */
@media (max-width: 640px) { @media (max-width: 640px) {
+34 -45
View File
@@ -23,7 +23,7 @@
import { h, render, Fragment } from '../vendor/preact.js'; import { h, render, Fragment } from '../vendor/preact.js';
import htm from '../vendor/htm.js'; import htm from '../vendor/htm.js';
import { import {
utciCategory, UTCI_BANDS, utciCategory, UTCI_BANDS, bandGradient,
SKIN_TYPES, sunburnMinutes, burnLabel, SKIN_TYPES, sunburnMinutes, burnLabel,
VEHICLE_TYPES, BUILDING_TYPES, VEHICLE_TYPES, BUILDING_TYPES,
confidenceBand, moonGlyph, confidenceBand, moonGlyph,
@@ -128,7 +128,7 @@ export function UTCIForecast() {
const span = GROUP_ORDER[g].filter(isColVisible).length; const span = GROUP_ORDER[g].filter(isColVisible).length;
if (span === 0) continue; if (span === 0) continue;
const labels = { felt: 'Felt', surface: 'Surface', ambient: 'Ambient', precip: 'Precip', sky: 'Sky', wind: 'Wind', airqual: 'Air quality', solar: 'Solar' }; const labels = { felt: 'Felt', surface: 'Surface', ambient: 'Ambient', precip: 'Precip', sky: 'Sky', wind: 'Wind', airqual: 'Air quality', solar: 'Solar' };
cells.push(html`<th class=${`grp-label grp-label-${g}`} colspan=${span} scope="colgroup">${labels[g]}</th>`); cells.push(html`<th class=${`grp-label grp-label-${g} grp-${g}`} colspan=${span} scope="colgroup">${labels[g]}</th>`);
} }
return html`<tr class="grp-label-row">${cells}</tr>`; return html`<tr class="grp-label-row">${cells}</tr>`;
}; };
@@ -468,7 +468,7 @@ ${isPro && (activeProfile === 'custom' || activeCols['utciP']) && html`<button c
</label> </label>
<label class="display-opt"> <label class="display-opt">
<input type="checkbox" checked=${showDegree} onChange=${toggleShowDegree} /> <input type="checkbox" checked=${showDegree} onChange=${toggleShowDegree} />
°&nbsp;symbol ° symbol
</label> </label>
</div> </div>
@@ -538,41 +538,28 @@ ${isPro && (activeProfile === 'custom' || activeCols['utciP']) && html`<button c
const fmt = (v, dp = 1) => v == null ? '—' : (showDecimals ? v.toFixed(dp) : String(Math.round(v))); const fmt = (v, dp = 1) => v == null ? '—' : (showDecimals ? v.toFixed(dp) : String(Math.round(v)));
const deg = showDegree ? '°' : ''; const deg = showDegree ? '°' : '';
// Returns a background tint based on temperature value - all share the same // Returns a background tint based on temperature value - all share the same
// Thermal Stress Bands colors with 50% alpha for consistency. Uses exact legend ranges. // Thermal Stress Bands - solid colours matching the legend exactly.
// Converts a hex colour - r,g,b components for rgba(). // All band-coloured cells use the full bg/fg from UTCI_BANDS.
const hexToRgb = (hex) => {
const n = parseInt(hex.replace('#',''), 16);
return [(n>>16)&255, (n>>8)&255, n&255];
};
// Air + UTCI columns - 35% alpha light tint.
const tempBg = (t) => { const tempBg = (t) => {
if (t == null) return 'transparent'; if (t == null) return 'transparent';
const band = utciCategory(t); const band = utciCategory(t);
if (!band) return 'transparent'; return band ? bandGradient(band.bg, band.darkenAmt) : 'transparent';
const [r,g,b] = hexToRgb(band.bg);
return `rgba(${r},${g},${b},0.35)`;
}; };
// All other coloured columns - 50% alpha. const tempBgStrong = tempBg;
const tempBgStrong = (t) => { const tempFontColor = (t) => {
if (t == null) return 'transparent'; if (t == null) return '#1a1200';
const band = utciCategory(t); const band = utciCategory(t);
if (!band) return 'transparent'; return band ? band.fg : '#1a1200';
const [r,g,b] = hexToRgb(band.bg);
return `rgba(${r},${g},${b},0.5)`;
}; };
// Returns perceived luminance 0..1 from a hex colour. const tempFontColorStrong = tempFontColor;
const luminance = (hex) => { // Returns any extra band styles (fontWeight, textShadow) for a given temp.
const [r,g,b] = hexToRgb(hex); const bandExtras = (t) => {
return (0.299*r + 0.587*g + 0.114*b) / 255;
};
// Light tint (35% alpha) - always dark text, background is never dark enough.
const tempFontColor = (t) => '#4a3218';
// Medium tint (50% alpha) - use light text on dark bands, dark text on light bands.
const tempFontColorStrong = (t) => {
if (t == null) return '#4a3218';
const band = utciCategory(t); const band = utciCategory(t);
if (!band) return '#4a3218'; if (!band) return {};
return luminance(band.bg) < 0.35 ? '#ffffff' : '#4a3218'; return {
...(band.fontWeight ? { fontWeight: band.fontWeight } : {}),
...(band.textShadow ? { textShadow: band.textShadow } : {}),
};
}; };
const scaleBg = (v, min, max, rgb, maxAlpha = 0.14) => { const scaleBg = (v, min, max, rgb, maxAlpha = 0.14) => {
if (v == null || isNaN(v)) return 'transparent'; if (v == null || isNaN(v)) return 'transparent';
@@ -619,19 +606,19 @@ ${isPro && (activeProfile === 'custom' || activeCols['utciP']) && html`<button c
</span> </span>
</td> </td>
${visibleCols.utciP && html` ${visibleCols.utciP && html`
<td class=${groupStart('utciP')} style=${{ background: hexToRgba(adjCat.bg, 0.50), color: tempFontColorStrong(r.utciAdj), fontWeight: 700, fontSize: '13px' }}> <td class=${groupStart('utciP')} style=${{ background: bandGradient(adjCat.bg, adjCat.darkenAmt), color: adjCat.fg, fontSize: '13px', ...bandExtras(r.utciAdj) }}>
${fmt(r.utciAdj)}${deg} ${fmt(r.utciAdj)}${deg}
</td>`} </td>`}
${visibleCols.vehicleT && html` ${visibleCols.vehicleT && html`
<td class=${groupStart('vehicleT')} style=${{ color: tempFontColorStrong(r.vehicleT), fontWeight: 'bold', background: tempBgStrong(r.vehicleT) }}> <td class=${groupStart('vehicleT')} style=${{ color: tempFontColorStrong(r.vehicleT), background: tempBgStrong(r.vehicleT), ...bandExtras(r.vehicleT) }}>
${fmt(r.vehicleT)}${deg} ${fmt(r.vehicleT)}${deg}
</td>`} </td>`}
${indoorMode === 'on' && !indoorManaged && html` ${indoorMode === 'on' && !indoorManaged && html`
<td class=${groupStart('indoorT')} style=${{ color: tempFontColorStrong(r.indoorT), fontWeight: 'bold', background: tempBgStrong(r.indoorT) }}> <td class=${groupStart('indoorT')} style=${{ color: tempFontColorStrong(r.indoorT), background: tempBgStrong(r.indoorT), ...bandExtras(r.indoorT) }}>
${fmt(r.indoorT)}${deg} ${fmt(r.indoorT)}${deg}
</td>`} </td>`}
${indoorMode === 'on' && indoorManaged && html` ${indoorMode === 'on' && indoorManaged && html`
<td class=${groupStart('managedT')} style=${{ color: tempFontColorStrong(r.managedT), fontWeight: 'bold', background: tempBgStrong(r.managedT) }}> <td class=${groupStart('managedT')} style=${{ color: tempFontColorStrong(r.managedT), background: tempBgStrong(r.managedT), ...bandExtras(r.managedT) }}>
${fmt(r.managedT)}${deg} ${fmt(r.managedT)}${deg}
</td>`} </td>`}
${visibleCols.burn && html` ${visibleCols.burn && html`
@@ -639,31 +626,30 @@ ${isPro && (activeProfile === 'custom' || activeCols['utciP']) && html`<button c
${burnLabel(burnMins)} ${burnLabel(burnMins)}
</td>`} </td>`}
${visibleCols.utci && html` ${visibleCols.utci && html`
<td class=${groupStart('utci')} style=${{ background: hexToRgba(cat.bg, 0.35), color: tempFontColorStrong(r.utci), fontWeight: 600 }}> <td class=${groupStart('utci')} style=${{ background: bandGradient(cat.bg, cat.darkenAmt), color: cat.fg, ...bandExtras(r.utci) }}>
${fmt(r.utci)}${deg} ${fmt(r.utci)}${deg}
</td>`} </td>`}
${visibleCols.delta && html` ${visibleCols.delta && html`
<td class=${groupStart('delta')} style=${{ <td class=${groupStart('delta')} style=${{
color: delta > 3 ? '#c8601a' : delta < -3 ? '#3f73c4' : '#4a3218', color: delta > 3 ? '#c8601a' : delta < -3 ? '#3f73c4' : '#4a3218',
fontWeight: 600,
background: deltaBg(delta), background: deltaBg(delta),
}}> }}>
${delta > 0 ? '+' : ''}${fmt(delta)} ${delta > 0 ? '+' : ''}${fmt(delta)}
</td>`} </td>`}
${visibleCols.tmrt && html`<td class=${groupStart('tmrt')} style=${{ background: tempBgStrong(r.Tmrt), color: tempFontColorStrong(r.Tmrt) }}>${fmt(r.Tmrt)}${deg}</td>`} ${visibleCols.tmrt && html`<td class=${groupStart('tmrt')} style=${{ background: tempBgStrong(r.Tmrt), color: tempFontColorStrong(r.Tmrt), ...bandExtras(r.Tmrt) }}>${fmt(r.Tmrt)}${deg}</td>`}
${visibleCols.concreteT && html` ${visibleCols.concreteT && html`
<td class=${groupStart('concreteT')} style=${{ color: tempFontColorStrong(r.concreteT), fontWeight: 'bold', background: tempBgStrong(r.concreteT) }}> <td class=${groupStart('concreteT')} style=${{ color: tempFontColorStrong(r.concreteT), background: tempBgStrong(r.concreteT), ...bandExtras(r.concreteT) }}>
${fmt(r.concreteT)}${deg} ${fmt(r.concreteT)}${deg}
</td>`} </td>`}
${visibleCols.soilT && html` ${visibleCols.soilT && html`
<td class=${groupStart('soilT')} style=${{ color: tempFontColorStrong(r.soilT0), background: tempBgStrong(r.soilT0) }}>${fmt(r.soilT0)}${deg}</td>`} <td class=${groupStart('soilT')} style=${{ color: tempFontColorStrong(r.soilT0), background: tempBgStrong(r.soilT0), ...bandExtras(r.soilT0) }}>${fmt(r.soilT0)}${deg}</td>`}
${visibleCols.soilT6 && html` ${visibleCols.soilT6 && html`
<td class=${groupStart('soilT6')} style=${{ color: tempFontColorStrong(r.soilT6), background: tempBgStrong(r.soilT6) }}>${fmt(r.soilT6)}${deg}</td>`} <td class=${groupStart('soilT6')} style=${{ color: tempFontColorStrong(r.soilT6), background: tempBgStrong(r.soilT6), ...bandExtras(r.soilT6) }}>${fmt(r.soilT6)}${deg}</td>`}
${visibleCols.soilM && html` ${visibleCols.soilM && html`
<td class=${groupStart('soilM')} style=${{ color: '#2a6a90', background: scaleBg(r.soilM, 0.12, 0.45, '50,125,190') }}>${r.soilM != null ? fmt(r.soilM * 100, 1) + '%' : '—'}</td>`} <td class=${groupStart('soilM')} style=${{ color: '#2a6a90', background: scaleBg(r.soilM, 0.12, 0.45, '50,125,190') }}>${r.soilM != null ? fmt(r.soilM * 100, 1) + '%' : '—'}</td>`}
${visibleCols.air && html`<td class=${groupStart('air')} style=${{ background: tempBg(r.Ta), color: tempFontColorStrong(r.Ta) }}>${fmt(r.Ta)}${deg}</td>`} ${visibleCols.air && html`<td class=${groupStart('air')} style=${{ background: tempBg(r.Ta), color: tempFontColorStrong(r.Ta), ...bandExtras(r.Ta) }}>${fmt(r.Ta)}${deg}</td>`}
${visibleCols.rh && html`<td class=${groupStart('rh')} style=${{ background: scaleBg(r.RH, 30, 100, '70,145,200') }}>${Math.round(r.RH)}</td>`} ${visibleCols.rh && html`<td class=${groupStart('rh')} style=${{ background: scaleBg(r.RH, 30, 100, '70,145,200') }}>${Math.round(r.RH)}</td>`}
${visibleCols.dew && html`<td class=${groupStart('dew')} style=${{ background: tempBgStrong(r.dew), color: tempFontColorStrong(r.dew) }}>${fmt(r.dew)}${deg}</td>`} ${visibleCols.dew && html`<td class=${groupStart('dew')} style=${{ background: tempBgStrong(r.dew), color: tempFontColorStrong(r.dew), ...bandExtras(r.dew) }}>${fmt(r.dew)}${deg}</td>`}
${visibleCols.precip && html` ${visibleCols.precip && html`
<td class=${groupStart('precip')} style=${{ background: r.snow > 0 ? scaleBg(r.snow, 0, 4, '90,140,210') : scaleBg(r.precip, 0, 8, '70,145,200') }}> <td class=${groupStart('precip')} style=${{ background: r.snow > 0 ? scaleBg(r.snow, 0, 4, '90,140,210') : scaleBg(r.precip, 0, 8, '70,145,200') }}>
<span style=${{ display: 'inline-flex', alignItems: 'center', gap: '5px', verticalAlign: 'middle' }}> <span style=${{ display: 'inline-flex', alignItems: 'center', gap: '5px', verticalAlign: 'middle' }}>
@@ -824,11 +810,14 @@ ${isPro && (activeProfile === 'custom' || activeCols['utciP']) && html`<button c
</div>`; </div>`;
})()} })()}
<div class="utci-legend-row"> <div class="utci-legend">
<span class="utci-legend-label">Thermal stress bands</span>
<div class="utci-legend-row">
${UTCI_BANDS.map((b, i) => html` ${UTCI_BANDS.map((b, i) => html`
<span key=${i} class="utci-legend-item" style=${{ background: b.bg, color: b.fg }}> <span key=${i} class="utci-legend-item" style=${{ background: bandGradient(b.bg), color: b.fg, ...(b.fontWeight ? { fontWeight: b.fontWeight } : {}), ...(b.textShadow ? { textShadow: b.textShadow } : {}) }}>
${b.label} ${b.label}
</span>`)} </span>`)}
</div>
</div> </div>
+28 -13
View File
@@ -34,19 +34,19 @@
// utciCategory() reads from this array - do not duplicate elsewhere. // utciCategory() reads from this array - do not duplicate elsewhere.
// ------------------------------------------------------------------- // -------------------------------------------------------------------
export const UTCI_BANDS = [ export const UTCI_BANDS = [
{ max: -20, label: 'Extreme cold', bg: '#0e1a50', fg: '#c8d4f0' }, { max: -20, label: 'Extreme cold', bg: '#5e668a', fg: '#ffffff' },
{ max: -10, label: 'Arctic', bg: '#2a52b0', fg: '#dce8ff' }, { max: -10, label: 'Arctic', bg: '#708bca', fg: '#ffffff' },
{ max: 0, label: 'Freezing', bg: '#5c90cc', fg: '#ffffff' }, { max: 0, label: 'Freezing', bg: '#92b5dd', fg: '#1a1200' },
{ max: 5, label: 'Very cold', bg: '#90bcd8', fg: '#08182e' }, { max: 5, label: 'Very cold', bg: '#b5d2e5', fg: '#1a1200' },
{ max: 10, label: 'Cold', bg: '#b8d4dc', fg: '#08182e' }, { max: 10, label: 'Cold', bg: '#cfe2e8', fg: '#1a1200' },
{ max: 15, label: 'Chilly', bg: '#c8dfc0', fg: '#082010' }, { max: 15, label: 'Chilly', bg: '#daead5', fg: '#1a1200' },
{ max: 19, label: 'Cool', bg: '#8aca78', fg: '#082010' }, { max: 19, label: 'Cool', bg: '#b1dba5', fg: '#1a1200' },
{ max: 24, label: 'Comfortable', bg: '#157018', fg: '#b8f0b8' }, { max: 24, label: 'Comfortable', bg: '#90d090', fg: '#157a15', fontWeight: 700 },
{ max: 29, label: 'Warm', bg: '#e8d840', fg: '#201800' }, { max: 29, label: 'Warm', bg: '#f8e554', fg: '#1a1200' },
{ max: 34, label: 'Hot', bg: '#eca030', fg: '#201800' }, { max: 34, label: 'Hot', bg: '#f5aa54', fg: '#1a1200' },
{ max: 39, label: 'Very hot', bg: '#d84818', fg: '#ffffff' }, { max: 39, label: 'Very hot', bg: '#f07f6a', fg: '#1a1200' },
{ max: 44, label: 'Extremely hot', bg: '#a81810', fg: '#ffc8b8' }, { max: 44, label: 'Extremely hot', bg: '#dd5454', fg: '#000000' },
{ label: 'Danger', bg: '#3a0404', fg: '#ffffff' }, { label: 'Danger', bg: '#880000', fg: '#ffffff', fontWeight: 700, darkenAmt: 0.18, textShadow: '-1px -1px 0 #3a0000, 1px -1px 0 #3a0000, -1px 1px 0 #3a0000, 1px 1px 0 #3a0000', solid: true },
]; ];
export function utciCategory(u) { export function utciCategory(u) {
@@ -55,6 +55,21 @@ export function utciCategory(u) {
} }
} }
// Diagonal sweep gradient for a band background hex colour.
// Pre-blends with 50% white to sit harmoniously alongside lighter table cells,
// then applies a light→mid→dark sweep for depth.
export function bandGradient(hex, darkenAmt = 0.04) {
const n = parseInt(hex.replace('#',''), 16);
const wb = (c) => Math.min(255, Math.round(c + (255 - c) * 0.50));
const [r, g, b] = [wb((n>>16)&255), wb((n>>8)&255), wb(n&255)];
const lighten = (c) => Math.min(255, Math.round(c + (255 - c) * 0.30));
const darken = (c) => Math.round(c * (1 - darkenAmt));
const light = '#' + [lighten(r), lighten(g), lighten(b)].map(x => x.toString(16).padStart(2,'0')).join('');
const mid = '#' + [r, g, b].map(x => x.toString(16).padStart(2,'0')).join('');
const dark = '#' + [darken(r), darken(g), darken(b) ].map(x => x.toString(16).padStart(2,'0')).join('');
return `linear-gradient(135deg, ${light} 0%, ${mid} 60%, ${dark} 100%)`;
}
// ------------------------------------------------------------------- // -------------------------------------------------------------------
// SOAK-FACTOR - SunScope's original rain/snow penalty. // SOAK-FACTOR - SunScope's original rain/snow penalty.
// ------------------------------------------------------------------- // -------------------------------------------------------------------
+1 -1
View File
@@ -29,5 +29,5 @@
@import "./css/base.css"; @import "./css/base.css";
@import "./css/header.css"; @import "./css/header.css";
@import "./css/table.css"; @import "./css/table.css?v=2";
@import "./css/ui.css"; @import "./css/ui.css";
+2 -2
View File
@@ -78,10 +78,10 @@
href="https://fonts.bunny.net/css2?family=Fraunces:ital,wght@0,700;0,900;1,300;1,700&family=JetBrains+Mono:wght@400;600&family=Manrope:wght@400;500;600;700&display=swap" /> href="https://fonts.bunny.net/css2?family=Fraunces:ital,wght@0,700;0,900;1,300;1,700&family=JetBrains+Mono:wght@400;600&family=Manrope:wght@400;500;600;700&display=swap" />
</noscript> </noscript>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>☀️</text></svg>" /> <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>☀️</text></svg>" />
<link rel="stylesheet" href="./assets/sunscope.css?v=15a"> <link rel="stylesheet" href="./assets/sunscope.css?v=15b">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script type="module" src="./assets/js/main.js?v=15a"></script> <script type="module" src="./assets/js/main.js?v=15b"></script>
</body> </body>
</html> </html>