1.7.7
Fix tables colours and moon phase
This commit is contained in:
+42
-56
@@ -23,7 +23,7 @@
|
||||
import { h, render, Fragment } from '../vendor/preact.js';
|
||||
import htm from '../vendor/htm.js';
|
||||
import {
|
||||
utciCategory,
|
||||
utciCategory, UTCI_BANDS,
|
||||
SKIN_TYPES, sunburnMinutes, burnLabel,
|
||||
VEHICLE_TYPES, BUILDING_TYPES,
|
||||
confidenceBand, moonGlyph,
|
||||
@@ -505,39 +505,40 @@ ${isPro && (activeProfile === 'custom' || activeCols['air']) && html`<button cla
|
||||
};
|
||||
// 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.
|
||||
// Converts a hex colour - r,g,b components for rgba().
|
||||
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) => {
|
||||
if (t == null || t <= -27) return 'transparent';
|
||||
if (t <= -13) return `rgba(63,115,196,0.5)`; // Arctic: -27 to -13 (from #3f73c4)
|
||||
if (t <= 0) return `rgba(126,176,224,0.5)`; // Freezing: -13 to 0 (from #7eb0e0)
|
||||
if (t <= 9) return `rgba(188,217,236,0.5)`; // Cold: 0 to 9 (from #bcd9ec)
|
||||
if (t <= 18) return `rgba(200,220,192,0.5)`; // Chilled: 9 to 18 (from #c8dcc0)
|
||||
if (t <= 26) return `rgba(74,138,58,0.5)`; // Comfortable: 18 to 26 (from #4a8a3a)
|
||||
if (t <= 32) return `rgba(232,197,71,0.5)`; // Mod heat: 26 to 32 (from #e8c547)
|
||||
if (t <= 38) return `rgba(220,138,58,0.5)`; // Strong heat: 32 to 38 (from #dc8a3a)
|
||||
if (t <= 46) return `rgba(196,74,58,0.5)`; // V. strong: 38 to 46 (from #c44a3a)
|
||||
return `rgba(122,26,26,0.5)`; // Extreme heat: >46 (from #7a1a1a)
|
||||
if (t == null) return 'transparent';
|
||||
const band = utciCategory(t);
|
||||
if (!band) return 'transparent';
|
||||
const [r,g,b] = hexToRgb(band.bg);
|
||||
return `rgba(${r},${g},${b},0.35)`;
|
||||
};
|
||||
// All other coloured columns - 50% alpha.
|
||||
const tempBgStrong = (t) => {
|
||||
if (t == null || t <= -27) return 'transparent';
|
||||
if (t <= -13) return `rgba(63,115,196,0.5)`; // Arctic: -27 to -13
|
||||
if (t <= 0) return `rgba(126,176,224,0.5)`; // Freezing: -13 to 0
|
||||
if (t <= 9) return `rgba(188,217,236,0.5)`; // Cold: 0 to 9
|
||||
if (t <= 18) return `rgba(200,220,192,0.5)`; // Chilled: 9 to 18
|
||||
if (t <= 26) return `rgba(74,138,58,0.5)`; // Comfortable: 18 to 26
|
||||
if (t <= 32) return `rgba(232,197,71,0.5)`; // Mod heat: 26 to 32
|
||||
if (t <= 38) return `rgba(220,138,58,0.5)`; // Strong heat: 32 to 38
|
||||
if (t <= 46) return `rgba(196,74,58,0.5)`; // V. strong: 38 to 46
|
||||
return `rgba(122,26,26,0.5)`; // Extreme heat: >46
|
||||
if (t == null) return 'transparent';
|
||||
const band = utciCategory(t);
|
||||
if (!band) return 'transparent';
|
||||
const [r,g,b] = hexToRgb(band.bg);
|
||||
return `rgba(${r},${g},${b},0.5)`;
|
||||
};
|
||||
const tempFontColor = (t) => {
|
||||
// Returns perceived luminance 0..1 from a hex colour.
|
||||
const luminance = (hex) => {
|
||||
const [r,g,b] = hexToRgb(hex);
|
||||
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';
|
||||
if (t <= -13) return '#fff'; // Arctic (-27 to -13)
|
||||
if (t <= 0) return '#111'; // Freezing (-13 to 0)
|
||||
if (t <= 9) return '#111'; // Cold (0 to 9)
|
||||
if (t <= 18) return '#111'; // Chilled (9 to 18)
|
||||
if (t <= 26) return '#fff'; // Comfortable (18 to 26)
|
||||
if (t <= 38) return '#111'; // Mod heat/Strong heat (26 to 38)
|
||||
return '#fff'; // V. strong/Extreme (38+)
|
||||
const band = utciCategory(t);
|
||||
if (!band) return '#4a3218';
|
||||
return luminance(band.bg) < 0.35 ? '#ffffff' : '#4a3218';
|
||||
};
|
||||
const scaleBg = (v, min, max, rgb, maxAlpha = 0.14) => {
|
||||
if (v == null || isNaN(v)) return 'transparent';
|
||||
@@ -672,12 +673,12 @@ ${isPro && (activeProfile === 'custom' || activeCols['air']) && html`<button cla
|
||||
<span class="wind-dir-label" style=${{ fontFamily: 'Manrope, sans-serif', fontSize: '11px', fontWeight: 700 }}>${r.compass.label}</span>
|
||||
</span>
|
||||
</td>`}
|
||||
${visibleCols.air && html`<td class=${groupStart('air')} style=${{ background: tempBg(r.Ta), color: tempFontColor(r.Ta) }}>${r.Ta.toFixed(1)}</td>`}
|
||||
${visibleCols.air && html`<td class=${groupStart('air')} style=${{ background: tempBg(r.Ta), color: tempFontColorStrong(r.Ta) }}>${r.Ta.toFixed(1)}</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: tempBg(r.dew), color: tempFontColor(r.dew) }}>${r.dew != null ? r.dew.toFixed(1) : '—'}</td>`}
|
||||
${visibleCols.tmrt && html` <td class=${groupStart('tmrt')} style=${{ background: tempBg(r.Tmrt), color: tempFontColor(r.Tmrt) }}>${r.Tmrt.toFixed(1)}</td>`}
|
||||
${visibleCols.dew && html`<td class=${groupStart('dew')} style=${{ background: tempBgStrong(r.dew), color: tempFontColorStrong(r.dew) }}>${r.dew != null ? r.dew.toFixed(1) : '—'}</td>`}
|
||||
${visibleCols.tmrt && html` <td class=${groupStart('tmrt')} style=${{ background: tempBgStrong(r.Tmrt), color: tempFontColorStrong(r.Tmrt) }}>${r.Tmrt.toFixed(1)}</td>`}
|
||||
${visibleCols.utci && html`
|
||||
<td class=${groupStart('utci')} style=${{ background: hexToRgba(cat.bg, 0.22), color: cat.fg === '#fff' ? cat.fg : '#2a1a08', fontWeight: 600 }}>
|
||||
<td class=${groupStart('utci')} style=${{ background: hexToRgba(cat.bg, 0.35), color: tempFontColorStrong(r.utci), fontWeight: 600 }}>
|
||||
${r.utci.toFixed(1)}°
|
||||
</td>`}
|
||||
${visibleCols.delta && html`
|
||||
@@ -689,29 +690,27 @@ ${isPro && (activeProfile === 'custom' || activeCols['air']) && html`<button cla
|
||||
${delta > 0 ? '+' : ''}${delta.toFixed(1)}
|
||||
</td>`}
|
||||
${visibleCols.utciP && html`
|
||||
<td class=${groupStart('utciP')} style=${{ background: hexToRgba(adjCat.bg, 0.55), color: adjCat.fg === '#fff' ? adjCat.fg : '#2a1a08', fontWeight: 700, fontSize: '13px' }}>
|
||||
<td class=${groupStart('utciP')} style=${{ background: hexToRgba(adjCat.bg, 0.50), color: tempFontColorStrong(r.utciAdj), fontWeight: 700, fontSize: '13px' }}>
|
||||
${r.utciAdj.toFixed(1)}°
|
||||
</td>`}
|
||||
${visibleCols.concreteT && html`
|
||||
<td class=${groupStart('concreteT')} style=${{ color: tempFontColor(r.concreteT), fontWeight: 'bold', background: tempBg(r.concreteT) }}>
|
||||
<td class=${groupStart('concreteT')} style=${{ color: tempFontColorStrong(r.concreteT), fontWeight: 'bold', background: tempBgStrong(r.concreteT) }}>
|
||||
${r.concreteT != null ? r.concreteT.toFixed(1) : '—'}
|
||||
</td>`}
|
||||
${visibleCols.vehicleT && html`
|
||||
<td class=${groupStart('vehicleT')} style=${{ color: tempFontColor(r.vehicleT), fontWeight: 'bold', background: tempBgStrong(r.vehicleT) }}>
|
||||
<td class=${groupStart('vehicleT')} style=${{ color: tempFontColorStrong(r.vehicleT), fontWeight: 'bold', background: tempBgStrong(r.vehicleT) }}>
|
||||
${r.vehicleT != null ? r.vehicleT.toFixed(1) : '—'}
|
||||
</td>`}
|
||||
${indoorMode === 'on' && !indoorManaged && html`
|
||||
<td class=${groupStart('indoorT')} style=${{ color: tempFontColor(r.indoorT), fontWeight: 'bold', background: tempBgStrong(r.indoorT) }}>
|
||||
<td class=${groupStart('indoorT')} style=${{ color: tempFontColorStrong(r.indoorT), fontWeight: 'bold', background: tempBgStrong(r.indoorT) }}>
|
||||
${r.indoorT != null ? r.indoorT.toFixed(1) : '—'}
|
||||
</td>`}
|
||||
${indoorMode === 'on' && indoorManaged && html`
|
||||
<td class=${groupStart('managedT')} style=${{ color: tempFontColor(r.managedT), fontWeight: 'bold', background: tempBgStrong(r.managedT) }}>
|
||||
<td class=${groupStart('managedT')} style=${{ color: tempFontColorStrong(r.managedT), fontWeight: 'bold', background: tempBgStrong(r.managedT) }}>
|
||||
${r.managedT != null ? r.managedT.toFixed(1) : '—'}
|
||||
</td>`}
|
||||
${visibleCols.soilT && html`
|
||||
<td class=${groupStart('soilT')} style=${{ color: tempFontColor(r.soilT0), background: tempBg(r.soilT0) }}>${r.soilT0 != null ? r.soilT0.toFixed(1) : '—'}</td>`}
|
||||
${visibleCols.soilT6 && html`
|
||||
<td class=${groupStart('soilT6')} style=${{ color: tempFontColor(r.soilT6), background: tempBg(r.soilT6) }}>${r.soilT6 != null ? r.soilT6.toFixed(1) : '—'}</td>`}
|
||||
<td class=${groupStart('soilT')} style=${{ color: tempFontColorStrong(r.soilT0), background: tempBgStrong(r.soilT0) }}>${r.soilT0 != null ? r.soilT0.toFixed(1) : '—'}</td>`}
|
||||
${visibleCols.soilM && html`
|
||||
<td class=${groupStart('soilM')} style=${{ color: '#2a6a90', background: scaleBg(r.soilM, 0.12, 0.45, '50,125,190') }}>${r.soilM != null ? r.soilM.toFixed(3) : '—'}</td>`}
|
||||
${visibleCols.precip && html`
|
||||
@@ -785,25 +784,12 @@ ${isPro && (activeProfile === 'custom' || activeCols['air']) && html`<button cla
|
||||
</div>`;
|
||||
})()}
|
||||
|
||||
<div class="utci-legend">
|
||||
<span class="utci-legend-label">Thermal stress bands</span>
|
||||
<div class="utci-legend-row">
|
||||
${[
|
||||
{ l: '-27 to -13 Arctic', bg: '#3f73c4', fg: '#fff' },
|
||||
{ l: '-13 to 0 Freezing', bg: '#7eb0e0', fg: '#111' },
|
||||
{ l: '0 to 9 Cold', bg: '#bcd9ec', fg: '#111' },
|
||||
{ l: '9 to 18 Chilled', bg: '#c8dcc0', fg: '#111' },
|
||||
{ l: '18 to 26 Comfortable', bg: '#4a8a3a', fg: '#fff' },
|
||||
{ l: '26 to 32 Mod heat', bg: '#e8c547', fg: '#111' },
|
||||
{ l: '32 to 38 Strong heat', bg: '#dc8a3a', fg: '#111' },
|
||||
{ l: '38 to 46 V. strong', bg: '#c44a3a', fg: '#fff' },
|
||||
{ l: '> 46 Extreme heat', bg: '#7a1a1a', fg: '#fff' },
|
||||
].map((b, i) => html`
|
||||
${UTCI_BANDS.map((b, i) => html`
|
||||
<span key=${i} class="utci-legend-item" style=${{ background: b.bg, color: b.fg }}>
|
||||
${b.l}
|
||||
${b.label}
|
||||
</span>`)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
${(() => {
|
||||
|
||||
@@ -179,9 +179,9 @@ export function SkyScope({ elev, dt, glob = 0, size = 26 }) {
|
||||
|
||||
const phase = moonPhaseFraction(dt);
|
||||
const moonR = innerR * 0.55;
|
||||
const phaseOffset = Math.cos(2 * Math.PI * phase) * moonR;
|
||||
const phaseOffset = Math.cos(Math.PI * phase) * moonR;
|
||||
const moonLitFromRight = phase < 0.5;
|
||||
const shadowCx = r + (moonLitFromRight ? -phaseOffset : phaseOffset);
|
||||
const shadowCx = r + phaseOffset;
|
||||
const grass = grassFillForElev(elev);
|
||||
|
||||
const uid = `ss-${size}-${Math.round(elev * 10)}-${Math.round(phase * 1000)}-${isRising ? 'r' : 's'}`;
|
||||
@@ -424,9 +424,9 @@ export function ScopeReticle({ value, cat, loading, elev = 0, dt = new Date(), g
|
||||
const sunGlowPeak = 0.30 + radFrac * 0.45 + horizFrac * 0.15; // centre opacity
|
||||
const moonDrawR = 15;
|
||||
const moonY = cy - 45;
|
||||
const phaseOffset = Math.cos(2 * Math.PI * phase) * moonDrawR;
|
||||
const phaseOffset = Math.cos(Math.PI * phase) * moonDrawR;
|
||||
const moonLitFromRight = phase < 0.5;
|
||||
const moonShadowCx = cx + (moonLitFromRight ? -phaseOffset : phaseOffset);
|
||||
const moonShadowCx = cx + phaseOffset;
|
||||
// Readout text colours flip with day/night for legibility against the sky.
|
||||
const readoutColor = isDay ? '#1e1208' : '#f5edd6';
|
||||
const readoutMutedColor = isDay ? '#9a7d5a' : '#d4c5a8';
|
||||
|
||||
+26
-15
@@ -24,24 +24,35 @@
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// UTCI THERMAL STRESS BANDS - the coloured pills in the table.
|
||||
// UTCI THERMAL STRESS BANDS - single source of truth.
|
||||
// -------------------------------------------------------------------
|
||||
// To recolour any band, change its bg/fg hex code. To shift the
|
||||
// boundary between bands (e.g. make "Comfortable" wider), change the
|
||||
// 'if (u < -)' thresholds. Order matters - they're checked top-down.
|
||||
// Edit this array to change labels, colours or thresholds.
|
||||
// Each entry: { max, label, bg, fg }
|
||||
// max - upper boundary (exclusive). Omit on the last entry.
|
||||
// bg - background hex for table cells and legend.
|
||||
// fg - foreground hex for text on that background.
|
||||
// utciCategory() reads from this array - do not duplicate elsewhere.
|
||||
// -------------------------------------------------------------------
|
||||
export const UTCI_BANDS = [
|
||||
{ max: -20, label: 'Extreme cold', bg: '#0e1a50', fg: '#c8d4f0' },
|
||||
{ max: -10, label: 'Arctic', bg: '#2a52b0', fg: '#dce8ff' },
|
||||
{ max: 0, label: 'Freezing', bg: '#5c90cc', fg: '#ffffff' },
|
||||
{ max: 5, label: 'Very cold', bg: '#90bcd8', fg: '#08182e' },
|
||||
{ max: 10, label: 'Cold', bg: '#b8d4dc', fg: '#08182e' },
|
||||
{ max: 15, label: 'Chilly', bg: '#c8dfc0', fg: '#082010' },
|
||||
{ max: 19, label: 'Cool', bg: '#8aca78', fg: '#082010' },
|
||||
{ max: 24, label: 'Comfortable', bg: '#157018', fg: '#b8f0b8' },
|
||||
{ max: 29, label: 'Warm', bg: '#e8d840', fg: '#201800' },
|
||||
{ max: 34, label: 'Hot', bg: '#eca030', fg: '#201800' },
|
||||
{ max: 39, label: 'Very hot', bg: '#d84818', fg: '#ffffff' },
|
||||
{ max: 44, label: 'Extremely hot', bg: '#a81810', fg: '#ffc8b8' },
|
||||
{ label: 'Danger', bg: '#3a0404', fg: '#ffffff' },
|
||||
];
|
||||
|
||||
export function utciCategory(u) {
|
||||
if (u < -40) return { label: 'Extreme cold', bg: '#1a1438', fg: '#fff' };
|
||||
if (u < -27) return { label: 'Very strong cold', bg: '#23408f', fg: '#fff' };
|
||||
if (u < -13) return { label: 'Arctic', bg: '#3f73c4', fg: '#fff' };
|
||||
if (u < 0) return { label: 'Freezing', bg: '#7eb0e0', fg: '#1a1612' };
|
||||
if (u < 9) return { label: 'Cold', bg: '#bcd9ec', fg: '#1a1612' };
|
||||
if (u < 18) return { label: 'Chilled', bg: '#c8dcc0', fg: '#1a1612' };
|
||||
if (u < 26) return { label: 'Comfortable', bg: '#4a8a3a', fg: '#fff' };
|
||||
if (u < 32) return { label: 'Moderate heat', bg: '#e8c547', fg: '#1a1612' };
|
||||
if (u < 38) return { label: 'Strong heat', bg: '#dc8a3a', fg: '#1a1612' };
|
||||
if (u < 46) return { label: 'Very strong heat', bg: '#c44a3a', fg: '#fff' };
|
||||
return { label: 'Extreme heat', bg: '#7a1a1a', fg: '#fff' };
|
||||
for (const band of UTCI_BANDS) {
|
||||
if (band.max === undefined || u < band.max) return band;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user