1.9.1.4
Update scope with new thermal colours
This commit is contained in:
+51
-34
@@ -134,6 +134,38 @@ export function UTCIForecast() {
|
|||||||
return html`<tr class="grp-label-row">${cells}</tr>`;
|
return html`<tr class="grp-label-row">${cells}</tr>`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Continuous temperature colour scale - hoisted so both the table columns
|
||||||
|
// and the thermal stress legend can share the same function.
|
||||||
|
const TEMP_STOPS = [
|
||||||
|
[-10, [ 90, 155, 220]],
|
||||||
|
[ 0, [140, 195, 235]],
|
||||||
|
[ 10, [155, 215, 195]],
|
||||||
|
[ 16, [140, 210, 140]],
|
||||||
|
[ 20, [195, 225, 110]],
|
||||||
|
[ 24, [240, 225, 80]],
|
||||||
|
[ 28, [250, 175, 65]],
|
||||||
|
[ 32, [240, 120, 55]],
|
||||||
|
[ 36, [220, 70, 50]],
|
||||||
|
[ 40, [185, 30, 30]],
|
||||||
|
[ 50, [130, 0, 20]],
|
||||||
|
];
|
||||||
|
const airTempRgb = (t) => {
|
||||||
|
if (t == null) return null;
|
||||||
|
const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
||||||
|
let i = TEMP_STOPS.findIndex(s => t < s[0]);
|
||||||
|
if (i === -1) i = TEMP_STOPS.length;
|
||||||
|
const raw = i === 0 ? TEMP_STOPS[0][1]
|
||||||
|
: i >= TEMP_STOPS.length ? TEMP_STOPS[TEMP_STOPS.length-1][1]
|
||||||
|
: (() => {
|
||||||
|
const [t0, c0] = TEMP_STOPS[i-1];
|
||||||
|
const [t1, c1] = TEMP_STOPS[i];
|
||||||
|
const x = clamp((t - t0) / (t1 - t0), 0, 1);
|
||||||
|
const lerp = (a, b) => Math.round(a + (b - a) * x);
|
||||||
|
return [lerp(c0[0],c1[0]), lerp(c0[1],c1[1]), lerp(c0[2],c1[2])];
|
||||||
|
})();
|
||||||
|
return raw.map(c => Math.min(255, Math.round(c + (255 - c) * 0.66)));
|
||||||
|
};
|
||||||
|
|
||||||
// ─── 4. JSX RETURN ───────────────────────────────────────────────────
|
// ─── 4. JSX RETURN ───────────────────────────────────────────────────
|
||||||
// Everything below is the actual page markup, written as one big HTM
|
// Everything below is the actual page markup, written as one big HTM
|
||||||
// template. Search tips:
|
// template. Search tips:
|
||||||
@@ -577,36 +609,7 @@ ${(isPro ? (activeProfile === 'custom' || activeProfile === 'showall' || activeC
|
|||||||
// airTempBg(t, tPrev, tNext) returns a top→bottom gradient that
|
// airTempBg(t, tPrev, tNext) returns a top→bottom gradient that
|
||||||
// flows from the previous row's colour through this row's colour to
|
// flows from the previous row's colour through this row's colour to
|
||||||
// the next row's colour, making the whole column one seamless heatmap.
|
// the next row's colour, making the whole column one seamless heatmap.
|
||||||
const TEMP_STOPS = [
|
// airTempRgb and TEMP_STOPS hoisted to component scope above.
|
||||||
[-10, [ 90, 155, 220]],
|
|
||||||
[ 0, [140, 195, 235]],
|
|
||||||
[ 10, [155, 215, 195]],
|
|
||||||
[ 16, [140, 210, 140]],
|
|
||||||
[ 20, [195, 225, 110]],
|
|
||||||
[ 24, [240, 225, 80]],
|
|
||||||
[ 28, [250, 175, 65]],
|
|
||||||
[ 32, [240, 120, 55]],
|
|
||||||
[ 36, [220, 70, 50]],
|
|
||||||
[ 40, [185, 30, 30]],
|
|
||||||
[ 50, [130, 0, 20]],
|
|
||||||
];
|
|
||||||
const airTempRgb = (t) => {
|
|
||||||
if (t == null) return null;
|
|
||||||
const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
|
||||||
let i = TEMP_STOPS.findIndex(s => t < s[0]);
|
|
||||||
if (i === -1) i = TEMP_STOPS.length;
|
|
||||||
const raw = i === 0 ? TEMP_STOPS[0][1]
|
|
||||||
: i >= TEMP_STOPS.length ? TEMP_STOPS[TEMP_STOPS.length-1][1]
|
|
||||||
: (() => {
|
|
||||||
const [t0, c0] = TEMP_STOPS[i-1];
|
|
||||||
const [t1, c1] = TEMP_STOPS[i];
|
|
||||||
const x = clamp((t - t0) / (t1 - t0), 0, 1);
|
|
||||||
const lerp = (a, b) => Math.round(a + (b - a) * x);
|
|
||||||
return [lerp(c0[0],c1[0]), lerp(c0[1],c1[1]), lerp(c0[2],c1[2])];
|
|
||||||
})();
|
|
||||||
// Blend 66% toward white for the pastel look.
|
|
||||||
return raw.map(c => Math.min(255, Math.round(c + (255 - c) * 0.66)));
|
|
||||||
};
|
|
||||||
const toRgb = (rgb) => rgb ? `rgb(${rgb[0]},${rgb[1]},${rgb[2]})` : 'transparent';
|
const toRgb = (rgb) => rgb ? `rgb(${rgb[0]},${rgb[1]},${rgb[2]})` : 'transparent';
|
||||||
const airTempBg = (t, tPrev, tNext) => {
|
const airTempBg = (t, tPrev, tNext) => {
|
||||||
if (t == null) return 'transparent';
|
if (t == null) return 'transparent';
|
||||||
@@ -880,10 +883,24 @@ ${(isPro ? (activeProfile === 'custom' || activeProfile === 'showall' || activeC
|
|||||||
<div class="utci-legend">
|
<div class="utci-legend">
|
||||||
<span class="utci-legend-label">Thermal stress bands</span>
|
<span class="utci-legend-label">Thermal stress bands</span>
|
||||||
<div class="utci-legend-row">
|
<div class="utci-legend-row">
|
||||||
${UTCI_BANDS.map((b, i) => html`
|
${[
|
||||||
<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 } : {}) }}>
|
{ t: -9, label: 'Freezing' },
|
||||||
${b.label}
|
{ t: 1, label: 'Cold' },
|
||||||
</span>`)}
|
{ t: 11, label: 'Cool' },
|
||||||
|
{ t: 21, label: 'Comfortable', bold: true },
|
||||||
|
{ t: 31, label: 'Hot' },
|
||||||
|
{ t: 41, label: 'Very hot' },
|
||||||
|
{ t: 51, label: 'Danger' },
|
||||||
|
].map((b, i) => {
|
||||||
|
const rgb = airTempRgb(b.t) || [200, 200, 200];
|
||||||
|
const mid = `rgb(${rgb[0]},${rgb[1]},${rgb[2]})`;
|
||||||
|
const light = `rgb(${rgb.map(c => Math.min(255, Math.round(c + (255-c)*0.30))).join(',')})`;
|
||||||
|
const dark = `rgb(${rgb.map(c => Math.round(c * 0.96)).join(',')})`;
|
||||||
|
const bg = `linear-gradient(135deg, ${light} 0%, ${mid} 60%, ${dark} 100%)`;
|
||||||
|
const lum = (rgb[0]*299 + rgb[1]*587 + rgb[2]*114) / 1000;
|
||||||
|
const fg = lum > 165 ? '#1a1a1a' : '#ffffff';
|
||||||
|
return html`<span key=${i} class="utci-legend-item" style=${{ background: bg, color: fg, ...(b.bold ? { fontWeight: 700 } : {}) }}>${b.label}</span>`;
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+10
-10
@@ -444,16 +444,16 @@ export function ScopeReticle({ value, cat, loading, elev = 0, dt = new Date(), g
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
const stressBands = [
|
const stressBands = [
|
||||||
{ min: -40, max: -27, color: '#23408f' },
|
{ min: -40, max: -27, color: '#5a9bdc' },
|
||||||
{ min: -27, max: -13, color: '#3f73c4' },
|
{ min: -27, max: -13, color: '#5a9bdc' },
|
||||||
{ min: -13, max: 0, color: '#7eb0e0' },
|
{ min: -13, max: 0, color: '#6ca9e1' },
|
||||||
{ min: 0, max: 9, color: '#bcd9ec' },
|
{ min: 0, max: 9, color: '#93ccd9' },
|
||||||
{ min: 9, max: 18, color: '#c8dcc0' },
|
{ min: 9, max: 18, color: '#92d4a3' },
|
||||||
{ min: 18, max: 26, color: '#6ab05a' },
|
{ min: 18, max: 26, color: '#dae15f' },
|
||||||
{ min: 26, max: 32, color: '#e8c547' },
|
{ min: 26, max: 32, color: '#f8a13f' },
|
||||||
{ min: 32, max: 38, color: '#dc8a3a' },
|
{ min: 32, max: 38, color: '#e15333' },
|
||||||
{ min: 38, max: 46, color: '#c44a3a' },
|
{ min: 38, max: 46, color: '#ae181c' },
|
||||||
{ min: 46, max: 50, color: '#7a1a1a' },
|
{ min: 46, max: 50, color: '#8d0616' },
|
||||||
];
|
];
|
||||||
function fracToXY(frac, r) {
|
function fracToXY(frac, r) {
|
||||||
const deg = 135 + frac * 270;
|
const deg = 135 + frac * 270;
|
||||||
|
|||||||
Reference in New Issue
Block a user