1.8.1
Add table tick boxes for decimals and degree symbols in data Change Soil Moist to %
This commit is contained in:
+5
-5
@@ -153,7 +153,7 @@
|
|||||||
"name": "How can I tell if the ground is too wet for a motorhome or caravan?",
|
"name": "How can I tell if the ground is too wet for a motorhome or caravan?",
|
||||||
"acceptedAnswer": {
|
"acceptedAnswer": {
|
||||||
"@type": "Answer",
|
"@type": "Answer",
|
||||||
"text": "SunScope's soil moisture column shows volumetric water content in m³/m³. A reading above 0.4 indicates saturated ground where a heavy vehicle is likely to sink or churn the surface. Below 0.2 is dry and firm. The Vehicle profile includes soil moisture by default for motorhome and caravan users assessing pitch suitability."
|
"text": "SunScope's soil moisture column shows water saturation of the top 1 cm of soil as a percentage. A reading above 40% indicates saturated ground where a heavy vehicle is likely to sink or churn the surface. Below 20% is dry and firm. The Vehicle profile includes soil moisture by default for motorhome and caravan users assessing pitch suitability."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -411,7 +411,7 @@
|
|||||||
temperature</strong> give context, <strong>precipitation</strong> covers driving
|
temperature</strong> give context, <strong>precipitation</strong> covers driving
|
||||||
conditions, and <strong>UTCI+P</strong> tells you what it will feel like when you step
|
conditions, and <strong>UTCI+P</strong> tells you what it will feel like when you step
|
||||||
outside. <strong>Soil moisture</strong> is included specifically for motorhome and caravan
|
outside. <strong>Soil moisture</strong> is included specifically for motorhome and caravan
|
||||||
users — values above 0.4 m³/m³ indicate saturated ground where pitching or manoeuvring
|
users — values above 40% indicate saturated ground where pitching or manoeuvring
|
||||||
is likely to cause problems.
|
is likely to cause problems.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -476,7 +476,7 @@
|
|||||||
<strong>UV-A</strong> and <strong>burn time</strong> for daytime sun protection,
|
<strong>UV-A</strong> and <strong>burn time</strong> for daytime sun protection,
|
||||||
<strong>precipitation</strong> for planning, <strong>air quality index</strong> and
|
<strong>precipitation</strong> for planning, <strong>air quality index</strong> and
|
||||||
<strong>pollen</strong> — particularly relevant for multi-day outdoor exposure — and
|
<strong>pollen</strong> — particularly relevant for multi-day outdoor exposure — and
|
||||||
<strong>soil moisture</strong> — the mud indicator. Values above 0.4 m³/m³ mean the ground
|
<strong>soil moisture</strong> — the mud indicator. Values above 40% mean the ground
|
||||||
is saturated and vehicle access, camping areas, and walkways will be affected.
|
is saturated and vehicle access, camping areas, and walkways will be affected.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
@@ -652,7 +652,7 @@
|
|||||||
<tr><td><strong>Precip mm</strong></td><td>Expected rainfall or snowfall in mm per hour. Even light drizzle meaningfully reduces felt temperature when combined with wind.</td></tr>
|
<tr><td><strong>Precip mm</strong></td><td>Expected rainfall or snowfall in mm per hour. Even light drizzle meaningfully reduces felt temperature when combined with wind.</td></tr>
|
||||||
<tr><td><strong>Soil °C surface</strong></td><td>Temperature of the soil at the surface (0 cm depth). Most seeds germinate above 7–10 °C. Useful for sowing decisions and frost assessment.</td></tr>
|
<tr><td><strong>Soil °C surface</strong></td><td>Temperature of the soil at the surface (0 cm depth). Most seeds germinate above 7–10 °C. Useful for sowing decisions and frost assessment.</td></tr>
|
||||||
<tr><td><strong>Soil °C 6 cm</strong></td><td>Soil temperature at 6 cm depth — the root zone for many crops and seedlings. Lags behind the surface by several hours.</td></tr>
|
<tr><td><strong>Soil °C 6 cm</strong></td><td>Soil temperature at 6 cm depth — the root zone for many crops and seedlings. Lags behind the surface by several hours.</td></tr>
|
||||||
<tr><td><strong>Soil moisture</strong></td><td>Volumetric water content of the top 1 cm of soil in m³/m³. Above 0.4 indicates saturated ground; below 0.2 is dry. Useful for assessing whether ground is workable, and for motorhome or caravan pitch suitability.</td></tr>
|
<tr><td><strong>Soil moisture</strong></td><td>Water saturation of the top 1 cm of soil as a percentage. Above 40% indicates saturated ground; below 20% is dry. Useful for assessing whether ground is workable, and for motorhome or caravan pitch suitability.</td></tr>
|
||||||
<tr><td><strong>Concrete °C</strong></td><td>Estimated temperature of sun-exposed urban paving. Concrete absorbs more solar energy than grass and cannot cool itself through evaporation — surface temps can run 15–25 °C above air temperature on sunny days.</td></tr>
|
<tr><td><strong>Concrete °C</strong></td><td>Estimated temperature of sun-exposed urban paving. Concrete absorbs more solar energy than grass and cannot cool itself through evaporation — surface temps can run 15–25 °C above air temperature on sunny days.</td></tr>
|
||||||
<tr><td><strong>Vehicle °C</strong></td><td>Estimated ambient cabin temperature inside a sealed, parked vehicle — adjusted for your chosen vehicle type. Values above 35 °C are dangerous for children and pets; above 45 °C potentially fatal within minutes.</td></tr>
|
<tr><td><strong>Vehicle °C</strong></td><td>Estimated ambient cabin temperature inside a sealed, parked vehicle — adjusted for your chosen vehicle type. Values above 35 °C are dangerous for children and pets; above 45 °C potentially fatal within minutes.</td></tr>
|
||||||
<tr><td><strong>Indoors °C</strong></td><td>Estimated temperature inside a building with windows closed and no active cooling. Select your building type from the dropdown — brick, modern insulated, Victorian terrace, stone cottage, timber frame, top-floor flat, or conservatory. Each type has its own insulation, retained warmth, thermal mass, and glazing characteristics. Indoor peak typically lags the outdoor peak by 2–4 hours due to thermal mass.</td></tr>
|
<tr><td><strong>Indoors °C</strong></td><td>Estimated temperature inside a building with windows closed and no active cooling. Select your building type from the dropdown — brick, modern insulated, Victorian terrace, stone cottage, timber frame, top-floor flat, or conservatory. Each type has its own insulation, retained warmth, thermal mass, and glazing characteristics. Indoor peak typically lags the outdoor peak by 2–4 hours due to thermal mass.</td></tr>
|
||||||
@@ -801,7 +801,7 @@
|
|||||||
|
|
||||||
<details class="faq-block">
|
<details class="faq-block">
|
||||||
<summary class="faq-q">How can I tell if the ground is too wet for a motorhome or caravan?</summary>
|
<summary class="faq-q">How can I tell if the ground is too wet for a motorhome or caravan?</summary>
|
||||||
<p class="faq-a">SunScope's soil moisture column shows the volumetric water content of the top centimetre of soil in m³/m³. A reading above 0.4 indicates saturated ground where a heavy vehicle is likely to sink or churn the surface. Below 0.2 is dry and firm. The Vehicle profile includes soil moisture by default precisely for motorhome and caravan users who need to assess pitch suitability before arriving on site.</p>
|
<p class="faq-a">SunScope's soil moisture column shows the water saturation of the top centimetre of soil as a percentage. A reading above 40% indicates saturated ground where a heavy vehicle is likely to sink or churn the surface. Below 20% is dry and firm. The Vehicle profile includes soil moisture by default precisely for motorhome and caravan users who need to assess pitch suitability before arriving on site.</p>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details class="faq-block">
|
<details class="faq-block">
|
||||||
|
|||||||
@@ -679,6 +679,26 @@
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* display-options row - decimal and degree symbol toggles */
|
||||||
|
.display-options {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 14px;
|
||||||
|
padding: 4px 10px 2px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--c-muted, #888);
|
||||||
|
}
|
||||||
|
.display-opt {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.display-opt input[type="checkbox"] {
|
||||||
|
accent-color: var(--c-accent, #e07b2a);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── 12. RESPONSIVE (mobile) ───────────────────────────────────────── */
|
/* ── 12. RESPONSIVE (mobile) ───────────────────────────────────────── */
|
||||||
/* On screens narrower than 800px, stack the header vertically and
|
/* On screens narrower than 800px, stack the header vertically and
|
||||||
@@ -727,6 +747,8 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* -- CUSTOM SELECT BUTTON - mobile label clipping ----------------------
|
/* -- CUSTOM SELECT BUTTON - mobile label clipping ----------------------
|
||||||
On narrow screens buttons are fixed ~33% width. The .cs-btn-label span
|
On narrow screens buttons are fixed ~33% width. The .cs-btn-label span
|
||||||
wraps the text node so ellipsis can work (text-overflow needs a block
|
wraps the text node so ellipsis can work (text-overflow needs a block
|
||||||
|
|||||||
+33
-21
@@ -52,6 +52,8 @@ export function UTCIForecast() {
|
|||||||
isPro, setIsPro,
|
isPro, setIsPro,
|
||||||
activeProfile, setActiveProfile, activateProfile, activeCols,
|
activeProfile, setActiveProfile, activateProfile, activeCols,
|
||||||
visibleCols, setVisibleCols, toggleCol,
|
visibleCols, setVisibleCols, toggleCol,
|
||||||
|
showDecimals, toggleShowDecimals,
|
||||||
|
showDegree, toggleShowDegree,
|
||||||
activityOptions, placeOptions,
|
activityOptions, placeOptions,
|
||||||
activityValue, activityLabel, placeValue, placeLabel,
|
activityValue, activityLabel, placeValue, placeLabel,
|
||||||
skinType, setSkinType,
|
skinType, setSkinType,
|
||||||
@@ -459,8 +461,16 @@ ${isPro && (activeProfile === 'custom' || activeCols['utciP']) && html`<button c
|
|||||||
|
|
||||||
</div>`}
|
</div>`}
|
||||||
|
|
||||||
|
<div class="display-options">
|
||||||
|
<label class="display-opt">
|
||||||
|
<input type="checkbox" checked=${showDecimals} onChange=${toggleShowDecimals} />
|
||||||
|
Decimals
|
||||||
|
</label>
|
||||||
|
<label class="display-opt">
|
||||||
|
<input type="checkbox" checked=${showDegree} onChange=${toggleShowDegree} />
|
||||||
|
° symbol
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class=${`utci-table-wrap${tableCanScrollLeft ? ' scroll-fade-left' : ''}${tableCanScrollRight ? ' scroll-fade-right' : ''}`} ref=${tableWrapRef}>
|
<div class=${`utci-table-wrap${tableCanScrollLeft ? ' scroll-fade-left' : ''}${tableCanScrollRight ? ' scroll-fade-right' : ''}`} ref=${tableWrapRef}>
|
||||||
<span class="utci-scroll-chevron left" aria-hidden="true">‹</span>
|
<span class="utci-scroll-chevron left" aria-hidden="true">‹</span>
|
||||||
@@ -484,7 +494,7 @@ ${isPro && (activeProfile === 'custom' || activeCols['utciP']) && html`<button c
|
|||||||
${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.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.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.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.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.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.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.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">%</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.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')} ${groupColor('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')} ${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.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>`}
|
||||||
@@ -525,6 +535,8 @@ ${isPro && (activeProfile === 'custom' || activeCols['utciP']) && html`<button c
|
|||||||
const b = parseInt(h.slice(4,6),16);
|
const b = parseInt(h.slice(4,6),16);
|
||||||
return `rgba(${r},${g},${b},${a})`;
|
return `rgba(${r},${g},${b},${a})`;
|
||||||
};
|
};
|
||||||
|
const fmt = (v, dp = 1) => v == null ? '—' : (showDecimals ? v.toFixed(dp) : String(Math.round(v)));
|
||||||
|
const deg = showDegree ? '°' : '';
|
||||||
// Returns a background tint based on temperature value - all share the same
|
// Returns a background tint based on temperature value - all share the same
|
||||||
// Thermal Stress Bands colors with 50% alpha for consistency. Uses exact legend ranges.
|
// Thermal Stress Bands colors with 50% alpha for consistency. Uses exact legend ranges.
|
||||||
// Converts a hex colour - r,g,b components for rgba().
|
// Converts a hex colour - r,g,b components for rgba().
|
||||||
@@ -608,19 +620,19 @@ ${isPro && (activeProfile === 'custom' || activeCols['utciP']) && html`<button c
|
|||||||
</td>
|
</td>
|
||||||
${visibleCols.utciP && html`
|
${visibleCols.utciP && html`
|
||||||
<td class=${groupStart('utciP')} style=${{ background: hexToRgba(adjCat.bg, 0.50), color: tempFontColorStrong(r.utciAdj), fontWeight: 700, fontSize: '13px' }}>
|
<td class=${groupStart('utciP')} style=${{ background: hexToRgba(adjCat.bg, 0.50), color: tempFontColorStrong(r.utciAdj), fontWeight: 700, fontSize: '13px' }}>
|
||||||
${r.utciAdj.toFixed(1)}°
|
${fmt(r.utciAdj)}${deg}
|
||||||
</td>`}
|
</td>`}
|
||||||
${visibleCols.vehicleT && html`
|
${visibleCols.vehicleT && html`
|
||||||
<td class=${groupStart('vehicleT')} style=${{ color: tempFontColorStrong(r.vehicleT), fontWeight: 'bold', background: tempBgStrong(r.vehicleT) }}>
|
<td class=${groupStart('vehicleT')} style=${{ color: tempFontColorStrong(r.vehicleT), fontWeight: 'bold', background: tempBgStrong(r.vehicleT) }}>
|
||||||
${r.vehicleT != null ? r.vehicleT.toFixed(1) : '—'}
|
${fmt(r.vehicleT)}${deg}
|
||||||
</td>`}
|
</td>`}
|
||||||
${indoorMode === 'on' && !indoorManaged && html`
|
${indoorMode === 'on' && !indoorManaged && html`
|
||||||
<td class=${groupStart('indoorT')} style=${{ color: tempFontColorStrong(r.indoorT), fontWeight: 'bold', background: tempBgStrong(r.indoorT) }}>
|
<td class=${groupStart('indoorT')} style=${{ color: tempFontColorStrong(r.indoorT), fontWeight: 'bold', background: tempBgStrong(r.indoorT) }}>
|
||||||
${r.indoorT != null ? r.indoorT.toFixed(1) : '—'}
|
${fmt(r.indoorT)}${deg}
|
||||||
</td>`}
|
</td>`}
|
||||||
${indoorMode === 'on' && indoorManaged && html`
|
${indoorMode === 'on' && indoorManaged && html`
|
||||||
<td class=${groupStart('managedT')} style=${{ color: tempFontColorStrong(r.managedT), fontWeight: 'bold', background: tempBgStrong(r.managedT) }}>
|
<td class=${groupStart('managedT')} style=${{ color: tempFontColorStrong(r.managedT), fontWeight: 'bold', background: tempBgStrong(r.managedT) }}>
|
||||||
${r.managedT != null ? r.managedT.toFixed(1) : '—'}
|
${fmt(r.managedT)}${deg}
|
||||||
</td>`}
|
</td>`}
|
||||||
${visibleCols.burn && html`
|
${visibleCols.burn && html`
|
||||||
<td class=${groupStart('burn')} style=${{ color: r.uv > 0 ? (burnMins < 30 ? '#c44a3a' : '#c8601a') : '#4a3218', background: burnBg(burnMins, r.uv) }}>
|
<td class=${groupStart('burn')} style=${{ color: r.uv > 0 ? (burnMins < 30 ? '#c44a3a' : '#c8601a') : '#4a3218', background: burnBg(burnMins, r.uv) }}>
|
||||||
@@ -628,7 +640,7 @@ ${isPro && (activeProfile === 'custom' || activeCols['utciP']) && html`<button c
|
|||||||
</td>`}
|
</td>`}
|
||||||
${visibleCols.utci && html`
|
${visibleCols.utci && html`
|
||||||
<td class=${groupStart('utci')} style=${{ background: hexToRgba(cat.bg, 0.35), color: tempFontColorStrong(r.utci), fontWeight: 600 }}>
|
<td class=${groupStart('utci')} style=${{ background: hexToRgba(cat.bg, 0.35), color: tempFontColorStrong(r.utci), fontWeight: 600 }}>
|
||||||
${r.utci.toFixed(1)}°
|
${fmt(r.utci)}${deg}
|
||||||
</td>`}
|
</td>`}
|
||||||
${visibleCols.delta && html`
|
${visibleCols.delta && html`
|
||||||
<td class=${groupStart('delta')} style=${{
|
<td class=${groupStart('delta')} style=${{
|
||||||
@@ -636,28 +648,28 @@ ${isPro && (activeProfile === 'custom' || activeCols['utciP']) && html`<button c
|
|||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
background: deltaBg(delta),
|
background: deltaBg(delta),
|
||||||
}}>
|
}}>
|
||||||
${delta > 0 ? '+' : ''}${delta.toFixed(1)}
|
${delta > 0 ? '+' : ''}${fmt(delta)}
|
||||||
</td>`}
|
</td>`}
|
||||||
${visibleCols.tmrt && html`<td class=${groupStart('tmrt')} style=${{ background: tempBgStrong(r.Tmrt), color: tempFontColorStrong(r.Tmrt) }}>${r.Tmrt.toFixed(1)}</td>`}
|
${visibleCols.tmrt && html`<td class=${groupStart('tmrt')} style=${{ background: tempBgStrong(r.Tmrt), color: tempFontColorStrong(r.Tmrt) }}>${fmt(r.Tmrt)}${deg}</td>`}
|
||||||
${visibleCols.concreteT && html`
|
${visibleCols.concreteT && html`
|
||||||
<td class=${groupStart('concreteT')} style=${{ color: tempFontColorStrong(r.concreteT), fontWeight: 'bold', background: tempBgStrong(r.concreteT) }}>
|
<td class=${groupStart('concreteT')} style=${{ color: tempFontColorStrong(r.concreteT), fontWeight: 'bold', background: tempBgStrong(r.concreteT) }}>
|
||||||
${r.concreteT != null ? r.concreteT.toFixed(1) : '—'}
|
${fmt(r.concreteT)}${deg}
|
||||||
</td>`}
|
</td>`}
|
||||||
${visibleCols.soilT && html`
|
${visibleCols.soilT && html`
|
||||||
<td class=${groupStart('soilT')} style=${{ color: tempFontColorStrong(r.soilT0), background: tempBgStrong(r.soilT0) }}>${r.soilT0 != null ? r.soilT0.toFixed(1) : '—'}</td>`}
|
<td class=${groupStart('soilT')} style=${{ color: tempFontColorStrong(r.soilT0), background: tempBgStrong(r.soilT0) }}>${fmt(r.soilT0)}${deg}</td>`}
|
||||||
${visibleCols.soilT6 && html`
|
${visibleCols.soilT6 && html`
|
||||||
<td class=${groupStart('soilT6')} style=${{ color: tempFontColorStrong(r.soilT6), background: tempBgStrong(r.soilT6) }}>${r.soilT6 != null ? r.soilT6.toFixed(1) : '—'}</td>`}
|
<td class=${groupStart('soilT6')} style=${{ color: tempFontColorStrong(r.soilT6), background: tempBgStrong(r.soilT6) }}>${fmt(r.soilT6)}${deg}</td>`}
|
||||||
${visibleCols.soilM && html`
|
${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>`}
|
<td class=${groupStart('soilM')} style=${{ color: '#2a6a90', background: scaleBg(r.soilM, 0.12, 0.45, '50,125,190') }}>${r.soilM != null ? fmt(r.soilM * 100, 1) + '%' : '—'}</td>`}
|
||||||
${visibleCols.air && html`<td class=${groupStart('air')} style=${{ background: tempBg(r.Ta), color: tempFontColorStrong(r.Ta) }}>${r.Ta.toFixed(1)}</td>`}
|
${visibleCols.air && html`<td class=${groupStart('air')} style=${{ background: tempBg(r.Ta), color: tempFontColorStrong(r.Ta) }}>${fmt(r.Ta)}${deg}</td>`}
|
||||||
${visibleCols.rh && html`<td class=${groupStart('rh')} style=${{ background: scaleBg(r.RH, 30, 100, '70,145,200') }}>${Math.round(r.RH)}</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.dew && html`<td class=${groupStart('dew')} style=${{ background: tempBgStrong(r.dew), color: tempFontColorStrong(r.dew) }}>${fmt(r.dew)}${deg}</td>`}
|
||||||
${visibleCols.precip && html`
|
${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') }}>
|
<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' }}>
|
<span style=${{ display: 'inline-flex', alignItems: 'center', gap: '5px', verticalAlign: 'middle' }}>
|
||||||
<${PrecipIcon} precip=${r.precip} snow=${r.snow} size=${28} />
|
<${PrecipIcon} precip=${r.precip} snow=${r.snow} size=${28} />
|
||||||
<span style=${{ color: r.snow > 0 ? '#2a5fa8' : r.precip > 0 ? '#2a6a90' : '#7a5c30' }}>
|
<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) : '—'}
|
${r.snow > 0 ? fmt(r.snow) + 'cm' : r.precip > 0 ? fmt(r.precip) : '—'}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</td>`}
|
</td>`}
|
||||||
@@ -682,7 +694,7 @@ ${isPro && (activeProfile === 'custom' || activeCols['utciP']) && html`<button c
|
|||||||
: 'transparent';
|
: 'transparent';
|
||||||
const color = v != null && v < 4 ? '#8a3a1a' : '#4a3218';
|
const color = v != null && v < 4 ? '#8a3a1a' : '#4a3218';
|
||||||
return html`<td class=${groupStart('vis')} style=${{ background: bg, color }}>
|
return html`<td class=${groupStart('vis')} style=${{ background: bg, color }}>
|
||||||
${v != null ? (v < 10 ? v.toFixed(1) : Math.round(v)) : '—'}
|
${v != null ? fmt(v) : '—'}
|
||||||
</td>`;
|
</td>`;
|
||||||
})()}
|
})()}
|
||||||
${visibleCols.wind && html`<td class=${groupStart('wind')} style=${{ background: scaleBg((r.gust ?? r.va) * 2.237, 0, 40, '85,130,180') }}>
|
${visibleCols.wind && html`<td class=${groupStart('wind')} style=${{ background: scaleBg((r.gust ?? r.va) * 2.237, 0, 40, '85,130,180') }}>
|
||||||
@@ -741,13 +753,13 @@ ${isPro && (activeProfile === 'custom' || activeCols['utciP']) && html`<button c
|
|||||||
})()}
|
})()}
|
||||||
${visibleCols.uvA && html`
|
${visibleCols.uvA && html`
|
||||||
<td class=${groupStart('uvA')} style=${{ color: r.uvA > 0 ? '#c8922a' : '#4a3218', background: scaleBg(r.uvA, 0, 8, '220,155,40') }}>
|
<td class=${groupStart('uvA')} style=${{ color: r.uvA > 0 ? '#c8922a' : '#4a3218', background: scaleBg(r.uvA, 0, 8, '220,155,40') }}>
|
||||||
${r.uvA > 0 ? r.uvA.toFixed(1) : '—'}
|
${r.uvA > 0 ? fmt(r.uvA) : '—'}
|
||||||
</td>`}
|
</td>`}
|
||||||
${visibleCols.uvB && html`
|
${visibleCols.uvB && 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('uvB')} style=${{ color: r.uvB > 0 ? '#c44a3a' : '#4a3218', background: scaleBg(r.uvB, 0, 1.2, '210,70,50') }}>
|
||||||
${r.uvB > 0 ? r.uvB.toFixed(2) : '—'}
|
${r.uvB > 0 ? fmt(r.uvB, 2) : '—'}
|
||||||
</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.sun && html`<td class=${groupStart('sun')} style=${{ background: scaleBg(r.elev > 0 ? r.elev : null, 0, 70, '225,160,45') }}>${r.elev > 0 ? fmt(r.elev) + deg : '—'}</td>`}
|
||||||
${visibleCols.direct && html`<td class=${groupStart('direct')} style=${{ background: scaleBg(r.dir, 0, 850, '230,155,35') }}>${Math.round(r.dir)}</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>`}
|
${visibleCols.diffuse && html`<td class=${groupStart('diffuse')} style=${{ background: scaleBg(r.dif, 0, 450, '230,190,70') }}>${Math.round(r.dif)}</td>`}
|
||||||
</tr>`;
|
</tr>`;
|
||||||
|
|||||||
+1
-1
@@ -179,7 +179,7 @@ export const COL_DESCRIPTIONS = {
|
|||||||
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.' },
|
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: 'Water saturation of the top 1 cm of soil as a percentage. Above 40% suggests saturated ground; below 20% indicates dry conditions.' },
|
||||||
concreteT: { title: 'Concrete Surface', desc: 'Estimated temperature of sun-exposed urban concrete or paving. Concrete absorbs more solar energy than grass and cannot cool itself through evaporation — surface temps can run 15–25 °C above air temperature on sunny days.' },
|
concreteT: { title: 'Concrete Surface', desc: 'Estimated temperature of sun-exposed urban concrete or paving. Concrete absorbs more solar energy than grass and cannot cool itself through evaporation — surface temps can run 15–25 °C above air temperature on sunny days.' },
|
||||||
vehicleT: { title: 'Vehicle Interior', desc: 'Estimated ambient cabin temperature inside a parked vehicle. Cars heat rapidly through thin body panels and glass; motorhomes and caravans are modelled as insulated occupied living spaces with retained warmth, lower glass gain, and slower heat response. Dangerous for children and pets above 35 °C; potentially fatal above 45 °C.' },
|
vehicleT: { title: 'Vehicle Interior', desc: 'Estimated ambient cabin temperature inside a parked vehicle. Cars heat rapidly through thin body panels and glass; motorhomes and caravans are modelled as insulated occupied living spaces with retained warmth, lower glass gain, and slower heat response. Dangerous for children and pets above 35 °C; potentially fatal above 45 °C.' },
|
||||||
indoorT: { title: 'Indoors', desc: 'Estimated ambient temperature inside a selected building type with windows closed and no air conditioning. Accounts for retained warmth, window solar gain, internal gains, and thermal lag without repeatedly accumulating solar heat hour after hour.' },
|
indoorT: { title: 'Indoors', desc: 'Estimated ambient temperature inside a selected building type with windows closed and no air conditioning. Accounts for retained warmth, window solar gain, internal gains, and thermal lag without repeatedly accumulating solar heat hour after hour.' },
|
||||||
|
|||||||
@@ -207,6 +207,28 @@ export function useAppState() {
|
|||||||
try { localStorage.setItem('sunscope_pollen_type', v); } catch (e) { /* ignore */ }
|
try { localStorage.setItem('sunscope_pollen_type', v); } catch (e) { /* ignore */ }
|
||||||
setPollenType(v);
|
setPollenType(v);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [showDecimals, setShowDecimals] = useState(() => {
|
||||||
|
try { const s = localStorage.getItem('sunscope_show_decimals'); return s === null ? false : s !== '0'; } catch (e) { return false; }
|
||||||
|
});
|
||||||
|
const toggleShowDecimals = () => setShowDecimals(v => {
|
||||||
|
const next = !v;
|
||||||
|
try { localStorage.setItem('sunscope_show_decimals', next ? '1' : '0'); } catch (e) { /* ignore */ }
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
|
||||||
|
const [showDegree, setShowDegree] = useState(() => {
|
||||||
|
try {
|
||||||
|
const s = localStorage.getItem('sunscope_show_degree');
|
||||||
|
if (s !== null) return s !== '0';
|
||||||
|
return typeof window !== 'undefined' ? window.innerWidth >= 640 : true;
|
||||||
|
} catch (e) { return true; }
|
||||||
|
});
|
||||||
|
const toggleShowDegree = () => setShowDegree(v => {
|
||||||
|
const next = !v;
|
||||||
|
try { localStorage.setItem('sunscope_show_degree', next ? '1' : '0'); } catch (e) { /* ignore */ }
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
|
||||||
const searchTimeout = useRef(null);
|
const searchTimeout = useRef(null);
|
||||||
|
|
||||||
@@ -429,6 +451,8 @@ export function useAppState() {
|
|||||||
indoorManaged, setIndoorManaged,
|
indoorManaged, setIndoorManaged,
|
||||||
indoorMode, setIndoorMode,
|
indoorMode, setIndoorMode,
|
||||||
pollenType, setPollenTypeAndSave,
|
pollenType, setPollenTypeAndSave,
|
||||||
|
showDecimals, toggleShowDecimals,
|
||||||
|
showDegree, toggleShowDegree,
|
||||||
// table refs
|
// table refs
|
||||||
headStickyRef, headTrackRef, headTableRef,
|
headStickyRef, headTrackRef, headTableRef,
|
||||||
bodyScrollRef, bodyTableRef, tableWrapRef,
|
bodyScrollRef, bodyTableRef, tableWrapRef,
|
||||||
|
|||||||
Reference in New Issue
Block a user