5.5
New big and small scope sunset and sunrise simulation New calculations for the day tabs Added missing icons to simple view New record popups Week ahead description popups Camping profile Tweak to some of the indoor tems
This commit is contained in:
@@ -323,8 +323,6 @@ export function ConfigPanel({
|
||||
setActiveProfile('outdoors');
|
||||
setOutdoorsVariantAndSave(opt.value);
|
||||
setVisibleCols({ ...OUTDOORS_VARIANTS[opt.value].cols });
|
||||
setIndoorMode('off');
|
||||
setIndoorManaged(false);
|
||||
}}
|
||||
>
|
||||
<span class="profile-btn-scene" style=${{ background: opt.scene, filter: opt.disabled ? 'grayscale(100%)' : 'none' }}></span>
|
||||
@@ -344,8 +342,6 @@ export function ConfigPanel({
|
||||
setActiveProfile('outdoors');
|
||||
setOutdoorsVariantAndSave(opt.value);
|
||||
setVisibleCols({ ...OUTDOORS_VARIANTS[opt.value].cols });
|
||||
setIndoorMode('off');
|
||||
setIndoorManaged(false);
|
||||
}}
|
||||
>
|
||||
<span class="profile-btn-scene" style=${{ background: opt.scene, filter: opt.disabled ? 'grayscale(100%)' : 'none' }}></span>
|
||||
@@ -366,8 +362,6 @@ export function ConfigPanel({
|
||||
setActiveProfile('outdoors');
|
||||
setOutdoorsVariantAndSave(opt.value);
|
||||
setVisibleCols({ ...OUTDOORS_VARIANTS[opt.value].cols });
|
||||
if (opt.value === 'office') { setBuildingType('office'); setIndoorMode('on'); setIndoorManaged(false); }
|
||||
else { setIndoorMode('off'); setIndoorManaged(false); }
|
||||
}}
|
||||
>
|
||||
<span class="profile-btn-scene" style=${{ background: opt.scene, filter: opt.disabled ? 'grayscale(100%)' : 'none' }}></span>
|
||||
|
||||
@@ -176,7 +176,28 @@ function cloudDim(rgb, w) {
|
||||
// pale out the same way.
|
||||
const lum = 0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2];
|
||||
const neutral = Math.min(255, lum + 14);
|
||||
return mixRgb(rgb, [neutral, neutral, neutral], w);
|
||||
// Channels BELOW the neutral are brought up faster than channels above it
|
||||
// are brought down. This is the actual cure for the khaki, and the earlier
|
||||
// fixes missed it: mixing all three channels at the same rate leaves gold's
|
||||
// blue channel lagging far behind its red and green, and "red and green high,
|
||||
// blue low" is the definition of khaki — so every partially clouded gold tab
|
||||
// had to pass through it no matter how the darkening was tuned.
|
||||
// Filling the deficient channel first routes gold -> cream -> grey instead,
|
||||
// which never has all three channels spread apart. It also happens to be
|
||||
// right at the cold end: for a pale blue the deficient channel is red, so
|
||||
// blue is the one held back, and a clouded cold day pales toward grey while
|
||||
// staying recognisably blue.
|
||||
const fast = Math.sqrt(w);
|
||||
const dulled = rgb.map(c => c + (neutral - c) * (c < neutral ? fast : w));
|
||||
// Overcast is grey AND darker, but darkening can only be spent once the
|
||||
// colour is actually neutral: darkening a HALF-desaturated yellow is exactly
|
||||
// how olive is made, while darkening a true grey can only ever give a darker
|
||||
// grey. So it is cubed against the same w that drives the desaturation —
|
||||
// near-nothing through the middle where hue is still present (0.06 at w=0.5),
|
||||
// arriving in force only once the colour has already gone neutral (0.31 at
|
||||
// full cloud). That is what lets 100% cloud land on a genuinely darkish grey
|
||||
// without putting khaki back on the warm bands.
|
||||
return dulled.map(c => Math.round(c * (1 - 0.50 * w * w * w)));
|
||||
}
|
||||
|
||||
// Weather weights are pushed through this before they are used.
|
||||
@@ -258,16 +279,21 @@ export function DayTabs({
|
||||
|
||||
// The row pins to the top of the viewport on wide screens, and the table's
|
||||
// sticky hour header has to stop just below it. Publish the measured height
|
||||
// (plus its 4px margin) as --profile-row-h rather than hard-coding one in
|
||||
// table.css: the row is one line or two depending on how long the profile
|
||||
// and thermal-basis labels are, so a fixed offset leaves the header tucked
|
||||
// under it.
|
||||
// as --profile-row-h rather than hard-coding one in table.css: the row is
|
||||
// one line or two depending on how long the profile and thermal-basis
|
||||
// labels are, so a fixed offset leaves the header tucked under it.
|
||||
// Nothing is added on top of that height. The row's 4px bottom margin is
|
||||
// transparent and hangs below its border edge, so counting it only pushed
|
||||
// the header 4px past the bottom of the band — a strip of the scrolling
|
||||
// page showing through between the profile row and the column titles.
|
||||
// Floored rather than rounded so a fractional height has the header
|
||||
// overlap by a hair (the row outranks it on z-index) instead of gapping.
|
||||
const profileRowRef = useRef(null);
|
||||
useEffect(() => {
|
||||
const el = profileRowRef.current;
|
||||
if (!el) return;
|
||||
const publish = () => {
|
||||
const h = Math.round(el.getBoundingClientRect().height) + 4;
|
||||
const h = Math.floor(el.getBoundingClientRect().height);
|
||||
document.documentElement.style.setProperty('--profile-row-h', `${h}px`);
|
||||
};
|
||||
publish();
|
||||
@@ -484,7 +510,9 @@ export function DayTabs({
|
||||
// daylight repRows) since fog and frost often hit overnight or on
|
||||
// an early commute, then picks the worst hazard by priority.
|
||||
let vehicleHazard = null;
|
||||
let hazPrecip = 0, hazSnow = 0;
|
||||
let hazPrecip = 0, hazSnow = 0, hazDrops = 1;
|
||||
const hazProbs = d.rows.map(r => r.precipProb).filter(v => isFinite(v));
|
||||
const hazMaxProb = hazProbs.length ? Math.max(...hazProbs) : 0;
|
||||
if (isVehicleProfile) {
|
||||
const allSnow = d.rows.reduce((s, r) => s + (r.snow || 0), 0);
|
||||
const allPrecip = d.rows.reduce((s, r) => s + (r.precip || 0), 0);
|
||||
@@ -498,8 +526,13 @@ export function DayTabs({
|
||||
vehicleHazard = 'ice';
|
||||
} else if (minVis !== null && minVis < 2) {
|
||||
vehicleHazard = 'fog';
|
||||
} else if (allPrecip >= 0.3) {
|
||||
} else if (allPrecip >= 0.3 && hazMaxProb >= 50) {
|
||||
// Same probability gate as the main rain icon: a 20% chance
|
||||
// day must not show rain just because the modelled total is >0.3mm.
|
||||
vehicleHazard = 'rain'; hazPrecip = allPrecip;
|
||||
// Medium (2 drops) needs >1mm AND >=50% chance; otherwise light.
|
||||
// PrecipIcon would otherwise pick drops from the amount alone.
|
||||
hazDrops = allPrecip > 1 ? 2 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -902,7 +935,7 @@ export function DayTabs({
|
||||
${tempOnly
|
||||
? (isVehicleProfile
|
||||
? (vehicleHazard === 'snow' ? html`<${PrecipIcon} precip=${0} snow=${hazSnow} size=${30} />`
|
||||
: vehicleHazard === 'rain' ? html`<${PrecipIcon} precip=${hazPrecip} snow=${0} size=${30} />`
|
||||
: vehicleHazard === 'rain' ? html`<${PrecipIcon} precip=${hazPrecip} snow=${0} drops=${hazDrops} size=${30} />`
|
||||
: vehicleHazard === 'fog' ? html`<${FogIcon} size=${30} />`
|
||||
: vehicleHazard === 'ice' ? html`<${IceIcon} size=${30} />`
|
||||
: html`<${CarIcon} size=${30} />`)
|
||||
|
||||
Reference in New Issue
Block a user