1.8.0
New table column order Table groups and colouring Chance of rain column
This commit is contained in:
@@ -710,6 +710,64 @@
|
|||||||
border-left: 2px solid rgba(74, 50, 24, 0.18);
|
border-left: 2px solid rgba(74, 50, 24, 0.18);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── COLUMN GROUP COLOURS — header cell tints per group ─────────────────
|
||||||
|
Each grp-* class gives the th a distinct background + text colour.
|
||||||
|
The hover state darkens slightly. The col-unit underneath inherits
|
||||||
|
a matching accent colour. */
|
||||||
|
|
||||||
|
.utci-table th.grp-felt { background: #dff3ec; color: #075c3a; }
|
||||||
|
.utci-table th.grp-surface { background: #ddeaf8; color: #0b3e72; }
|
||||||
|
.utci-table th.grp-ambient { background: #eceae3; color: #3c3a35; }
|
||||||
|
.utci-table th.grp-precip { background: #f7e4ee; color: #6b1e3a; }
|
||||||
|
.utci-table th.grp-sky { background: #eae8fc; color: #352e7a; }
|
||||||
|
.utci-table th.grp-wind { background: #fae6e0; color: #6a2210; }
|
||||||
|
.utci-table th.grp-airqual { background: #e5f0d8; color: #244408; }
|
||||||
|
.utci-table th.grp-solar { background: #faeacc; color: #5a3004; }
|
||||||
|
|
||||||
|
.utci-table th.grp-felt.col-info-th:hover { background: #c8ead9; }
|
||||||
|
.utci-table th.grp-surface.col-info-th:hover { background: #c5d9f0; }
|
||||||
|
.utci-table th.grp-ambient.col-info-th:hover { background: #d8d6ce; }
|
||||||
|
.utci-table th.grp-precip.col-info-th:hover { background: #eecfdf; }
|
||||||
|
.utci-table th.grp-sky.col-info-th:hover { background: #d4d0f5; }
|
||||||
|
.utci-table th.grp-wind.col-info-th:hover { background: #f0cfc5; }
|
||||||
|
.utci-table th.grp-airqual.col-info-th:hover { background: #d0e4bc; }
|
||||||
|
.utci-table th.grp-solar.col-info-th:hover { background: #f0d5a8; }
|
||||||
|
|
||||||
|
.utci-table th.grp-felt .col-unit { color: #0f9e63; }
|
||||||
|
.utci-table th.grp-surface .col-unit { color: #2672c8; }
|
||||||
|
.utci-table th.grp-ambient .col-unit { color: #7a7870; }
|
||||||
|
.utci-table th.grp-precip .col-unit { color: #b03060; }
|
||||||
|
.utci-table th.grp-sky .col-unit { color: #6058c8; }
|
||||||
|
.utci-table th.grp-wind .col-unit { color: #b04030; }
|
||||||
|
.utci-table th.grp-airqual .col-unit { color: #4a8018; }
|
||||||
|
.utci-table th.grp-solar .col-unit { color: #b07820; }
|
||||||
|
|
||||||
|
/* ── GROUP LABEL ROW — spans above the column header row ────────────────
|
||||||
|
A second tr in the thead. Each cell spans all visible columns in its
|
||||||
|
group. The Hour column gets a blank matching-background cell. */
|
||||||
|
|
||||||
|
.grp-label-row th {
|
||||||
|
font-family: Manrope, sans-serif;
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.10em;
|
||||||
|
padding: 4px 8px 2px;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
border-bottom: 1px solid rgba(0,0,0,0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grp-label-hour { background: #f0e4c4; }
|
||||||
|
.grp-label-felt { background: #dff3ec; color: #075c3a; border-left: 2px solid rgba(74,50,24,0.18); }
|
||||||
|
.grp-label-surface { background: #ddeaf8; color: #0b3e72; border-left: 2px solid rgba(74,50,24,0.18); }
|
||||||
|
.grp-label-ambient { background: #eceae3; color: #3c3a35; border-left: 2px solid rgba(74,50,24,0.18); }
|
||||||
|
.grp-label-precip { background: #f7e4ee; color: #6b1e3a; border-left: 2px solid rgba(74,50,24,0.18); }
|
||||||
|
.grp-label-sky { background: #eae8fc; color: #352e7a; border-left: 2px solid rgba(74,50,24,0.18); }
|
||||||
|
.grp-label-wind { background: #fae6e0; color: #6a2210; border-left: 2px solid rgba(74,50,24,0.18); }
|
||||||
|
.grp-label-airqual { background: #e5f0d8; color: #244408; border-left: 2px solid rgba(74,50,24,0.18); }
|
||||||
|
.grp-label-solar { background: #faeacc; color: #5a3004; border-left: 2px solid rgba(74,50,24,0.18); }
|
||||||
|
|
||||||
/* ── FILTER PROFILES + COLUMN TOGGLES — mobile fixes ───────────────── */
|
/* ── FILTER PROFILES + COLUMN TOGGLES — mobile fixes ───────────────── */
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
|
|
||||||
|
|||||||
+193
-165
@@ -84,13 +84,14 @@ export function UTCIForecast() {
|
|||||||
// Columns that mark the start of a logical group - used to draw a faint
|
// Columns that mark the start of a logical group - used to draw a faint
|
||||||
// vertical border separating groups in the forecast table.
|
// vertical border separating groups in the forecast table.
|
||||||
const GROUP_ORDER = {
|
const GROUP_ORDER = {
|
||||||
solar: ['sun', 'direct', 'diffuse', 'uvA', 'uvB', 'burn'],
|
felt: ['utciP', 'vehicleT', 'indoorT', 'managedT', 'burn', 'utci', 'delta', 'tmrt'],
|
||||||
sky: ['cloud', 'vis', 'aqi', 'pollen'],
|
surface: ['concreteT', 'soilT', 'soilT6', 'soilM'],
|
||||||
wind: ['wind', 'dir'],
|
|
||||||
ambient: ['air', 'rh', 'dew'],
|
ambient: ['air', 'rh', 'dew'],
|
||||||
felt: ['tmrt', 'utci', 'delta', 'utciP'],
|
precip: ['precip', 'precipProb'],
|
||||||
surface: ['concreteT', 'vehicleT', 'indoorT', 'managedT', 'soilT', 'soilT6', 'soilM'],
|
sky: ['cloud', 'vis'],
|
||||||
precip: ['precip'],
|
wind: ['wind', 'dir'],
|
||||||
|
airqual: ['aqi', 'pollen'],
|
||||||
|
solar: ['uvA', 'uvB', 'sun', 'direct', 'diffuse'],
|
||||||
};
|
};
|
||||||
const GROUP_OF = Object.fromEntries(
|
const GROUP_OF = Object.fromEntries(
|
||||||
Object.entries(GROUP_ORDER).flatMap(([g, keys]) => keys.map(k => [k, g]))
|
Object.entries(GROUP_ORDER).flatMap(([g, keys]) => keys.map(k => [k, g]))
|
||||||
@@ -109,6 +110,26 @@ export function UTCIForecast() {
|
|||||||
const first = GROUP_ORDER[group].find(isColVisible);
|
const first = GROUP_ORDER[group].find(isColVisible);
|
||||||
return first === key ? 'col-group-start' : '';
|
return first === key ? 'col-group-start' : '';
|
||||||
};
|
};
|
||||||
|
// Returns a CSS class encoding the group name - used to tint header cells
|
||||||
|
// and group label spans.
|
||||||
|
const groupColor = (key) => {
|
||||||
|
const g = GROUP_OF[key];
|
||||||
|
return g ? `grp-${g}` : '';
|
||||||
|
};
|
||||||
|
// Builds the group label row above the column headers.
|
||||||
|
// Each visible group gets one spanning cell; groups with no visible
|
||||||
|
// columns are skipped entirely. Hour always gets a blank lead cell.
|
||||||
|
const groupLabelRow = () => {
|
||||||
|
const groups = Object.keys(GROUP_ORDER);
|
||||||
|
const cells = [html`<th class="grp-label-hour" scope="col"></th>`];
|
||||||
|
for (const g of groups) {
|
||||||
|
const span = GROUP_ORDER[g].filter(isColVisible).length;
|
||||||
|
if (span === 0) continue;
|
||||||
|
const labels = { felt: 'Felt', surface: 'Surface', ambient: 'Ambient', precip: 'Precip', sky: 'Sky', wind: 'Wind', airqual: 'Air quality', solar: 'Solar' };
|
||||||
|
cells.push(html`<th class=${`grp-label grp-label-${g}`} colspan=${span} scope="colgroup">${labels[g]}</th>`);
|
||||||
|
}
|
||||||
|
return html`<tr class="grp-label-row">${cells}</tr>`;
|
||||||
|
};
|
||||||
|
|
||||||
// ─── 4. JSX RETURN ───────────────────────────────────────────────────
|
// ─── 4. JSX RETURN ───────────────────────────────────────────────────
|
||||||
// Everything below is the actual page markup, written as one big HTM
|
// Everything below is the actual page markup, written as one big HTM
|
||||||
@@ -313,65 +334,7 @@ export function UTCIForecast() {
|
|||||||
<div class="col-toggles">
|
<div class="col-toggles">
|
||||||
<span class="col-toggles-label">Columns:</span>
|
<span class="col-toggles-label">Columns:</span>
|
||||||
|
|
||||||
${isPro && (activeProfile === 'custom' || activeCols['air']) && html`<button class=${`col-toggle${visibleCols.air ? ' on' : ''}`} onClick=${() => toggleCol('air')}>Air</button>`}
|
${isPro && (activeProfile === 'custom' || activeCols['utciP']) && html`<button class=${`col-toggle${visibleCols.utciP ? ' on' : ''}`} onClick=${() => toggleCol('utciP')}>UTCI+P</button>`}
|
||||||
${isPro && (activeProfile === 'custom' || activeCols['rh']) && html`<button class=${`col-toggle${visibleCols.rh ? ' on' : ''}`} onClick=${() => toggleCol('rh')}>RH</button>`}
|
|
||||||
${isPro && (activeProfile === 'custom' || activeCols['dew']) && html`<button class=${`col-toggle${visibleCols.dew ? ' on' : ''}`} onClick=${() => toggleCol('dew')}>Dew</button>`}
|
|
||||||
${isPro && (activeProfile === 'custom' || activeCols['soilT']) && html`<button class=${`col-toggle${visibleCols.soilT ? ' on' : ''}`} onClick=${() => toggleCol('soilT')}>Soil °C</button>`}
|
|
||||||
${isPro && (activeProfile === 'custom' || activeCols['soilT6']) && html`<button class=${`col-toggle${visibleCols.soilT6 ? ' on' : ''}`} onClick=${() => toggleCol('soilT6')}>Soil 6cm</button>`}
|
|
||||||
${isPro && (activeProfile === 'custom' || activeCols['soilM']) && html`<button class=${`col-toggle${visibleCols.soilM ? ' on' : ''}`} onClick=${() => toggleCol('soilM')}>Soil moist</button>`}
|
|
||||||
${isPro && (activeProfile === 'custom' || activeCols['concreteT']) && html`<button class=${`col-toggle${visibleCols.concreteT ? ' on' : ''}`} onClick=${() => toggleCol('concreteT')}>Concrete</button>`}
|
|
||||||
${isPro && (activeProfile === 'custom' || activeCols['wind']) && html`<button class=${`col-toggle${visibleCols.wind ? ' on' : ''}`} onClick=${() => toggleCol('wind')}>Wind</button>`}
|
|
||||||
${isPro && (activeProfile === 'custom' || activeCols['dir']) && html`<button class=${`col-toggle${visibleCols.dir ? ' on' : ''}`} onClick=${() => toggleCol('dir')}>Dir</button>`}
|
|
||||||
${isPro && (activeProfile === 'custom' || activeCols['cloud']) && html`<button class=${`col-toggle${visibleCols.cloud ? ' on' : ''}`} onClick=${() => toggleCol('cloud')}>Cloud</button>`}
|
|
||||||
${isPro && (activeProfile === 'custom' || activeCols['vis']) && html`<button class=${`col-toggle${visibleCols.vis ? ' on' : ''}`} onClick=${() => toggleCol('vis')}>Visibility</button>`}
|
|
||||||
${isPro && (activeProfile === 'custom' || activeCols['aqi']) && html`<button class=${`col-toggle${visibleCols.aqi ? ' on' : ''}`} onClick=${() => toggleCol('aqi')}>Air Quality</button>`}
|
|
||||||
${(isPro ? (activeProfile === 'custom' || activeCols['pollen']) : activeCols['pollen']) && html`<${CustomSelect}
|
|
||||||
value=${pollenType}
|
|
||||||
isOn=${visibleCols.pollen}
|
|
||||||
hideLabel="Pollen"
|
|
||||||
hidingLabel="Hide Pollen"
|
|
||||||
options=${Object.entries(POLLEN_TYPES).flatMap(([k, v], i) => [
|
|
||||||
{ value: k, label: v.name },
|
|
||||||
...(i === 0 ? [{ value: '_div', divider: true }] : []),
|
|
||||||
])}
|
|
||||||
onChange=${(v) => {
|
|
||||||
if (v === 'off') {
|
|
||||||
setVisibleCols(prev => ({ ...prev, pollen: false }));
|
|
||||||
} else {
|
|
||||||
setPollenTypeAndSave(v);
|
|
||||||
setVisibleCols(prev => ({ ...prev, pollen: true }));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>`}
|
|
||||||
${isPro && (activeProfile === 'custom' || activeCols['sun']) && html`<button class=${`col-toggle${visibleCols.sun ? ' on' : ''}`} onClick=${() => toggleCol('sun')}>Sun</button>`}
|
|
||||||
${isPro && (activeProfile === 'custom' || activeCols['direct']) && html`<button class=${`col-toggle${visibleCols.direct ? ' on' : ''}`} onClick=${() => toggleCol('direct')}>Direct</button>`}
|
|
||||||
${isPro && (activeProfile === 'custom' || activeCols['diffuse']) && html`<button class=${`col-toggle${visibleCols.diffuse ? ' on' : ''}`} onClick=${() => toggleCol('diffuse')}>Diffuse</button>`}
|
|
||||||
${isPro && (activeProfile === 'custom' || activeCols['tmrt']) && html`<button class=${`col-toggle${visibleCols.tmrt ? ' on' : ''}`} onClick=${() => toggleCol('tmrt')}>Tmrt</button>`}
|
|
||||||
${isPro && (activeProfile === 'custom' || activeCols['delta']) && html`<button class=${`col-toggle${visibleCols.delta ? ' on' : ''}`} onClick=${() => toggleCol('delta')}>Δ</button>`}
|
|
||||||
${isPro && (activeProfile === 'custom' || activeCols['utci']) && html`<button class=${`col-toggle${visibleCols.utci ? ' on' : ''}`} onClick=${() => toggleCol('utci')}>UTCI</button>`}
|
|
||||||
${isPro && (activeProfile === 'custom' || activeCols['uvA']) && html`<button class=${`col-toggle${visibleCols.uvA ? ' on' : ''}`} onClick=${() => toggleCol('uvA')}>UV-A</button>`}
|
|
||||||
${isPro && (activeProfile === 'custom' || activeCols['uvB']) && html`<button class=${`col-toggle${visibleCols.uvB ? ' on' : ''}`} onClick=${() => toggleCol('uvB')}>UV-B</button>`}
|
|
||||||
|
|
||||||
${(isPro ? (activeProfile === 'custom' || activeCols['burn']) : activeCols['burn']) && html`
|
|
||||||
<${CustomSelect}
|
|
||||||
value=${skinType}
|
|
||||||
isOn=${visibleCols.burn}
|
|
||||||
hideLabel="Burn"
|
|
||||||
hidingLabel="Hide Burn"
|
|
||||||
options=${Object.entries(SKIN_TYPES).map(([k, v]) => ({
|
|
||||||
value: k,
|
|
||||||
label: v.name.split(' · ')[1] + ' skin',
|
|
||||||
}))}
|
|
||||||
onChange=${(v) => {
|
|
||||||
if (v === 'off') {
|
|
||||||
setVisibleCols(prev => ({ ...prev, burn: false }));
|
|
||||||
} else {
|
|
||||||
setSkinType(v);
|
|
||||||
setVisibleCols(prev => ({ ...prev, burn: true }));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>`}
|
|
||||||
|
|
||||||
${(isPro ? (activeProfile === 'custom' || activeCols['vehicleT']) : activeCols['vehicleT']) && html`
|
${(isPro ? (activeProfile === 'custom' || activeCols['vehicleT']) : activeCols['vehicleT']) && html`
|
||||||
<span class=${'col-toggle-group' + (visibleCols.vehicleT ? ' col-toggle-group--expanded' : '')}>
|
<span class=${'col-toggle-group' + (visibleCols.vehicleT ? ' col-toggle-group--expanded' : '')}>
|
||||||
<${CustomSelect}
|
<${CustomSelect}
|
||||||
@@ -403,7 +366,6 @@ ${isPro && (activeProfile === 'custom' || activeCols['air']) && html`<button cla
|
|||||||
title="Ventilation — open windows significantly reduce cabin heat build-up"
|
title="Ventilation — open windows significantly reduce cabin heat build-up"
|
||||||
/>`}
|
/>`}
|
||||||
</span>`}
|
</span>`}
|
||||||
|
|
||||||
${(isPro ? (activeProfile === 'custom' || activeCols['indoorT'] || activeCols['managedT']) : (activeCols['indoorT'] || activeCols['managedT'])) && html`
|
${(isPro ? (activeProfile === 'custom' || activeCols['indoorT'] || activeCols['managedT']) : (activeCols['indoorT'] || activeCols['managedT'])) && html`
|
||||||
<span class=${'col-toggle-group' + (indoorMode === 'on' ? ' col-toggle-group--expanded' : '')}>
|
<span class=${'col-toggle-group' + (indoorMode === 'on' ? ' col-toggle-group--expanded' : '')}>
|
||||||
<${CustomSelect}
|
<${CustomSelect}
|
||||||
@@ -435,7 +397,65 @@ ${isPro && (activeProfile === 'custom' || activeCols['air']) && html`<button cla
|
|||||||
title="Managed: curtains closed by day, windows open when cooler outside"
|
title="Managed: curtains closed by day, windows open when cooler outside"
|
||||||
/>`}
|
/>`}
|
||||||
</span>`}
|
</span>`}
|
||||||
|
${(isPro ? (activeProfile === 'custom' || activeCols['burn']) : activeCols['burn']) && html`
|
||||||
|
<${CustomSelect}
|
||||||
|
value=${skinType}
|
||||||
|
isOn=${visibleCols.burn}
|
||||||
|
hideLabel="Burn"
|
||||||
|
hidingLabel="Hide Burn"
|
||||||
|
options=${Object.entries(SKIN_TYPES).map(([k, v]) => ({
|
||||||
|
value: k,
|
||||||
|
label: v.name.split(' · ')[1] + ' skin',
|
||||||
|
}))}
|
||||||
|
onChange=${(v) => {
|
||||||
|
if (v === 'off') {
|
||||||
|
setVisibleCols(prev => ({ ...prev, burn: false }));
|
||||||
|
} else {
|
||||||
|
setSkinType(v);
|
||||||
|
setVisibleCols(prev => ({ ...prev, burn: true }));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>`}
|
||||||
|
${isPro && (activeProfile === 'custom' || activeCols['utci']) && html`<button class=${`col-toggle${visibleCols.utci ? ' on' : ''}`} onClick=${() => toggleCol('utci')}>UTCI</button>`}
|
||||||
|
${isPro && (activeProfile === 'custom' || activeCols['delta']) && html`<button class=${`col-toggle${visibleCols.delta ? ' on' : ''}`} onClick=${() => toggleCol('delta')}>Δ</button>`}
|
||||||
|
${isPro && (activeProfile === 'custom' || activeCols['tmrt']) && html`<button class=${`col-toggle${visibleCols.tmrt ? ' on' : ''}`} onClick=${() => toggleCol('tmrt')}>Tmrt</button>`}
|
||||||
|
${isPro && (activeProfile === 'custom' || activeCols['concreteT']) && html`<button class=${`col-toggle${visibleCols.concreteT ? ' on' : ''}`} onClick=${() => toggleCol('concreteT')}>Concrete</button>`}
|
||||||
|
${isPro && (activeProfile === 'custom' || activeCols['soilT']) && html`<button class=${`col-toggle${visibleCols.soilT ? ' on' : ''}`} onClick=${() => toggleCol('soilT')}>Soil °C</button>`}
|
||||||
|
${isPro && (activeProfile === 'custom' || activeCols['soilT6']) && html`<button class=${`col-toggle${visibleCols.soilT6 ? ' on' : ''}`} onClick=${() => toggleCol('soilT6')}>Soil 6cm</button>`}
|
||||||
|
${isPro && (activeProfile === 'custom' || activeCols['soilM']) && html`<button class=${`col-toggle${visibleCols.soilM ? ' on' : ''}`} onClick=${() => toggleCol('soilM')}>Soil moist</button>`}
|
||||||
|
${isPro && (activeProfile === 'custom' || activeCols['air']) && html`<button class=${`col-toggle${visibleCols.air ? ' on' : ''}`} onClick=${() => toggleCol('air')}>Air</button>`}
|
||||||
|
${isPro && (activeProfile === 'custom' || activeCols['rh']) && html`<button class=${`col-toggle${visibleCols.rh ? ' on' : ''}`} onClick=${() => toggleCol('rh')}>RH</button>`}
|
||||||
|
${isPro && (activeProfile === 'custom' || activeCols['dew']) && html`<button class=${`col-toggle${visibleCols.dew ? ' on' : ''}`} onClick=${() => toggleCol('dew')}>Dew</button>`}
|
||||||
${isPro && (activeProfile === 'custom' || activeCols['precip']) && html`<button class=${`col-toggle${visibleCols.precip ? ' on' : ''}`} onClick=${() => toggleCol('precip')}>Precip</button>`}
|
${isPro && (activeProfile === 'custom' || activeCols['precip']) && html`<button class=${`col-toggle${visibleCols.precip ? ' on' : ''}`} onClick=${() => toggleCol('precip')}>Precip</button>`}
|
||||||
|
${isPro && (activeProfile === 'custom' || activeCols['precipProb']) && html`<button class=${`col-toggle${visibleCols.precipProb ? ' on' : ''}`} onClick=${() => toggleCol('precipProb')}>Rain%</button>`}
|
||||||
|
${isPro && (activeProfile === 'custom' || activeCols['cloud']) && html`<button class=${`col-toggle${visibleCols.cloud ? ' on' : ''}`} onClick=${() => toggleCol('cloud')}>Cloud</button>`}
|
||||||
|
${isPro && (activeProfile === 'custom' || activeCols['vis']) && html`<button class=${`col-toggle${visibleCols.vis ? ' on' : ''}`} onClick=${() => toggleCol('vis')}>Visibility</button>`}
|
||||||
|
${isPro && (activeProfile === 'custom' || activeCols['wind']) && html`<button class=${`col-toggle${visibleCols.wind ? ' on' : ''}`} onClick=${() => toggleCol('wind')}>Wind</button>`}
|
||||||
|
${isPro && (activeProfile === 'custom' || activeCols['dir']) && html`<button class=${`col-toggle${visibleCols.dir ? ' on' : ''}`} onClick=${() => toggleCol('dir')}>Dir</button>`}
|
||||||
|
${isPro && (activeProfile === 'custom' || activeCols['aqi']) && html`<button class=${`col-toggle${visibleCols.aqi ? ' on' : ''}`} onClick=${() => toggleCol('aqi')}>Air Quality</button>`}
|
||||||
|
${(isPro ? (activeProfile === 'custom' || activeCols['pollen']) : activeCols['pollen']) && html`<${CustomSelect}
|
||||||
|
value=${pollenType}
|
||||||
|
isOn=${visibleCols.pollen}
|
||||||
|
hideLabel="Pollen"
|
||||||
|
hidingLabel="Hide Pollen"
|
||||||
|
options=${Object.entries(POLLEN_TYPES).flatMap(([k, v], i) => [
|
||||||
|
{ value: k, label: v.name },
|
||||||
|
...(i === 0 ? [{ value: '_div', divider: true }] : []),
|
||||||
|
])}
|
||||||
|
onChange=${(v) => {
|
||||||
|
if (v === 'off') {
|
||||||
|
setVisibleCols(prev => ({ ...prev, pollen: false }));
|
||||||
|
} else {
|
||||||
|
setPollenTypeAndSave(v);
|
||||||
|
setVisibleCols(prev => ({ ...prev, pollen: true }));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>`}
|
||||||
|
${isPro && (activeProfile === 'custom' || activeCols['uvA']) && html`<button class=${`col-toggle${visibleCols.uvA ? ' on' : ''}`} onClick=${() => toggleCol('uvA')}>UV-A</button>`}
|
||||||
|
${isPro && (activeProfile === 'custom' || activeCols['uvB']) && html`<button class=${`col-toggle${visibleCols.uvB ? ' on' : ''}`} onClick=${() => toggleCol('uvB')}>UV-B</button>`}
|
||||||
|
${isPro && (activeProfile === 'custom' || activeCols['sun']) && html`<button class=${`col-toggle${visibleCols.sun ? ' on' : ''}`} onClick=${() => toggleCol('sun')}>Sun</button>`}
|
||||||
|
${isPro && (activeProfile === 'custom' || activeCols['direct']) && html`<button class=${`col-toggle${visibleCols.direct ? ' on' : ''}`} onClick=${() => toggleCol('direct')}>Direct</button>`}
|
||||||
|
${isPro && (activeProfile === 'custom' || activeCols['diffuse']) && html`<button class=${`col-toggle${visibleCols.diffuse ? ' on' : ''}`} onClick=${() => toggleCol('diffuse')}>Diffuse</button>`}
|
||||||
|
|
||||||
</div>`}
|
</div>`}
|
||||||
|
|
||||||
@@ -450,36 +470,38 @@ ${isPro && (activeProfile === 'custom' || activeCols['air']) && html`<button cla
|
|||||||
<div class="utci-thead-track" ref=${headTrackRef}>
|
<div class="utci-thead-track" ref=${headTrackRef}>
|
||||||
<table class="utci-table utci-table-head" ref=${headTableRef}>
|
<table class="utci-table utci-table-head" ref=${headTableRef}>
|
||||||
<thead>
|
<thead>
|
||||||
|
${groupLabelRow()}
|
||||||
<tr>
|
<tr>
|
||||||
<th class="col-info-th" scope="col" onClick=${(e) => handleThClick('hour', e)} onMouseEnter=${(e) => handleThEnter('hour', e)} onMouseLeave=${handleThLeave}>Hour</th>
|
<th class="col-info-th" scope="col" onClick=${(e) => handleThClick('hour', e)} onMouseEnter=${(e) => handleThEnter('hour', e)} onMouseLeave=${handleThLeave}>Hour</th>
|
||||||
${visibleCols.sun && html`<th class=${`col-info-th ${groupStart('sun')}`} scope="col" onClick=${(e) => handleThClick('sun', e)} onMouseEnter=${(e) => handleThEnter('sun', e)} onMouseLeave=${handleThLeave}>Sun <span class="col-unit">elev°</span></th>`}
|
${visibleCols.utciP && html`<th class=${`col-info-th ${groupStart('utciP')} ${groupColor('utciP')}`} scope="col" onClick=${(e) => handleThClick('utciP', e)} onMouseEnter=${(e) => handleThEnter('utciP', e)} onMouseLeave=${handleThLeave}>UTCI+P <span class="col-unit">°C adj.</span></th>`}
|
||||||
${visibleCols.direct && html`<th class=${`col-info-th ${groupStart('direct')}`} scope="col" onClick=${(e) => handleThClick('direct', e)} onMouseEnter=${(e) => handleThEnter('direct', e)} onMouseLeave=${handleThLeave}>Direct <span class="col-unit">W/m²</span></th>`}
|
${visibleCols.vehicleT && html`<th class=${`col-info-th ${groupStart('vehicleT')} ${groupColor('vehicleT')}`} scope="col" onClick=${(e) => handleThClick('vehicleT', e)} onMouseEnter=${(e) => handleThEnter('vehicleT', e)} onMouseLeave=${handleThLeave}>Vehicle <span class="col-unit">°C peak</span></th>`}
|
||||||
${visibleCols.diffuse && html`<th class=${`col-info-th ${groupStart('diffuse')}`} scope="col" onClick=${(e) => handleThClick('diffuse', e)} onMouseEnter=${(e) => handleThEnter('diffuse', e)} onMouseLeave=${handleThLeave}>Diffuse <span class="col-unit">W/m²</span></th>`}
|
${indoorMode === 'on' && !indoorManaged && html`<th class=${`col-info-th ${groupStart('indoorT')} ${groupColor('indoorT')}`} scope="col" onClick=${(e) => handleThClick('indoorT', e)} onMouseEnter=${(e) => handleThEnter('indoorT', e)} onMouseLeave=${handleThLeave}>Indoors <span class="col-unit">°C est.</span></th>`}
|
||||||
${visibleCols.uvA && html`<th class=${`col-info-th ${groupStart('uvA')}`} scope="col" onClick=${(e) => handleThClick('uvA', e)} onMouseEnter=${(e) => handleThEnter('uvA', e)} onMouseLeave=${handleThLeave}>UV-A <span class="col-unit">est. idx</span></th>`}
|
${indoorMode === 'on' && indoorManaged && html`<th class=${`col-info-th ${groupStart('managedT')} ${groupColor('managedT')}`} scope="col" onClick=${(e) => handleThClick('managedT', e)} onMouseEnter=${(e) => handleThEnter('managedT', e)} onMouseLeave=${handleThLeave}>Managed <span class="col-unit">°C est.</span></th>`}
|
||||||
${visibleCols.uvB && html`<th class=${`col-info-th ${groupStart('uvB')}`} scope="col" onClick=${(e) => handleThClick('uvB', e)} onMouseEnter=${(e) => handleThEnter('uvB', e)} onMouseLeave=${handleThLeave}>UV-B <span class="col-unit">est. idx</span></th>`}
|
${visibleCols.burn && html`<th class=${`col-info-th ${groupStart('burn')} ${groupColor('burn')}`} scope="col" onClick=${(e) => handleThClick('burn', e)} onMouseEnter=${(e) => handleThEnter('burn', e)} onMouseLeave=${handleThLeave}>Burn <span class="col-unit">to MED</span></th>`}
|
||||||
${visibleCols.burn && html`<th class=${`col-info-th ${groupStart('burn')}`} scope="col" onClick=${(e) => handleThClick('burn', e)} onMouseEnter=${(e) => handleThEnter('burn', e)} onMouseLeave=${handleThLeave}>Burn <span class="col-unit">to MED</span></th>`}
|
${visibleCols.utci && html`<th class=${`col-info-th ${groupStart('utci')} ${groupColor('utci')}`} scope="col" onClick=${(e) => handleThClick('utci', e)} onMouseEnter=${(e) => handleThEnter('utci', e)} onMouseLeave=${handleThLeave}>UTCI <span class="col-unit">°C felt</span></th>`}
|
||||||
${visibleCols.cloud && html`<th class=${`col-info-th ${groupStart('cloud')}`} scope="col" onClick=${(e) => handleThClick('cloud', e)} onMouseEnter=${(e) => handleThEnter('cloud', e)} onMouseLeave=${handleThLeave}>Cloud <span class="col-unit">%</span></th>`}
|
${visibleCols.delta && html`<th class=${`col-info-th ${groupStart('delta')} ${groupColor('delta')}`} scope="col" onClick=${(e) => handleThClick('delta', e)} onMouseEnter=${(e) => handleThEnter('delta', e)} onMouseLeave=${handleThLeave}>Δ <span class="col-unit">UTCI−Air</span></th>`}
|
||||||
${visibleCols.vis && html`<th class=${`utci-tight-head col-info-th ${groupStart('vis')}`} scope="col" onClick=${(e) => handleThClick('vis', e)} onMouseEnter=${(e) => handleThEnter('vis', e)} onMouseLeave=${handleThLeave}>Vis <span class="col-unit">km</span></th>`}
|
${visibleCols.tmrt && html`<th class=${`col-info-th ${groupStart('tmrt')} ${groupColor('tmrt')}`} scope="col" onClick=${(e) => handleThClick('tmrt', e)} onMouseEnter=${(e) => handleThEnter('tmrt', e)} onMouseLeave=${handleThLeave}>Tmrt <span class="col-unit">°C</span></th>`}
|
||||||
${visibleCols.aqi && html`<th class=${`col-info-th ${groupStart('aqi')}`} scope="col" onClick=${(e) => handleThClick('aqi', e)} onMouseEnter=${(e) => handleThEnter('aqi', e)} onMouseLeave=${handleThLeave}>AQI <span class="col-unit">EU idx</span></th>`}
|
${visibleCols.concreteT && html`<th class=${`col-info-th ${groupStart('concreteT')} ${groupColor('concreteT')}`} scope="col" onClick=${(e) => handleThClick('concreteT', e)} onMouseEnter=${(e) => handleThEnter('concreteT', e)} onMouseLeave=${handleThLeave}>Concrete <span class="col-unit">°C surface</span></th>`}
|
||||||
${visibleCols.pollen && html`<th class=${`col-info-th ${groupStart('pollen')}`} scope="col" onClick=${(e) => handleThClick('pollen', e)} onMouseEnter=${(e) => handleThEnter('pollen', e)} onMouseLeave=${handleThLeave}>${pollenType === 'all_pollen' ? 'Pollen' : POLLEN_TYPES[pollenType]?.name.replace(' pollen','') ?? 'Pollen'} <span class="col-unit">grains/m³</span></th>`}
|
${visibleCols.soilT && html`<th class=${`col-info-th ${groupStart('soilT')} ${groupColor('soilT')}`} scope="col" onClick=${(e) => handleThClick('soilT', e)} onMouseEnter=${(e) => handleThEnter('soilT', e)} onMouseLeave=${handleThLeave}>Soil °C <span class="col-unit">surface</span></th>`}
|
||||||
${visibleCols.wind && html`<th class=${`col-info-th ${groupStart('wind')}`} scope="col" onClick=${(e) => handleThClick('wind', e)} onMouseEnter=${(e) => handleThEnter('wind', e)} onMouseLeave=${handleThLeave}>Wind <span class="col-unit">mph (gust)</span></th>`}
|
${visibleCols.soilT6 && html`<th class=${`col-info-th ${groupStart('soilT6')} ${groupColor('soilT6')}`} scope="col" onClick=${(e) => handleThClick('soilT6', e)} onMouseEnter=${(e) => handleThEnter('soilT6', e)} onMouseLeave=${handleThLeave}>Soil 6cm <span class="col-unit">°C root</span></th>`}
|
||||||
${visibleCols.dir && html`<th class=${`utci-dir-cell col-info-th ${groupStart('dir')}`} scope="col" onClick=${(e) => handleThClick('dir', e)} onMouseEnter=${(e) => handleThEnter('dir', e)} onMouseLeave=${handleThLeave}>Dir <span class="col-unit">-</span></th>`}
|
${visibleCols.soilM && html`<th class=${`col-info-th ${groupStart('soilM')} ${groupColor('soilM')}`} scope="col" onClick=${(e) => handleThClick('soilM', e)} onMouseEnter=${(e) => handleThEnter('soilM', e)} onMouseLeave=${handleThLeave}>Soil moist <span class="col-unit">m³/m³</span></th>`}
|
||||||
${visibleCols.air && html`<th class=${`utci-tight-head col-info-th ${groupStart('air')}`} scope="col" onClick=${(e) => handleThClick('air', e)} onMouseEnter=${(e) => handleThEnter('air', e)} onMouseLeave=${handleThLeave}>Air <span class="col-unit">°C</span></th>`}
|
${visibleCols.air && html`<th class=${`utci-tight-head col-info-th ${groupStart('air')} ${groupColor('air')}`} scope="col" onClick=${(e) => handleThClick('air', e)} onMouseEnter=${(e) => handleThEnter('air', e)} onMouseLeave=${handleThLeave}>Air <span class="col-unit">°C</span></th>`}
|
||||||
${visibleCols.rh && html`<th class=${`col-info-th ${groupStart('rh')}`} scope="col" onClick=${(e) => handleThClick('rh', e)} onMouseEnter=${(e) => handleThEnter('rh', e)} onMouseLeave=${handleThLeave}>RH <span class="col-unit">%</span></th>`}
|
${visibleCols.rh && html`<th class=${`col-info-th ${groupStart('rh')} ${groupColor('rh')}`} scope="col" onClick=${(e) => handleThClick('rh', e)} onMouseEnter=${(e) => handleThEnter('rh', e)} onMouseLeave=${handleThLeave}>RH <span class="col-unit">%</span></th>`}
|
||||||
${visibleCols.dew && html`<th class=${`col-info-th ${groupStart('dew')}`} scope="col" onClick=${(e) => handleThClick('dew', e)} onMouseEnter=${(e) => handleThEnter('dew', e)} onMouseLeave=${handleThLeave}>Dew <span class="col-unit">°C</span></th>`}
|
${visibleCols.dew && html`<th class=${`col-info-th ${groupStart('dew')} ${groupColor('dew')}`} scope="col" onClick=${(e) => handleThClick('dew', e)} onMouseEnter=${(e) => handleThEnter('dew', e)} onMouseLeave=${handleThLeave}>Dew <span class="col-unit">°C</span></th>`}
|
||||||
${visibleCols.tmrt && html`<th class=${`col-info-th ${groupStart('tmrt')}`} scope="col" onClick=${(e) => handleThClick('tmrt', e)} onMouseEnter=${(e) => handleThEnter('tmrt', e)} onMouseLeave=${handleThLeave}>Tmrt <span class="col-unit">°C</span></th>`}
|
${visibleCols.precip && html`<th class=${`utci-tight-head col-info-th ${groupStart('precip')} ${groupColor('precip')}`} scope="col" onClick=${(e) => handleThClick('precip', e)} onMouseEnter=${(e) => handleThEnter('precip', e)} onMouseLeave=${handleThLeave}>Pcpt <span class="col-unit">mm/h</span></th>`}
|
||||||
${visibleCols.utci && html`<th class=${`col-info-th ${groupStart('utci')}`} scope="col" onClick=${(e) => handleThClick('utci', e)} onMouseEnter=${(e) => handleThEnter('utci', e)} onMouseLeave=${handleThLeave}>UTCI <span class="col-unit">°C felt</span></th>`}
|
${visibleCols.precipProb && html`<th class=${`utci-tight-head col-info-th ${groupStart('precipProb')} ${groupColor('precipProb')}`} scope="col" onClick=${(e) => handleThClick('precipProb', e)} onMouseEnter=${(e) => handleThEnter('precipProb', e)} onMouseLeave=${handleThLeave}>Rain <span class="col-unit">%</span></th>`}
|
||||||
${visibleCols.delta && html`<th class=${`col-info-th ${groupStart('delta')}`} scope="col" onClick=${(e) => handleThClick('delta', e)} onMouseEnter=${(e) => handleThEnter('delta', e)} onMouseLeave=${handleThLeave}>Δ <span class="col-unit">UTCI−Air</span></th>`}
|
${visibleCols.cloud && html`<th class=${`col-info-th ${groupStart('cloud')} ${groupColor('cloud')}`} scope="col" onClick=${(e) => handleThClick('cloud', e)} onMouseEnter=${(e) => handleThEnter('cloud', e)} onMouseLeave=${handleThLeave}>Cloud <span class="col-unit">%</span></th>`}
|
||||||
${visibleCols.utciP && html`<th class=${`col-info-th ${groupStart('utciP')}`} scope="col" onClick=${(e) => handleThClick('utciP', e)} onMouseEnter=${(e) => handleThEnter('utciP', e)} onMouseLeave=${handleThLeave}>UTCI+P <span class="col-unit">°C adj.</span></th>`}
|
${visibleCols.vis && html`<th class=${`utci-tight-head col-info-th ${groupStart('vis')} ${groupColor('vis')}`} scope="col" onClick=${(e) => handleThClick('vis', e)} onMouseEnter=${(e) => handleThEnter('vis', e)} onMouseLeave=${handleThLeave}>Vis <span class="col-unit">km</span></th>`}
|
||||||
${visibleCols.concreteT && html`<th class=${`col-info-th ${groupStart('concreteT')}`} scope="col" onClick=${(e) => handleThClick('concreteT', e)} onMouseEnter=${(e) => handleThEnter('concreteT', e)} onMouseLeave=${handleThLeave}>Concrete <span class="col-unit">°C surface</span></th>`}
|
${visibleCols.wind && html`<th class=${`col-info-th ${groupStart('wind')} ${groupColor('wind')}`} scope="col" onClick=${(e) => handleThClick('wind', e)} onMouseEnter=${(e) => handleThEnter('wind', e)} onMouseLeave=${handleThLeave}>Wind <span class="col-unit">mph (gust)</span></th>`}
|
||||||
${visibleCols.vehicleT && html`<th class=${`col-info-th ${groupStart('vehicleT')}`} scope="col" onClick=${(e) => handleThClick('vehicleT', e)} onMouseEnter=${(e) => handleThEnter('vehicleT', e)} onMouseLeave=${handleThLeave}>Vehicle <span class="col-unit">°C peak</span></th>`}
|
${visibleCols.dir && html`<th class=${`utci-dir-cell col-info-th ${groupStart('dir')} ${groupColor('dir')}`} scope="col" onClick=${(e) => handleThClick('dir', e)} onMouseEnter=${(e) => handleThEnter('dir', e)} onMouseLeave=${handleThLeave}>Dir <span class="col-unit">-</span></th>`}
|
||||||
${indoorMode === 'on' && !indoorManaged && html`<th class=${`col-info-th ${groupStart('indoorT')}`} scope="col" onClick=${(e) => handleThClick('indoorT', e)} onMouseEnter=${(e) => handleThEnter('indoorT', e)} onMouseLeave=${handleThLeave}>Indoors <span class="col-unit">°C est.</span></th>`}
|
${visibleCols.aqi && html`<th class=${`col-info-th ${groupStart('aqi')} ${groupColor('aqi')}`} scope="col" onClick=${(e) => handleThClick('aqi', e)} onMouseEnter=${(e) => handleThEnter('aqi', e)} onMouseLeave=${handleThLeave}>AQI <span class="col-unit">EU idx</span></th>`}
|
||||||
${indoorMode === 'on' && indoorManaged && html`<th class=${`col-info-th ${groupStart('managedT')}`} scope="col" onClick=${(e) => handleThClick('managedT', e)} onMouseEnter=${(e) => handleThEnter('managedT', e)} onMouseLeave=${handleThLeave}>Managed <span class="col-unit">°C est.</span></th>`}
|
${visibleCols.pollen && html`<th class=${`col-info-th ${groupStart('pollen')} ${groupColor('pollen')}`} scope="col" onClick=${(e) => handleThClick('pollen', e)} onMouseEnter=${(e) => handleThEnter('pollen', e)} onMouseLeave=${handleThLeave}>${pollenType === 'all_pollen' ? 'Pollen' : POLLEN_TYPES[pollenType]?.name.replace(' pollen','') ?? 'Pollen'} <span class="col-unit">grains/m³</span></th>`}
|
||||||
${visibleCols.soilT && html`<th class=${`col-info-th ${groupStart('soilT')}`} scope="col" onClick=${(e) => handleThClick('soilT', e)} onMouseEnter=${(e) => handleThEnter('soilT', e)} onMouseLeave=${handleThLeave}>Soil °C <span class="col-unit">surface</span></th>`}
|
${visibleCols.uvA && html`<th class=${`col-info-th ${groupStart('uvA')} ${groupColor('uvA')}`} scope="col" onClick=${(e) => handleThClick('uvA', e)} onMouseEnter=${(e) => handleThEnter('uvA', e)} onMouseLeave=${handleThLeave}>UV-A <span class="col-unit">est. idx</span></th>`}
|
||||||
${visibleCols.soilT6 && html`<th class=${`col-info-th ${groupStart('soilT6')}`} scope="col" onClick=${(e) => handleThClick('soilT6', e)} onMouseEnter=${(e) => handleThEnter('soilT6', e)} onMouseLeave=${handleThLeave}>Soil 6cm <span class="col-unit">°C root</span></th>`}
|
${visibleCols.uvB && html`<th class=${`col-info-th ${groupStart('uvB')} ${groupColor('uvB')}`} scope="col" onClick=${(e) => handleThClick('uvB', e)} onMouseEnter=${(e) => handleThEnter('uvB', e)} onMouseLeave=${handleThLeave}>UV-B <span class="col-unit">est. idx</span></th>`}
|
||||||
${visibleCols.soilM && html`<th class=${`col-info-th ${groupStart('soilM')}`} scope="col" onClick=${(e) => handleThClick('soilM', e)} onMouseEnter=${(e) => handleThEnter('soilM', e)} onMouseLeave=${handleThLeave}>Soil moist <span class="col-unit">m³/m³</span></th>`}
|
${visibleCols.sun && html`<th class=${`col-info-th ${groupStart('sun')} ${groupColor('sun')}`} scope="col" onClick=${(e) => handleThClick('sun', e)} onMouseEnter=${(e) => handleThEnter('sun', e)} onMouseLeave=${handleThLeave}>Sun <span class="col-unit">elev°</span></th>`}
|
||||||
${visibleCols.precip && html`<th class=${`utci-tight-head col-info-th ${groupStart('precip')}`} scope="col" onClick=${(e) => handleThClick('precip', e)} onMouseEnter=${(e) => handleThEnter('precip', e)} onMouseLeave=${handleThLeave}>Pcpt <span class="col-unit">mm/h</span></th>`}
|
${visibleCols.direct && html`<th class=${`col-info-th ${groupStart('direct')} ${groupColor('direct')}`} scope="col" onClick=${(e) => handleThClick('direct', e)} onMouseEnter=${(e) => handleThEnter('direct', e)} onMouseLeave=${handleThLeave}>Direct <span class="col-unit">W/m²</span></th>`}
|
||||||
</tr>
|
${visibleCols.diffuse && html`<th class=${`col-info-th ${groupStart('diffuse')} ${groupColor('diffuse')}`} scope="col" onClick=${(e) => handleThClick('diffuse', e)} onMouseEnter=${(e) => handleThEnter('diffuse', e)} onMouseLeave=${handleThLeave}>Diffuse <span class="col-unit">W/m²</span></th>`}
|
||||||
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -584,20 +606,64 @@ ${isPro && (activeProfile === 'custom' || activeCols['air']) && html`<button cla
|
|||||||
})()}
|
})()}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
${visibleCols.sun && html`<td class=${groupStart('sun')} style=${{ background: scaleBg(r.elev > 0 ? r.elev : null, 0, 70, '225,160,45') }}>${r.elev > 0 ? r.elev.toFixed(1) : '—'}</td>`}
|
${visibleCols.utciP && html`
|
||||||
${visibleCols.direct && html`<td class=${groupStart('direct')} style=${{ background: scaleBg(r.dir, 0, 850, '230,155,35') }}>${Math.round(r.dir)}</td>`}
|
<td class=${groupStart('utciP')} style=${{ background: hexToRgba(adjCat.bg, 0.50), color: tempFontColorStrong(r.utciAdj), fontWeight: 700, fontSize: '13px' }}>
|
||||||
${visibleCols.diffuse && html`<td class=${groupStart('diffuse')} style=${{ background: scaleBg(r.dif, 0, 450, '230,190,70') }}>${Math.round(r.dif)}</td>`}
|
${r.utciAdj.toFixed(1)}°
|
||||||
${visibleCols.uvA && html`
|
</td>`}
|
||||||
<td class=${groupStart('uvA')} style=${{ color: r.uvA > 0 ? '#c8922a' : '#4a3218', background: scaleBg(r.uvA, 0, 8, '220,155,40') }}>
|
${visibleCols.vehicleT && html`
|
||||||
${r.uvA > 0 ? r.uvA.toFixed(1) : '—'}
|
<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: 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: tempFontColorStrong(r.managedT), fontWeight: 'bold', background: tempBgStrong(r.managedT) }}>
|
||||||
|
${r.managedT != null ? r.managedT.toFixed(1) : '—'}
|
||||||
|
</td>`}
|
||||||
|
${visibleCols.burn && html`
|
||||||
|
<td class=${groupStart('burn')} style=${{ color: r.uv > 0 ? (burnMins < 30 ? '#c44a3a' : '#c8601a') : '#4a3218', background: burnBg(burnMins, r.uv) }}>
|
||||||
|
${burnLabel(burnMins)}
|
||||||
|
</td>`}
|
||||||
|
${visibleCols.utci && html`
|
||||||
|
<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`
|
||||||
|
<td class=${groupStart('delta')} style=${{
|
||||||
|
color: delta > 3 ? '#c8601a' : delta < -3 ? '#3f73c4' : '#4a3218',
|
||||||
|
fontWeight: 600,
|
||||||
|
background: deltaBg(delta),
|
||||||
|
}}>
|
||||||
|
${delta > 0 ? '+' : ''}${delta.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.concreteT && html`
|
||||||
|
<td class=${groupStart('concreteT')} style=${{ color: tempFontColorStrong(r.concreteT), fontWeight: 'bold', background: tempBgStrong(r.concreteT) }}>
|
||||||
|
${r.concreteT != null ? r.concreteT.toFixed(1) : '—'}
|
||||||
|
</td>`}
|
||||||
|
${visibleCols.soilT && html`
|
||||||
|
<td class=${groupStart('soilT')} style=${{ color: tempFontColorStrong(r.soilT0), background: tempBgStrong(r.soilT0) }}>${r.soilT0 != null ? r.soilT0.toFixed(1) : '—'}</td>`}
|
||||||
|
${visibleCols.soilT6 && html`
|
||||||
|
<td class=${groupStart('soilT6')} style=${{ color: tempFontColorStrong(r.soilT6), background: tempBgStrong(r.soilT6) }}>${r.soilT6 != null ? r.soilT6.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.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: tempBgStrong(r.dew), color: tempFontColorStrong(r.dew) }}>${r.dew != null ? r.dew.toFixed(1) : '—'}</td>`}
|
||||||
|
${visibleCols.precip && html`
|
||||||
|
<td class=${groupStart('precip')} style=${{ background: r.snow > 0 ? scaleBg(r.snow, 0, 4, '90,140,210') : scaleBg(r.precip, 0, 8, '70,145,200') }}>
|
||||||
|
<span style=${{ display: 'inline-flex', alignItems: 'center', gap: '5px', verticalAlign: 'middle' }}>
|
||||||
|
<${PrecipIcon} precip=${r.precip} snow=${r.snow} size=${28} />
|
||||||
|
<span style=${{ color: r.snow > 0 ? '#2a5fa8' : r.precip > 0 ? '#2a6a90' : '#7a5c30' }}>
|
||||||
|
${r.snow > 0 ? r.snow.toFixed(1) + 'cm' : r.precip > 0 ? r.precip.toFixed(1) : '—'}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
</td>`}
|
</td>`}
|
||||||
${visibleCols.uvB && html`
|
${visibleCols.precipProb && html`
|
||||||
<td class=${groupStart('uvB')} style=${{ color: r.uvB > 0 ? '#c44a3a' : '#4a3218', background: scaleBg(r.uvB, 0, 1.2, '210,70,50') }}>
|
<td class=${groupStart('precipProb')} style=${{ background: scaleBg(r.precipProb, 0, 100, '70,145,200'), color: r.precipProb >= 50 ? '#1a4a70' : r.precipProb > 0 ? '#2a6a90' : '#7a8a90' }}>
|
||||||
${r.uvB > 0 ? r.uvB.toFixed(2) : '—'}
|
${r.precipProb}%
|
||||||
</td>`}
|
|
||||||
${visibleCols.burn && html`
|
|
||||||
<td class=${groupStart('burn')} style=${{ color: r.uv > 0 ? (burnMins < 30 ? '#c44a3a' : '#c8601a') : '#4a3218', background: burnBg(burnMins, r.uv) }}>
|
|
||||||
${burnLabel(burnMins)}
|
|
||||||
</td>`}
|
</td>`}
|
||||||
${visibleCols.cloud && html`<td class=${groupStart('cloud')} style=${{ background: scaleBg(r.cc, 0, 100, '110,130,150', 0.07), verticalAlign: 'middle' }}>
|
${visibleCols.cloud && html`<td class=${groupStart('cloud')} style=${{ background: scaleBg(r.cc, 0, 100, '110,130,150', 0.07), verticalAlign: 'middle' }}>
|
||||||
<span style=${{ display: 'inline-flex', alignItems: 'center', gap: '6px', verticalAlign: 'middle' }}>
|
<span style=${{ display: 'inline-flex', alignItems: 'center', gap: '6px', verticalAlign: 'middle' }}>
|
||||||
@@ -619,6 +685,17 @@ ${isPro && (activeProfile === 'custom' || activeCols['air']) && html`<button cla
|
|||||||
${v != null ? (v < 10 ? v.toFixed(1) : Math.round(v)) : '—'}
|
${v != null ? (v < 10 ? v.toFixed(1) : Math.round(v)) : '—'}
|
||||||
</td>`;
|
</td>`;
|
||||||
})()}
|
})()}
|
||||||
|
${visibleCols.wind && html`<td class=${groupStart('wind')} style=${{ background: scaleBg((r.gust ?? r.va) * 2.237, 0, 40, '85,130,180') }}>
|
||||||
|
${Math.round(r.va * 2.237)}${r.gust != null && r.gust > r.va + 0.5
|
||||||
|
? (gm => html`<span style=${{ marginLeft: '4px', fontWeight: gm >= 25 ? 700 : 'normal', opacity: gm >= 25 ? 1 : 0.65, color: gm >= 55 ? '#b81010' : gm >= 40 ? '#d44010' : gm >= 25 ? '#c47a00' : 'inherit' }}>(${Math.round(gm)})</span>`)(r.gust * 2.237)
|
||||||
|
: ''}
|
||||||
|
</td>`}
|
||||||
|
${visibleCols.dir && html`<td class=${`utci-dir-cell ${groupStart('dir')}`}>
|
||||||
|
<span style=${{ display: 'inline-flex', alignItems: 'center', gap: '6px', verticalAlign: 'middle' }}>
|
||||||
|
<${WindVane} bearing=${r.wd} size=${28} />
|
||||||
|
<span class="wind-dir-label" style=${{ fontFamily: 'Manrope, sans-serif', fontSize: '11px', fontWeight: 700 }}>${r.compass.label}</span>
|
||||||
|
</span>
|
||||||
|
</td>`}
|
||||||
${visibleCols.aqi && (() => {
|
${visibleCols.aqi && (() => {
|
||||||
const v = r.aqi;
|
const v = r.aqi;
|
||||||
const bg = v == null ? 'transparent'
|
const bg = v == null ? 'transparent'
|
||||||
@@ -662,66 +739,17 @@ ${isPro && (activeProfile === 'custom' || activeCols['air']) && html`<button cla
|
|||||||
: `${Math.round(v)} V.High`;
|
: `${Math.round(v)} V.High`;
|
||||||
return html`<td class=${groupStart('pollen')} style=${{ background: bg, color, fontWeight: v != null && v >= 50 ? 600 : 400 }}>${label}</td>`;
|
return html`<td class=${groupStart('pollen')} style=${{ background: bg, color, fontWeight: v != null && v >= 50 ? 600 : 400 }}>${label}</td>`;
|
||||||
})()}
|
})()}
|
||||||
${visibleCols.wind && html`<td class=${groupStart('wind')} style=${{ background: scaleBg((r.gust ?? r.va) * 2.237, 0, 40, '85,130,180') }}>
|
${visibleCols.uvA && html`
|
||||||
${Math.round(r.va * 2.237)}${r.gust != null && r.gust > r.va + 0.5
|
<td class=${groupStart('uvA')} style=${{ color: r.uvA > 0 ? '#c8922a' : '#4a3218', background: scaleBg(r.uvA, 0, 8, '220,155,40') }}>
|
||||||
? (gm => html`<span style=${{ marginLeft: '4px', fontWeight: gm >= 25 ? 700 : 'normal', opacity: gm >= 25 ? 1 : 0.65, color: gm >= 55 ? '#b81010' : gm >= 40 ? '#d44010' : gm >= 25 ? '#c47a00' : 'inherit' }}>(${Math.round(gm)})</span>`)(r.gust * 2.237)
|
${r.uvA > 0 ? r.uvA.toFixed(1) : '—'}
|
||||||
: ''}
|
|
||||||
</td>`}
|
|
||||||
${visibleCols.dir && html`<td class=${`utci-dir-cell ${groupStart('dir')}`}>
|
|
||||||
<span style=${{ display: 'inline-flex', alignItems: 'center', gap: '6px', verticalAlign: 'middle' }}>
|
|
||||||
<${WindVane} bearing=${r.wd} size=${28} />
|
|
||||||
<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: 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: 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.35), color: tempFontColorStrong(r.utci), fontWeight: 600 }}>
|
|
||||||
${r.utci.toFixed(1)}°
|
|
||||||
</td>`}
|
</td>`}
|
||||||
${visibleCols.delta && html`
|
${visibleCols.uvB && html`
|
||||||
<td class=${groupStart('delta')} style=${{
|
<td class=${groupStart('uvB')} style=${{ color: r.uvB > 0 ? '#c44a3a' : '#4a3218', background: scaleBg(r.uvB, 0, 1.2, '210,70,50') }}>
|
||||||
color: delta > 3 ? '#c8601a' : delta < -3 ? '#3f73c4' : '#4a3218',
|
${r.uvB > 0 ? r.uvB.toFixed(2) : '—'}
|
||||||
fontWeight: 600,
|
|
||||||
background: deltaBg(delta),
|
|
||||||
}}>
|
|
||||||
${delta > 0 ? '+' : ''}${delta.toFixed(1)}
|
|
||||||
</td>`}
|
|
||||||
${visibleCols.utciP && html`
|
|
||||||
<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: 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: 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: 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: 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: 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`
|
|
||||||
<td class=${groupStart('precip')} style=${{ background: r.snow > 0 ? scaleBg(r.snow, 0, 4, '90,140,210') : scaleBg(r.precip, 0, 8, '70,145,200') }}>
|
|
||||||
<span style=${{ display: 'inline-flex', alignItems: 'center', gap: '5px', verticalAlign: 'middle' }}>
|
|
||||||
<${PrecipIcon} precip=${r.precip} snow=${r.snow} size=${28} />
|
|
||||||
<span style=${{ color: r.snow > 0 ? '#2a5fa8' : r.precip > 0 ? '#2a6a90' : '#7a5c30' }}>
|
|
||||||
${r.snow > 0 ? r.snow.toFixed(1) + 'cm' : r.precip > 0 ? r.precip.toFixed(1) : '—'}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</td>`}
|
</td>`}
|
||||||
|
${visibleCols.sun && html`<td class=${groupStart('sun')} style=${{ background: scaleBg(r.elev > 0 ? r.elev : null, 0, 70, '225,160,45') }}>${r.elev > 0 ? r.elev.toFixed(1) : '—'}</td>`}
|
||||||
|
${visibleCols.direct && html`<td class=${groupStart('direct')} style=${{ background: scaleBg(r.dir, 0, 850, '230,155,35') }}>${Math.round(r.dir)}</td>`}
|
||||||
|
${visibleCols.diffuse && html`<td class=${groupStart('diffuse')} style=${{ background: scaleBg(r.dif, 0, 450, '230,190,70') }}>${Math.round(r.dif)}</td>`}
|
||||||
</tr>`;
|
</tr>`;
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -50,8 +50,9 @@ export function buildHourlyRows({ forecast, airQuality, location, vehicleType, v
|
|||||||
const ccMid = h.cloud_cover_mid ? h.cloud_cover_mid[i] : null;
|
const ccMid = h.cloud_cover_mid ? h.cloud_cover_mid[i] : null;
|
||||||
const ccHigh = h.cloud_cover_high ? h.cloud_cover_high[i] : null;
|
const ccHigh = h.cloud_cover_high ? h.cloud_cover_high[i] : null;
|
||||||
const uv = h.uv_index ? (h.uv_index[i] || 0) : 0;
|
const uv = h.uv_index ? (h.uv_index[i] || 0) : 0;
|
||||||
const precip = h.precipitation[i] || 0;
|
const precip = h.precipitation[i] || 0;
|
||||||
const snow = h.snowfall[i] || 0;
|
const precipProb = h.precipitation_probability ? (h.precipitation_probability[i] ?? 0) : 0;
|
||||||
|
const snow = h.snowfall[i] || 0;
|
||||||
const soilT0 = h.soil_temperature_0cm ? h.soil_temperature_0cm[i] : null;
|
const soilT0 = h.soil_temperature_0cm ? h.soil_temperature_0cm[i] : null;
|
||||||
const soilT6 = h.soil_temperature_6cm ? h.soil_temperature_6cm[i] : null;
|
const soilT6 = h.soil_temperature_6cm ? h.soil_temperature_6cm[i] : null;
|
||||||
const soilM = h.soil_moisture_0_to_1cm ? h.soil_moisture_0_to_1cm[i] : null;
|
const soilM = h.soil_moisture_0_to_1cm ? h.soil_moisture_0_to_1cm[i] : null;
|
||||||
@@ -134,7 +135,7 @@ export function buildHourlyRows({ forecast, airQuality, location, vehicleType, v
|
|||||||
iso, dt, Ta, RH, dew, va, wd, gust, dir, dif, glob,
|
iso, dt, Ta, RH, dew, va, wd, gust, dir, dif, glob,
|
||||||
cc, ccLow, ccMid, ccHigh, cloudCat,
|
cc, ccLow, ccMid, ccHigh, cloudCat,
|
||||||
uv, uvA, uvB,
|
uv, uvA, uvB,
|
||||||
precip, snow,
|
precip, precipProb, snow,
|
||||||
soilT0, soilT6, soilM, vehicleT,
|
soilT0, soilT6, soilM, vehicleT,
|
||||||
effectiveRad,
|
effectiveRad,
|
||||||
elev, Tmrt, utci, utciAdj, eh, compass,
|
elev, Tmrt, utci, utciAdj, eh, compass,
|
||||||
|
|||||||
+24
-23
@@ -27,40 +27,40 @@ export const FILTER_PROFILES = {
|
|||||||
basic: {
|
basic: {
|
||||||
label: 'Basic',
|
label: 'Basic',
|
||||||
icon: '🌡️',
|
icon: '🌡️',
|
||||||
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, 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: true, aqi: false, pollen: false },
|
||||||
},
|
},
|
||||||
home: {
|
home: {
|
||||||
label: 'Home',
|
label: 'Home',
|
||||||
icon: '🏠',
|
icon: '🏠',
|
||||||
cols: { hour: true, air: true, rh: true, dew: true, 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: false, precip: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: true, managedT: true, vis: false, aqi: true, pollen: true },
|
cols: { hour: true, air: true, rh: true, dew: true, 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: false, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: true, managedT: true, vis: false, aqi: true, pollen: true },
|
||||||
},
|
},
|
||||||
vehicle: {
|
vehicle: {
|
||||||
label: 'Vehicle',
|
label: 'Vehicle',
|
||||||
icon: '🚗',
|
icon: '🚗',
|
||||||
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, soilT: false, soilT6: false, soilM: true, concreteT: false, vehicleT: true, 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: true, concreteT: false, vehicleT: true, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false },
|
||||||
},
|
},
|
||||||
farming: {
|
farming: {
|
||||||
label: 'Farming',
|
label: 'Farming',
|
||||||
icon: '🌾',
|
icon: '🌾',
|
||||||
cols: { hour: true, air: true, rh: true, dew: true, wind: true, dir: true, cloud: true, sun: true, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: true, precip: true, soilT: true, soilT6: true, soilM: true, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: false, aqi: true, pollen: true },
|
cols: { hour: true, air: true, rh: true, dew: true, wind: true, dir: true, cloud: true, sun: true, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: true, precip: true, precipProb: true, soilT: true, soilT6: true, soilM: true, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: false, aqi: true, pollen: true },
|
||||||
},
|
},
|
||||||
outdoors: {
|
outdoors: {
|
||||||
label: 'Places',
|
label: 'Places',
|
||||||
icon: '🌤️',
|
icon: '🌤️',
|
||||||
// Default cols match the first variant (urban). Switching variant updates visibleCols.
|
// Default cols match the first variant (urban). Switching variant updates visibleCols.
|
||||||
cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: true, uvB: false, burn: true, utciP: true, precip: true, soilT: false, soilT6: false, soilM: false, concreteT: true, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: true, pollen: false },
|
cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: true, uvB: false, burn: true, utciP: true, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: false, concreteT: true, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: true, pollen: false },
|
||||||
},
|
},
|
||||||
alltemps: {
|
alltemps: {
|
||||||
label: 'Temps',
|
label: 'Temps',
|
||||||
icon: '🌡️',
|
icon: '🌡️',
|
||||||
proOnly: true,
|
proOnly: true,
|
||||||
cols: { hour: true, air: true, rh: false, dew: false, wind: false, dir: false, cloud: false, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: true, precip: false, soilT: true, soilT6: false, soilM: false, concreteT: true, vehicleT: true, indoorT: true, managedT: true, vis: false, aqi: false, pollen: false },
|
cols: { hour: true, air: true, rh: false, dew: false, wind: false, dir: false, cloud: false, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: true, precip: false, precipProb: false, soilT: true, soilT6: false, soilM: false, concreteT: true, vehicleT: true, indoorT: true, managedT: true, vis: false, aqi: false, pollen: false },
|
||||||
},
|
},
|
||||||
custom: {
|
custom: {
|
||||||
label: 'Custom',
|
label: 'Custom',
|
||||||
icon: '⚙️',
|
icon: '⚙️',
|
||||||
proOnly: true,
|
proOnly: true,
|
||||||
cols: { hour: true, air: true, rh: true, dew: true, wind: true, dir: true, cloud: true, sun: true, direct: true, diffuse: true, tmrt: true, delta: true, utci: true, uvA: true, uvB: true, burn: true, utciP: true, precip: true, soilT: true, soilT6: true, soilM: true, concreteT: true, vehicleT: true, indoorT: true, managedT: true, vis: true, aqi: true, pollen: true },
|
cols: { hour: true, air: true, rh: true, dew: true, wind: true, dir: true, cloud: true, sun: true, direct: true, diffuse: true, tmrt: true, delta: true, utci: true, uvA: true, uvB: true, burn: true, utciP: true, precip: true, precipProb: true, soilT: true, soilT6: true, soilM: true, concreteT: true, vehicleT: true, indoorT: true, managedT: true, vis: true, aqi: true, pollen: true },
|
||||||
// -- FUTURE FEATURE v2: Saved Custom Profiles (Pro) -----------------------
|
// -- FUTURE FEATURE v2: Saved Custom Profiles (Pro) -----------------------
|
||||||
// Expand 'Custom' into a fully user-owned profile system. Pro users can:
|
// Expand 'Custom' into a fully user-owned profile system. Pro users can:
|
||||||
//
|
//
|
||||||
@@ -94,26 +94,26 @@ export const FILTER_PROFILES = {
|
|||||||
// Places sub-variants - each has its own column set.
|
// Places sub-variants - each has its own column set.
|
||||||
// Selecting a variant applies its cols to visibleCols.
|
// Selecting a variant applies its cols to visibleCols.
|
||||||
export const OUTDOORS_VARIANTS = {
|
export const OUTDOORS_VARIANTS = {
|
||||||
urban: { name: 'Urban', cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: true, uvB: false, burn: true, utciP: true, precip: true, soilT: false, soilT6: false, soilM: false, concreteT: true, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: true, pollen: false } },
|
urban: { name: 'Urban', cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: true, uvB: false, burn: true, utciP: true, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: false, concreteT: true, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: true, pollen: false } },
|
||||||
beach: { name: 'Beach', cols: { hour: true, air: true, rh: false, dew: true, wind: true, dir: true, cloud: true, sun: true, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: true, uvB: true, burn: true, utciP: true, precip: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false } },
|
beach: { name: 'Beach', cols: { hour: true, air: true, rh: false, dew: true, wind: true, dir: true, cloud: true, sun: true, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: true, uvB: true, burn: true, 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 } },
|
||||||
events: { name: 'Events', cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: true, uvB: false, burn: true, utciP: true, precip: true, soilT: false, soilT6: false, soilM: false, concreteT: true, vehicleT: false, indoorT: false, managedT: false, vis: false, aqi: false, pollen: true } },
|
events: { name: 'Events', cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: true, uvB: false, burn: true, utciP: true, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: false, concreteT: true, vehicleT: false, indoorT: false, managedT: false, vis: false, aqi: false, pollen: true } },
|
||||||
festival: { name: 'Festival', proOnly: true, cols: { hour: true, air: true, rh: false, dew: true, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: true, uvB: false, burn: true, utciP: true, precip: true, soilT: false, soilT6: false, soilM: true, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: false, aqi: true, pollen: true } },
|
festival: { name: 'Festival', proOnly: true, cols: { hour: true, air: true, rh: false, dew: true, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: true, uvB: false, burn: true, utciP: true, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: true, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: false, aqi: true, pollen: true } },
|
||||||
wintersports: { name: 'Winter Sports', proOnly: true, cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: true, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: true, uvB: true, burn: true, utciP: true, precip: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false } },
|
wintersports: { name: 'Winter Sports', proOnly: true, cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: true, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: true, uvB: true, burn: true, 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 } },
|
||||||
naturist: { name: 'Naturist', proOnly: true, cols: { hour: true, air: true, rh: false, dew: true, wind: true, dir: false, cloud: true, sun: true, direct: false, diffuse: false, tmrt: false, delta: false, utci: true, uvA: true, uvB: true, burn: true, utciP: true, precip: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: false, aqi: true, pollen: true } },
|
naturist: { name: 'Naturist', proOnly: true, cols: { hour: true, air: true, rh: false, dew: true, wind: true, dir: false, cloud: true, sun: true, direct: false, diffuse: false, tmrt: false, delta: false, utci: true, uvA: true, uvB: true, burn: true, utciP: true, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: false, aqi: true, pollen: true } },
|
||||||
sailing: { name: 'Sailing', proOnly: true, cols: { hour: true, air: true, rh: false, dew: true, wind: true, dir: true, cloud: true, sun: true, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: true, uvB: false, burn: true, utciP: true, precip: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false } },
|
sailing: { name: 'Sailing', proOnly: true, cols: { hour: true, air: true, rh: false, dew: true, wind: true, dir: true, cloud: true, sun: true, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: true, uvB: false, burn: true, 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 } },
|
||||||
// Places - free
|
// Places - free
|
||||||
park: { name: 'Park / Picnic', 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: true, utciP: true, precip: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: false, aqi: false, pollen: true } },
|
park: { name: 'Park / Picnic', 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: true, 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 } },
|
||||||
airport: { name: 'Airport / Travel', cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: false, precip: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false } },
|
airport: { name: 'Airport / Travel', cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: false, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false } },
|
||||||
// Places - Pro
|
// Places - Pro
|
||||||
construction: { name: 'Construction', proOnly: true, cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: true, uvA: false, uvB: false, burn: false, utciP: true, precip: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: true, pollen: false } },
|
construction: { name: 'Construction', proOnly: true, cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: true, 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: true, pollen: false } },
|
||||||
// Activities - free
|
// Activities - free
|
||||||
cycling: { name: 'Cycling', cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: true, utciP: true, precip: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false } },
|
cycling: { name: 'Cycling', cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: true, 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 } },
|
||||||
running: { name: 'Running', cols: { hour: true, air: true, rh: true, dew: false, wind: true, dir: false, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: true, uvA: false, uvB: false, burn: true, utciP: true, precip: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: false, aqi: false, pollen: true } },
|
running: { name: 'Running', cols: { hour: true, air: true, rh: true, dew: false, wind: true, dir: false, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: true, uvA: false, uvB: false, burn: true, 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 } },
|
||||||
dogwalk: { name: 'Dog Walking', 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, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: false, aqi: false, pollen: true } },
|
dogwalk: { name: 'Dog Walking', 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 } },
|
||||||
// Activities - Pro
|
// Activities - Pro
|
||||||
hiking: { name: 'Hiking', proOnly: true, cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: true, direct: false, diffuse: false, tmrt: false, delta: false, utci: true, uvA: true, uvB: true, burn: true, utciP: true, precip: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false } },
|
hiking: { name: 'Hiking', proOnly: true, cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: true, direct: false, diffuse: false, tmrt: false, delta: false, utci: true, uvA: true, uvB: true, burn: true, 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 } },
|
||||||
photography: { name: 'Photography', proOnly: true, cols: { hour: true, air: true, rh: false, dew: false, wind: false, dir: false, cloud: true, sun: true, direct: true, diffuse: true, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: false, precip: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false } },
|
photography: { name: 'Photography', proOnly: true, cols: { hour: true, air: true, rh: false, dew: false, wind: false, dir: false, cloud: true, sun: true, direct: true, diffuse: true, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: false, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false } },
|
||||||
fishing: { name: 'Fishing', proOnly: true, cols: { hour: true, air: true, rh: false, dew: true, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: true, precip: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false } },
|
fishing: { name: 'Fishing', proOnly: true, cols: { hour: true, air: true, rh: false, dew: true, wind: true, dir: true, 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 } },
|
||||||
};
|
};
|
||||||
|
|
||||||
// Pollen type options shown in the pulldown selector.
|
// Pollen type options shown in the pulldown selector.
|
||||||
@@ -176,6 +176,7 @@ export const COL_DESCRIPTIONS = {
|
|||||||
burn: { title: 'Burn Time', desc: 'Estimated time to reach one Minimal Erythemal Dose (MED) — the threshold for sunburn — based on the UV index and your selected skin type. This is a guide, not a medical measurement.' },
|
burn: { title: 'Burn Time', desc: 'Estimated time to reach one Minimal Erythemal Dose (MED) — the threshold for sunburn — based on the UV index and your selected skin type. This is a guide, not a medical measurement.' },
|
||||||
utciP: { title: 'UTCI+P', desc: 'SunScope\'s adjusted felt temperature: UTCI plus the soak-factor penalty for precipitation. Rain and snow on wet clothing can reduce the felt temperature by up to 8 °C.' },
|
utciP: { title: 'UTCI+P', desc: 'SunScope\'s adjusted felt temperature: UTCI plus the soak-factor penalty for precipitation. Rain and snow on wet clothing can reduce the felt temperature by up to 8 °C.' },
|
||||||
precip: { title: 'Precipitation', desc: 'Expected rainfall or snowfall in mm per hour. Snow is shown in cm. Even light drizzle meaningfully reduces felt temperature when combined with wind.' },
|
precip: { title: 'Precipitation', desc: 'Expected rainfall or snowfall in mm per hour. Snow is shown in cm. Even light drizzle meaningfully reduces felt temperature when combined with wind.' },
|
||||||
|
precipProb: { title: 'Rain Probability', desc: 'Probability of precipitation for this hour as a percentage (0-100%). Derived from Open-Meteo ensemble model runs. A high value means rain is likely even if the expected rate is low - useful for spotting showers that the deterministic forecast may miss.' },
|
||||||
soilT: { title: 'Soil Temperature', desc: 'Temperature of the soil at the surface (0 cm depth). Useful for planting decisions — most seeds germinate above 7–10 °C.' },
|
soilT: { title: 'Soil Temperature', desc: 'Temperature of the soil at the surface (0 cm depth). Useful for planting decisions — most seeds germinate above 7–10 °C.' },
|
||||||
soilT6: { title: 'Soil Temp 6 cm', desc: 'Temperature of the soil at 6 cm depth, the root zone for many crops and seedlings. Lags behind surface temperature by several hours.' },
|
soilT6: { title: 'Soil Temp 6 cm', desc: 'Temperature of the soil at 6 cm depth, the root zone for many crops and seedlings. Lags behind surface temperature by several hours.' },
|
||||||
soilM: { title: 'Soil Moisture', desc: 'Volumetric water content of the top 1 cm of soil (m³/m³). Values above 0.4 suggest saturated ground; below 0.2 indicates dry conditions.' },
|
soilM: { title: 'Soil Moisture', desc: 'Volumetric water content of the top 1 cm of soil (m³/m³). Values above 0.4 suggest saturated ground; below 0.2 indicates dry conditions.' },
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ function buildForecastUrl(loc) {
|
|||||||
`wind_speed_10m,wind_direction_10m,wind_gusts_10m,` +
|
`wind_speed_10m,wind_direction_10m,wind_gusts_10m,` +
|
||||||
`direct_radiation,diffuse_radiation,shortwave_radiation,` +
|
`direct_radiation,diffuse_radiation,shortwave_radiation,` +
|
||||||
`cloud_cover,cloud_cover_low,cloud_cover_mid,cloud_cover_high,` +
|
`cloud_cover,cloud_cover_low,cloud_cover_mid,cloud_cover_high,` +
|
||||||
`uv_index,precipitation,snowfall,visibility,` +
|
`uv_index,precipitation,precipitation_probability,snowfall,visibility,` +
|
||||||
`soil_temperature_0cm,soil_temperature_6cm,soil_moisture_0_to_1cm` +
|
`soil_temperature_0cm,soil_temperature_6cm,soil_moisture_0_to_1cm` +
|
||||||
`&wind_speed_unit=ms&timezone=auto&forecast_days=14`;
|
`&wind_speed_unit=ms&timezone=auto&forecast_days=14`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ export function useTableScroll({
|
|||||||
const bodyScroll = bodyScrollRef.current;
|
const bodyScroll = bodyScrollRef.current;
|
||||||
if (!headTable || !bodyTable || !bodyScroll) return;
|
if (!headTable || !bodyTable || !bodyScroll) return;
|
||||||
const bodyRow = bodyTable.querySelector('tbody tr');
|
const bodyRow = bodyTable.querySelector('tbody tr');
|
||||||
const headRow = headTable.querySelector('thead tr');
|
const headRow = headTable.querySelector('thead tr:last-child');
|
||||||
if (!bodyRow || !headRow) return;
|
if (!bodyRow || !headRow) return;
|
||||||
const headCells = Array.from(headRow.children);
|
const headCells = Array.from(headRow.children);
|
||||||
const bodyCells = Array.from(bodyRow.children);
|
const bodyCells = Array.from(bodyRow.children);
|
||||||
@@ -178,7 +178,7 @@ export function useTableScroll({
|
|||||||
// Step 4: restore the CSS-defined table-layout: fixed so the
|
// Step 4: restore the CSS-defined table-layout: fixed so the
|
||||||
// explicit cell widths we apply below are honoured by the browser
|
// explicit cell widths we apply below are honoured by the browser
|
||||||
// (not redistributed by the auto-layout algorithm).
|
// (not redistributed by the auto-layout algorithm).
|
||||||
headTable.style.tableLayout = '';
|
headTable.style.tableLayout = 'auto';
|
||||||
bodyTable.style.tableLayout = '';
|
bodyTable.style.tableLayout = '';
|
||||||
|
|
||||||
// Step 5: apply the final width to both head and body cells.
|
// Step 5: apply the final width to both head and body cells.
|
||||||
|
|||||||
Reference in New Issue
Block a user