5.2.2
Update day tabs colours
This commit is contained in:
@@ -102,6 +102,18 @@ function weatherGradientNeutral(r, g, b, edgeBlend = 0.50, centerBlend = null) {
|
|||||||
return `radial-gradient(circle at 50% 50%, ${center} 0%, ${center} 28%, ${edge} 100%)`;
|
return `radial-gradient(circle at 50% 50%, ${center} 0%, ${center} 28%, ${edge} 100%)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Colour-driving "sustained high": the day's high with brief spikes ignored, so a
|
||||||
|
// single hot hour on an otherwise cool, damp day doesn't paint the whole tab warm.
|
||||||
|
// Drops the top ~10% of hours (always at least the single hottest) and takes the next
|
||||||
|
// value down — a temperature that actually persisted. The PRINTED hi number and the
|
||||||
|
// Danger alarm still key off the true peak, not this.
|
||||||
|
function sustainedHigh(vals) {
|
||||||
|
if (!vals.length) return null;
|
||||||
|
const sorted = [...vals].sort((a, b) => b - a);
|
||||||
|
const drop = Math.max(1, Math.round(sorted.length * 0.1));
|
||||||
|
return sorted[Math.min(drop, sorted.length - 1)];
|
||||||
|
}
|
||||||
|
|
||||||
function weatherGradientActive(r, g, b, edgeBlend = 0.42, centerBlend = null) {
|
function weatherGradientActive(r, g, b, edgeBlend = 0.42, centerBlend = null) {
|
||||||
const blend = (c, amt) => Math.round(c + (255 - c) * amt);
|
const blend = (c, amt) => Math.round(c + (255 - c) * amt);
|
||||||
const lighten = (c) => Math.min(255, Math.round(c + (255 - c) * 0.38));
|
const lighten = (c) => Math.min(255, Math.round(c + (255 - c) * 0.38));
|
||||||
@@ -232,6 +244,17 @@ export function DayTabs({
|
|||||||
const dayHi = mainVals.length ? Math.round(Math.max(...mainVals)) : null;
|
const dayHi = mainVals.length ? Math.round(Math.max(...mainVals)) : null;
|
||||||
const dayLo = mainVals.length ? Math.round(Math.min(...mainVals)) : null;
|
const dayLo = mainVals.length ? Math.round(Math.min(...mainVals)) : null;
|
||||||
|
|
||||||
|
// The tab COLOUR is keyed to a spike-resistant "sustained high" rather than
|
||||||
|
// the raw max, so a single warm hour on a cool/damp day doesn't paint the
|
||||||
|
// whole tab warm. The Danger tier is the exception — it keeps alarming on
|
||||||
|
// the true peak — and colourHi collapses back to dayHi there, so a real
|
||||||
|
// Danger day still gets its full colouring. The printed hi/lo numbers below
|
||||||
|
// always show the true dayHi/dayLo.
|
||||||
|
const catFn = isPetsProfile ? petCategory : utciCategory;
|
||||||
|
const isDanger = dayHi !== null && catFn(dayHi).solid === true;
|
||||||
|
const colourHi = dayHi === null ? null
|
||||||
|
: (isDanger ? dayHi : Math.round(sustainedHigh(mainVals)));
|
||||||
|
|
||||||
// Secondary row under each hi/lo: Shade for SunSoak, Managed for
|
// Secondary row under each hi/lo: Shade for SunSoak, Managed for
|
||||||
// indoor, Pet Shade for pets, and plain air temperature for
|
// indoor, Pet Shade for pets, and plain air temperature for
|
||||||
// Vehicle/Driver so the outside-vs-cabin gap is visible.
|
// Vehicle/Driver so the outside-vs-cabin gap is visible.
|
||||||
@@ -290,8 +313,7 @@ export function DayTabs({
|
|||||||
// • Otherwise rain wins, then cloud, then a clear "sunny"/cold colour.
|
// • Otherwise rain wins, then cloud, then a clear "sunny"/cold colour.
|
||||||
// Shade ramps live in utils.js (WEATHER TINT PALETTE) so the warning
|
// Shade ramps live in utils.js (WEATHER TINT PALETTE) so the warning
|
||||||
// event banner can colour itself from exactly the same source.
|
// event banner can colour itself from exactly the same source.
|
||||||
const catFn = isPetsProfile ? petCategory : utciCategory;
|
const tBand = colourHi !== null ? catFn(colourHi) : { bg: '#f8e554' };
|
||||||
const tBand = dayHi !== null ? catFn(dayHi) : { bg: '#f8e554' };
|
|
||||||
// Band colour, but ramped: bandRampRgb() interpolates between the
|
// Band colour, but ramped: bandRampRgb() interpolates between the
|
||||||
// this band's colour and the next one's, so 30° isn't the same
|
// this band's colour and the next one's, so 30° isn't the same
|
||||||
// swatch as 27° while 32° still lands on the Extreme orange
|
// swatch as 27° while 32° still lands on the Extreme orange
|
||||||
@@ -299,7 +321,7 @@ export function DayTabs({
|
|||||||
// alarm colour, not a point on the gradient.
|
// alarm colour, not a point on the gradient.
|
||||||
const tBandRgb = tBand.solid
|
const tBandRgb = tBand.solid
|
||||||
? hexToRgb(tBand.bg)
|
? hexToRgb(tBand.bg)
|
||||||
: bandRampRgb(dayHi, isPetsProfile ? PET_BANDS : UTCI_BANDS);
|
: bandRampRgb(colourHi, isPetsProfile ? PET_BANDS : UTCI_BANDS);
|
||||||
|
|
||||||
const ccVals = repRows.map(r => r.cc).filter(v => isFinite(v));
|
const ccVals = repRows.map(r => r.cc).filter(v => isFinite(v));
|
||||||
const avgCloud = ccVals.length ? ccVals.reduce((s, v) => s + v, 0) / ccVals.length : 0;
|
const avgCloud = ccVals.length ? ccVals.reduce((s, v) => s + v, 0) / ccVals.length : 0;
|
||||||
@@ -309,8 +331,7 @@ export function DayTabs({
|
|||||||
// the same cutoffs are re-projected onto the pet scale so the
|
// the same cutoffs are re-projected onto the pet scale so the
|
||||||
// "hot hue" / "plain cold" branches kick in at the same relative
|
// "hot hue" / "plain cold" branches kick in at the same relative
|
||||||
// point in each band, not the same absolute degree.
|
// point in each band, not the same absolute degree.
|
||||||
const isHot = dayHi !== null && dayHi >= (isPetsProfile ? 35 : 27); // Hot band and above
|
const isHot = colourHi !== null && colourHi >= (isPetsProfile ? 35 : 27); // Hot band and above
|
||||||
const isDanger = tBand.solid === true;
|
|
||||||
// Extreme band gets a less pastelised gradient so it reads as a
|
// Extreme band gets a less pastelised gradient so it reads as a
|
||||||
// clear step between Caution and the pulsing Danger tier.
|
// clear step between Caution and the pulsing Danger tier.
|
||||||
const isExtreme = tBand.label === 'Extreme';
|
const isExtreme = tBand.label === 'Extreme';
|
||||||
@@ -327,8 +348,8 @@ export function DayTabs({
|
|||||||
// Keep the heat hue; cloud only mutes it a touch (orange/red never
|
// Keep the heat hue; cloud only mutes it a touch (orange/red never
|
||||||
// greens) and rain does not override heat.
|
// greens) and rain does not override heat.
|
||||||
const heatFactor = isPetsProfile
|
const heatFactor = isPetsProfile
|
||||||
? (dayHi >= 49 ? 0.15 : dayHi >= 43 ? 0.30 : 0.45)
|
? (colourHi >= 49 ? 0.15 : colourHi >= 43 ? 0.30 : 0.45)
|
||||||
: (dayHi >= 39 ? 0.15 : dayHi >= 34 ? 0.30 : 0.45);
|
: (colourHi >= 39 ? 0.15 : colourHi >= 34 ? 0.30 : 0.45);
|
||||||
const t = Math.max(0, Math.min(1, (avgCloud - 30) / 70)) * heatFactor;
|
const t = Math.max(0, Math.min(1, (avgCloud - 30) / 70)) * heatFactor;
|
||||||
rgb = mixRgb(tBandRgb, [170, 162, 152], t);
|
rgb = mixRgb(tBandRgb, [170, 162, 152], t);
|
||||||
} else if (daySnow >= 0.1) {
|
} else if (daySnow >= 0.1) {
|
||||||
@@ -340,7 +361,7 @@ export function DayTabs({
|
|||||||
} else if (avgCloud >= 30) {
|
} else if (avgCloud >= 30) {
|
||||||
// Cloud: light → dark grey with cover (pure grey, no temperature hue).
|
// Cloud: light → dark grey with cover (pure grey, no temperature hue).
|
||||||
rgb = cloudTint(avgCloud);
|
rgb = cloudTint(avgCloud);
|
||||||
} else if (dayHi !== null && dayHi < (isPetsProfile ? 2 : 10)) {
|
} else if (colourHi !== null && colourHi < (isPetsProfile ? 2 : 10)) {
|
||||||
// Clear & cold: keep the cold temperature blue.
|
// Clear & cold: keep the cold temperature blue.
|
||||||
rgb = tBandRgb;
|
rgb = tBandRgb;
|
||||||
} else {
|
} else {
|
||||||
@@ -353,7 +374,7 @@ export function DayTabs({
|
|||||||
if (isExtreme) {
|
if (isExtreme) {
|
||||||
const floor = isPetsProfile ? 40 : 32;
|
const floor = isPetsProfile ? 40 : 32;
|
||||||
const ceil = isPetsProfile ? 52 : 41;
|
const ceil = isPetsProfile ? 52 : 41;
|
||||||
const t = Math.max(0, Math.min(1, (dayHi - floor) / (ceil - floor)));
|
const t = Math.max(0, Math.min(1, (colourHi - floor) / (ceil - floor)));
|
||||||
rgb = mixRgb(rgb, [136, 0, 0], t * 0.55);
|
rgb = mixRgb(rgb, [136, 0, 0], t * 0.55);
|
||||||
}
|
}
|
||||||
const [wR, wG, wB] = rgb;
|
const [wR, wG, wB] = rgb;
|
||||||
@@ -369,8 +390,8 @@ export function DayTabs({
|
|||||||
const heatMid = isPetsProfile ? 40 : 32; // Extreme threshold
|
const heatMid = isPetsProfile ? 40 : 32; // Extreme threshold
|
||||||
const heatTop = isPetsProfile ? 52 : 41; // Danger threshold
|
const heatTop = isPetsProfile ? 52 : 41; // Danger threshold
|
||||||
const clamp01 = (v) => Math.max(0, Math.min(1, v));
|
const clamp01 = (v) => Math.max(0, Math.min(1, v));
|
||||||
const heatT = dayHi === null ? 0 : clamp01((dayHi - heatFloor) / (heatMid - heatFloor));
|
const heatT = colourHi === null ? 0 : clamp01((colourHi - heatFloor) / (heatMid - heatFloor));
|
||||||
const overT = dayHi === null ? 0 : clamp01((dayHi - heatMid) / (heatTop - heatMid));
|
const overT = colourHi === null ? 0 : clamp01((colourHi - heatMid) / (heatTop - heatMid));
|
||||||
const wBgNeutral = weatherGradientNeutral(
|
const wBgNeutral = weatherGradientNeutral(
|
||||||
wR, wG, wB,
|
wR, wG, wB,
|
||||||
0.50 - 0.30 * heatT - 0.05 * overT, // edge: 0.50 → 0.20 → 0.15
|
0.50 - 0.30 * heatT - 0.05 * overT, // edge: 0.50 → 0.20 → 0.15
|
||||||
|
|||||||
Reference in New Issue
Block a user