diff --git a/assets/js/app.js b/assets/js/app.js index 29ec206..64eadc9 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -69,6 +69,12 @@ function interpolateRowAt(rows, t) { elev: L(lo.elev, hi.elev), glob: L(lo.glob, hi.glob), utciAdj: L(lo.utciAdj, hi.utciAdj), + // Felt-temp inputs — interpolated so the "Why It Feels" panel breakdown + // stays smooth and consistent with the (interpolated) utciAdj category. + Ta: L(lo.Ta, hi.Ta), + Tmrt: L(lo.Tmrt, hi.Tmrt), + va: L(lo.va, hi.va), + eh: L(lo.eh, hi.eh), cc: L(lo.cc, hi.cc), ccLow: L(lo.ccLow, hi.ccLow), ccMid: L(lo.ccMid, hi.ccMid), @@ -413,8 +419,8 @@ export function UTCIForecast() { const airTempRgbVeryStrong = (t) => airTempRgb(t, 0.25); // ─── 3b. PANEL COMPUTATIONS ────────────────────────────────────────── - // Derived from currentRow and the active day - no side effects. - const whyFeelsLike = computeWhyFeelsLike(currentRow ?? null, UTCI_ENVIRONMENTS[utciEnv]); + // whyFeelsLike is derived below, after the playback rows are resolved, so + // the panel can track the simulated instant during play/scrub (see panelRow). // Interpolated row at the precise current instant — used for the scope // display even outside playback so there's no jump when play is pressed. @@ -432,6 +438,13 @@ export function UTCIForecast() { : (liveElev ?? currentRow?.elev ?? 0); const scopeDt = simRow ? simRow.dt : now; const scopeCat = scopeRow ? utciCategory(scopeRow.utciAdj) : currentCat; + // "Why It Feels" panel always mirrors the scope dial: the simulated instant + // during play/scrub, and the interpolated "now" row (scopeRow → nowRow) when + // stopped. Sharing scopeRow/scopeCat guarantees the panel's thermal label can + // never disagree with the dial's reticle. + const panelRow = scopeRow; + const panelCat = scopeCat; + const whyFeelsLike = computeWhyFeelsLike(panelRow ?? null, UTCI_ENVIRONMENTS[utciEnv]); // Synthesize a storm overlay (lightning) when the simulated hour is wet; // outside playback keep the real active event. const scopeEvent = simRow ? ((simRow.precip ?? 0) >= 4 ? { id: 'storm' } : null) : lensEvent; @@ -751,7 +764,7 @@ export function UTCIForecast() {
- ${forecast && currentRow && whyFeelsLike && html` + ${forecast && panelRow && whyFeelsLike && html`
Why It Feels - ${currentCat && html`${currentCat.label}`} + ${panelCat && html`${panelCat.label}`}
${(() => { const _wflItems = [ - { label: 'Air temperature', icon: '🌡', value: currentRow.Ta, isBase: true }, + { label: 'Air temperature', icon: '🌡', value: panelRow.Ta, isBase: true }, { label: 'Sun and sky', icon: '☀', value: whyFeelsLike.sunAndSky }, { label: 'Wind', icon: '🌬', value: whyFeelsLike.wind }, { label: 'Humidity', icon: '💧', value: whyFeelsLike.humidity }, diff --git a/assets/js/components/DayTabs.js b/assets/js/components/DayTabs.js index f4cccc7..1b90be9 100644 --- a/assets/js/components/DayTabs.js +++ b/assets/js/components/DayTabs.js @@ -484,15 +484,27 @@ export function DayTabs({ title: 'Office is part of SunScope Extra', detail: 'Extra adds the Office profile with solar gain, managed indoor temperature, humidity, air quality, and rain probability — useful for commute planning and building comfort.', }, + 'variant:driver': { + title: 'Driver / Trucker is part of SunScope Extra', + detail: 'Extra adds the Driver / Trucker profile with vehicle interior temperature, UV, wind speed and direction, rain probability, and visibility — the conditions that matter for long hours on the road.', + }, 'export': { title: 'Export day data is part of SunScope Extra', detail: 'Extra lets you download a full hourly spreadsheet for any day — all 41 columns including felt temperature, UV, soil, wind, and air quality, styled and ready to use in Excel or LibreOffice.', }, }; - const promptCopy = extraPromptCopy[proPromptSource] || { - title: `${dayName}'s forecast is part of SunScope Extra`, - detail: 'Extra unlocks the full 14-day forecast, customisable columns, specialist profiles, and an ad-free view.', - }; + // Day-tab locks get the day-specific heading; any other locked feature + // without its own entry above falls back to a feature-agnostic message. + const fallbackCopy = proPromptSource === 'day' + ? { + title: `${dayName}'s forecast is part of SunScope Extra`, + detail: 'Extra unlocks the full 14-day forecast, customisable columns, specialist profiles, and an ad-free view.', + } + : { + title: 'This is part of SunScope Extra', + detail: 'Extra unlocks the full 14-day forecast, customisable columns, specialist profiles, and an ad-free view.', + }; + const promptCopy = extraPromptCopy[proPromptSource] || fallbackCopy; return html` <${SubscribeModal} title=${promptCopy.title}