diff --git a/assets/css/table.css b/assets/css/table.css
index 8a384cb..58468b8 100644
--- a/assets/css/table.css
+++ b/assets/css/table.css
@@ -420,6 +420,14 @@
white-space: nowrap;
}
+.col-toggles-edit-btn {
+ display: none;
+}
+
+.col-toggles-body {
+ display: contents;
+}
+
/* The little pill buttons */
.col-toggle {
padding: 5px 13px;
@@ -1132,9 +1140,44 @@
}
.col-toggles-label {
+ display: none;
+ }
+
+ .col-toggles-edit-btn {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
flex: 0 0 100%;
- margin-right: 0;
- margin-bottom: 0;
+ padding: 8px 12px;
+ background: #f5edd6;
+ border: 1.5px solid #c9b08a;
+ color: #7a5c2a;
+ font-family: Manrope, sans-serif;
+ font-size: 12px;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ cursor: pointer;
+ text-align: left;
+ }
+
+ .col-toggles-edit-btn-icon {
+ width: 12px;
+ height: 12px;
+ flex-shrink: 0;
+ transition: transform 0.2s ease;
+ }
+
+ .col-toggles-edit-btn--open .col-toggles-edit-btn-icon {
+ transform: rotate(180deg);
+ }
+
+ .col-toggles-body {
+ display: none;
+ }
+
+ .col-toggles-body--open {
+ display: contents;
}
.col-toggle {
diff --git a/assets/js/app.js b/assets/js/app.js
index 578736f..883240e 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -131,6 +131,7 @@ export function UTCIForecast() {
// location. The input then overlays the location line to save space.
const [searchOpen, setSearchOpen] = useState(false);
const searchWrapRef = useRef(null);
+ const [colTogglesOpen, setColTogglesOpen] = useState(false);
const searchInputRef = useRef(null);
// Focus the input the moment the search opens.
@@ -290,6 +291,7 @@ export function UTCIForecast() {
activeProfile,
outdoorsVariant,
skinType,
+ visibleCols,
);
// Human-readable date for the "Day at a glance" heading, e.g. "Mon 2nd June 2026".
@@ -629,6 +631,11 @@ export function UTCIForecast() {
Columns:
+
+
${(isPro ? (activeProfile === 'custom' || activeProfile === 'showall' || activeCols['utciP']) : activeCols['utciP']) && html`
<${CustomSelect}
@@ -791,6 +798,7 @@ export function UTCIForecast() {
Units
+
@@ -1013,9 +1021,11 @@ export function UTCIForecast() {
${visibleCols.precip && html`
0 ? scaleBg(r.snow, 0, 4, '90,140,210') : scaleBg(r.precip, 0, 8, '70,145,200') }}>
- <${PrecipIcon} precip=${r.precip} snow=${r.snow} size=${28} />
+ <${PrecipIcon} precip=${r.precipProb > 0 ? r.precip : 0} snow=${r.precipProb > 0 ? r.snow : 0} size=${28} />
0 ? '#2a5fa8' : r.precip > 0 ? '#2a6a90' : '#7a5c30' }}>
- ${r.snow > 0 ? fmt(r.snow) + u('cm') : r.precip > 0 ? fmt(r.precip) + u('mm') : '—'}
+ ${r.precipProb > 0
+ ? (r.snow > 0 ? fmt(r.snow) + u('cm') : r.precip > 0 ? fmt(r.precip) + u('mm') : '—')
+ : '—'}
| `}
diff --git a/assets/js/compute.js b/assets/js/compute.js
index a9f4099..c76f30e 100644
--- a/assets/js/compute.js
+++ b/assets/js/compute.js
@@ -363,8 +363,9 @@ export function computeWhyFeelsLike(row, env) {
// variant - active sub-variant key e.g. "running", "beach" (or null)
// skinType - Fitzpatrick skin type key for UV burn time
// ------------------------------------------------------------------------
-export function computeGlanceSummary(todayRows, profile, variant, skinType) {
+export function computeGlanceSummary(todayRows, profile, variant, skinType, cols = null) {
if (!todayRows || todayRows.length === 0) return [];
+ const show = (key) => !cols || !!cols[key];
const hhmm = (iso) => iso ? iso.slice(11, 16) : null;
const dayRows = todayRows.filter(r => r.elev > 0);
@@ -437,20 +438,20 @@ export function computeGlanceSummary(todayRows, profile, variant, skinType) {
: 'No suitable window',
alert: !fieldWindow,
},
- {
+ ...(show('soilT') ? [{
icon: '🌱',
label: 'Soil warms to 10°C by',
value: soilWarmRow ? hhmm(soilWarmRow.iso) : 'Not today',
alert: !soilWarmRow,
- },
- rainItem,
- {
+ }] : []),
+ ...(show('precipProb') ? [rainItem] : []),
+ ...(show('soilM') ? [{
icon: '💧',
label: 'Soil moisture',
value: moistureLabel(soilM) ?? '-',
alert: soilM != null && (soilM < 0.10 || soilM > 0.50),
- },
- ...(maxPollen >= 10 ? [{
+ }] : []),
+ ...(show('pollen') && maxPollen >= 10 ? [{
icon: '🌿',
label: 'Grass pollen',
value: pollenLabel(maxPollen),
@@ -465,19 +466,18 @@ export function computeGlanceSummary(todayRows, profile, variant, skinType) {
const dangerFrom = todayRows.find(r => r.vehicleT != null && r.vehicleT >= 35);
return [
- {
+ ...(show('vehicleT') ? [{
icon: '🌡',
label: 'Peak cabin temp',
value: peakCabin ? `${Math.round(peakCabin.vehicleT)}° at ${hhmm(peakCabin.iso)}` : '-',
alert: !!(peakCabin && peakCabin.vehicleT >= 35),
- },
- {
+ }, {
icon: '🧒',
label: 'Children/pets in car',
value: dangerFrom ? `Unsafe from ${hhmm(dangerFrom.iso)}` : 'Safe all day',
alert: !!dangerFrom,
- },
- rainItem,
+ }] : []),
+ ...(show('precipProb') ? [rainItem] : []),
{
icon: '🌤',
label: 'Best travel comfort',
@@ -502,33 +502,33 @@ export function computeGlanceSummary(todayRows, profile, variant, skinType) {
const maxPollen = Math.max(0, ...todayRows.map(r => r.grassPollen ?? 0));
return [
- {
+ ...(show('indoorT') ? [{
icon: '🌡',
label: 'Peak indoor (unmanaged)',
value: peakIndoor ? `${Math.round(peakIndoor.indoorT)}° at ${hhmm(peakIndoor.iso)}` : '-',
alert: !!(peakIndoor && peakIndoor.indoorT >= 28),
- },
- {
+ }] : []),
+ ...(show('managedT') ? [{
icon: '🌡',
label: 'Peak indoor (managed)',
value: peakManaged ? `${Math.round(peakManaged.managedT)}°` : '-',
alert: !!(peakManaged && peakManaged.managedT >= 28),
- },
- {
+ }] : []),
+ ...(show('indoorT') ? [{
icon: '🪟',
label: 'Open windows',
value: ventWindow
? `${hhmm(ventWindow.start.iso)} – ${hhmm(ventWindow.end.iso)}`
: 'Keep closed',
alert: false,
- },
- {
+ }] : []),
+ ...(show('aqi') ? [{
icon: '💨',
label: 'Air quality',
value: peakAqi > 0 ? aqiLabel(peakAqi) : '-',
alert: peakAqi >= 60,
- },
- ...(maxPollen >= 10 ? [{
+ }] : []),
+ ...(show('pollen') && maxPollen >= 10 ? [{
icon: '🌼',
label: 'Pollen',
value: pollenLabel(maxPollen),
@@ -558,20 +558,20 @@ export function computeGlanceSummary(todayRows, profile, variant, skinType) {
: 'No cool window today',
alert: !coolWindow,
},
- rainItem,
- ...(burnMins ? [{
+ ...(show('precipProb') ? [rainItem] : []),
+ ...(show('burn') && burnMins ? [{
icon: '☀',
label: 'UV burn time (peak)',
value: burnMins,
alert: !!(peakUvRow && peakUvRow.uv >= 6),
}] : []),
- {
+ ...(show('aqi') ? [{
icon: '💨',
label: 'Air quality',
value: peakAqi > 0 ? aqiLabel(peakAqi) : '-',
alert: peakAqi >= 60,
- },
- ...(maxPollen >= 10 ? [{
+ }] : []),
+ ...(show('pollen') && maxPollen >= 10 ? [{
icon: '🌿',
label: 'Pollen',
value: pollenLabel(maxPollen),
@@ -587,6 +587,7 @@ export function computeGlanceSummary(todayRows, profile, variant, skinType) {
const peakFelt = peakRow('utciAdj');
const peakUvRow = peakRow('uv');
const peakAqi = Math.max(0, ...todayRows.map(r => r.aqi ?? 0));
+ const maxPollen = Math.max(0, ...todayRows.map(r => r.grassPollen ?? 0));
const burnMins = peakUvRow && peakUvRow.uv > 0
? burnLabel(sunburnMinutes(peakUvRow.uv, skinType))
: null;
@@ -606,18 +607,24 @@ export function computeGlanceSummary(todayRows, profile, variant, skinType) {
value: peakFelt ? `${Math.round(peakFelt.utciAdj)}° at ${hhmm(peakFelt.iso)}` : '-',
alert: !!(peakFelt && (peakFelt.utciAdj >= 32 || peakFelt.utciAdj < 0)),
},
- ...(burnMins ? [{
+ ...((show('burn') || show('uvA')) && burnMins ? [{
icon: '☀',
label: 'UV burn time (peak)',
value: burnMins,
alert: !!(peakUvRow && peakUvRow.uv >= 6),
}] : []),
- rainItem,
- {
+ ...(show('precipProb') ? [rainItem] : []),
+ ...(show('aqi') ? [{
icon: '💨',
label: 'Air quality',
value: peakAqi > 0 ? aqiLabel(peakAqi) : '-',
alert: peakAqi >= 60,
- },
+ }] : []),
+ ...(show('pollen') && maxPollen >= 10 ? [{
+ icon: '🌿',
+ label: 'Pollen',
+ value: pollenLabel(maxPollen),
+ alert: maxPollen >= 50,
+ }] : []),
];
}
\ No newline at end of file
diff --git a/assets/js/config.js b/assets/js/config.js
index 6ea8f74..57f30c0 100644
--- a/assets/js/config.js
+++ b/assets/js/config.js
@@ -29,7 +29,7 @@ export const FILTER_PROFILES = {
label: 'Basic',
icon: '🌡️',
scene: "url('assets/images/profiles/basic.png') center / cover no-repeat, linear-gradient(160deg,#b8d8e8,#daeef8)",
- cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: false, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: true, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false },
+ cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: false, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: true, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: false, aqi: false, pollen: true, rh: true },
},
home: {
label: 'Home',
diff --git a/build.js b/build.js
index c637375..621a13d 100644
--- a/build.js
+++ b/build.js
@@ -83,7 +83,7 @@ function rewriteHTML(html, version) {
html = html.replace(/