diff --git a/assets/css/header.css b/assets/css/header.css
index 051f6a6..7a1e804 100644
--- a/assets/css/header.css
+++ b/assets/css/header.css
@@ -242,6 +242,7 @@
gap: 4px;
width: 100%;
max-width: 270px; /* matches .scope-ring desktop size */
+ padding-bottom: 32px; /* compensates for the extra Air temp row in the Why It Feels panel */
}
/* Unified pill: icon on left, bar fills the right */
diff --git a/assets/css/ui.css b/assets/css/ui.css
index 1501fee..b066740 100644
--- a/assets/css/ui.css
+++ b/assets/css/ui.css
@@ -1607,6 +1607,16 @@
color: #3a78a8; /* cool blue - cooling contribution */
}
+.delta-base {
+ color: #4a3420; /* neutral — absolute air temperature, not a delta */
+}
+
+.insight-row--base {
+ opacity: 0.75;
+ border-bottom: 1px dashed rgba(74,52,32,0.2);
+ margin-bottom: 2px;
+}
+
/* Values for "Today at a glance" */
.insight-value {
font-family: JetBrains Mono, monospace;
diff --git a/assets/js/app.js b/assets/js/app.js
index 722ba24..dca39f6 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -742,6 +742,7 @@ export function UTCIForecast() {
${(() => {
const _wflItems = [
+ { label: 'Air temperature', icon: '🌡', value: currentRow.Ta, isBase: true },
{ label: 'Sun and sky', icon: '☀', value: whyFeelsLike.sunAndSky },
{ label: 'Wind', icon: '🌬', value: whyFeelsLike.wind },
{ label: 'Humidity', icon: '💧', value: whyFeelsLike.humidity },
@@ -752,19 +753,20 @@ export function UTCIForecast() {
{ label: 'Precipitation', icon: '🌧', value: whyFeelsLike.precipitation }
] : []),
];
- const _wflMax = Math.max(..._wflItems.map(i => Math.abs(i.value)), 0.1);
- return _wflItems.map(({ label, icon, value }) => html`
-
+ const _wflDeltaItems = _wflItems.filter(i => !i.isBase);
+ const _wflMax = Math.max(..._wflDeltaItems.map(i => Math.abs(i.value)), 0.1);
+ return _wflItems.map(({ label, icon, value, isBase }) => html`
+
${icon}
${label}
-
+ ${!isBase && html`
= 0 ? 'bar-pos' : 'bar-neg')}
style=${{ width: `${Math.round(Math.abs(value) / _wflMax * 100)}%` }}>
-
+ `}
- = 0 ? 'delta-pos' : 'delta-neg'}`}>
- ${value >= 0 ? '+' : ''}${value.toFixed(1)}°
+ = 0 ? 'delta-pos' : 'delta-neg'}`}>
+ ${isBase ? '' : value >= 0 ? '+' : ''}${value.toFixed(1)}°
`);
diff --git a/assets/js/compute.js b/assets/js/compute.js
index 5fcecc5..435a4b3 100644
--- a/assets/js/compute.js
+++ b/assets/js/compute.js
@@ -330,15 +330,27 @@ export function computeWhyFeelsLike(row, env) {
const { Ta, Tmrt, va, eh, precip, snow } = row;
const r1 = (v) => Math.round(v * 10) / 10;
- // Radiation - mean radiant temp above or below air temp.
- const sunAndSky = r1(Tmrt - Ta);
+ // Environment-adjusted air temp (same as used in buildHourlyRows).
+ const TaEnv = Ta + (env ? env.taOffset : 0);
- // Wind - UTCI with actual wind vs calm, holding Tmrt = Ta and RH neutral.
- const ehNeutral = vaporPressureHpa(Ta, 50);
- const wind = r1(utciApprox(Ta, Ta, va, ehNeutral) - utciApprox(Ta, Ta, 0, ehNeutral));
+ // Sequential UTCI isolation — all deltas are in the same UTCI-polynomial
+ // space so they add up: Ta + environment + sunAndSky + wind + humidity
+ // + precipitation ≈ SunSoak (utciAdj), within rounding.
+ //
+ // Tmrt already has env radiation scaling (dirFactor/difFactor/globFactor)
+ // baked in from buildHourlyRows, so each delta automatically reflects
+ // the active Solar Model without any extra work here.
+ const ehNeutral = vaporPressureHpa(TaEnv, 50);
+ const base = utciApprox(TaEnv, TaEnv, 0, ehNeutral); // ≈ TaEnv
- // Humidity - actual vapour pressure vs neutral RH 50%, no wind or sun.
- const humidity = r1(utciApprox(Ta, Ta, 0, eh) - utciApprox(Ta, Ta, 0, ehNeutral));
+ // Radiation: effect of actual Tmrt vs no-radiation baseline (wind=0, RH=50%).
+ const sunAndSky = r1(utciApprox(TaEnv, Tmrt, 0, ehNeutral) - base);
+
+ // Wind: cooling effect of actual wind with solar present (RH still neutral).
+ const wind = r1(utciApprox(TaEnv, Tmrt, va, ehNeutral) - utciApprox(TaEnv, Tmrt, 0, ehNeutral));
+
+ // Humidity: actual vapour pressure vs neutral RH 50%, with solar and wind.
+ const humidity = r1(utciApprox(TaEnv, Tmrt, va, eh) - utciApprox(TaEnv, Tmrt, va, ehNeutral));
// Environment modifier - explicit temperature offset from the env config.
const environment = env ? r1(env.taOffset) : 0;
@@ -346,7 +358,7 @@ export function computeWhyFeelsLike(row, env) {
// Precipitation soak penalty - negative or zero.
const precipitation = precipPenalty(precip, snow, va);
- return { sunAndSky, wind, humidity, environment, precipitation };
+ return { base: r1(base), sunAndSky, wind, humidity, environment, precipitation };
}
// ------------------------------------------------------------------------