Future Idea Comments
This commit is contained in:
@@ -143,6 +143,122 @@ export function UTCIForecast() {
|
|||||||
return localStorage.getItem('sunscope_pro') === '1';
|
return localStorage.getItem('sunscope_pro') === '1';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── FUTURE FEATURE v2: Dark Mode + Theme System (JSON-driven) ───────────────
|
||||||
|
// Add a `theme` state that drives a data-theme attribute on <body> or .utci-app.
|
||||||
|
// CSS custom properties do all the heavy lifting — no JS colour logic needed.
|
||||||
|
//
|
||||||
|
// THE THEME FILE: /assets/themes.json
|
||||||
|
// All themes live in a single JSON file. Easy to read, easy to edit —
|
||||||
|
// add a new theme by copying an existing block and changing the values.
|
||||||
|
// No build step, no JS changes required for new themes.
|
||||||
|
//
|
||||||
|
// Each theme has:
|
||||||
|
// id — unique key used in localStorage and data-theme attribute
|
||||||
|
// name — human-readable label (shown in theme picker UI)
|
||||||
|
// description — one-liner explaining the look/feel
|
||||||
|
// base — which base mode it extends: "light" | "dark"
|
||||||
|
// trigger — how/when it activates (see trigger types below)
|
||||||
|
// colors — the CSS custom properties it overrides (only what changes)
|
||||||
|
//
|
||||||
|
// Example themes.json:
|
||||||
|
// [
|
||||||
|
// {
|
||||||
|
// "id": "light",
|
||||||
|
// "name": "Light (Default)",
|
||||||
|
// "description": "Classic parchment and brass in full daylight",
|
||||||
|
// "base": "light",
|
||||||
|
// "trigger": { "type": "manual" },
|
||||||
|
// "colors": {
|
||||||
|
// "--bg": "#f5edd6",
|
||||||
|
// "--surface": "#ede0c4",
|
||||||
|
// "--brass": "#c8922a",
|
||||||
|
// "--text": "#2a1a08",
|
||||||
|
// "--text-muted": "#9a7d5a",
|
||||||
|
// "--accent": "#c8922a"
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "id": "dark",
|
||||||
|
// "name": "Dark",
|
||||||
|
// "description": "Deep walnut and glowing brass — made for night",
|
||||||
|
// "base": "dark",
|
||||||
|
// "trigger": { "type": "manual" },
|
||||||
|
// "colors": {
|
||||||
|
// "--bg": "#1a1208",
|
||||||
|
// "--surface": "#261a0a",
|
||||||
|
// "--brass": "#c8922a",
|
||||||
|
// "--text": "#f5edd6",
|
||||||
|
// "--text-muted": "#9a7d5a",
|
||||||
|
// "--accent": "#d4a030"
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "id": "christmas",
|
||||||
|
// "name": "Christmas",
|
||||||
|
// "description": "Holly green with crimson brass — Dec 20 to Jan 2",
|
||||||
|
// "base": "dark",
|
||||||
|
// "trigger": { "type": "date", "from": "12-20", "to": "01-02" },
|
||||||
|
// "colors": {
|
||||||
|
// "--bg": "#0f1f0f",
|
||||||
|
// "--surface": "#1a3a1a",
|
||||||
|
// "--brass": "#b8312f",
|
||||||
|
// "--text": "#f0ede0",
|
||||||
|
// "--text-muted": "#7a9a7a",
|
||||||
|
// "--accent": "#c8922a"
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "id": "halloween",
|
||||||
|
// "name": "Halloween",
|
||||||
|
// "description": "Near-black with amber and burnt orange — Oct 25–31",
|
||||||
|
// "base": "dark",
|
||||||
|
// "trigger": { "type": "date", "from": "10-25", "to": "10-31" },
|
||||||
|
// "colors": {
|
||||||
|
// "--bg": "#100a00",
|
||||||
|
// "--surface": "#1e1000",
|
||||||
|
// "--brass": "#cc6600",
|
||||||
|
// "--text": "#f0d080",
|
||||||
|
// "--text-muted": "#8a6020",
|
||||||
|
// "--accent": "#e07820"
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "id": "heatwave",
|
||||||
|
// "name": "Heatwave Alert",
|
||||||
|
// "description": "Deep red pulse when a dangerous heat day is forecast",
|
||||||
|
// "base": "light",
|
||||||
|
// "trigger": { "type": "alert", "alertType": "heat" },
|
||||||
|
// "colors": {
|
||||||
|
// "--brass": "#b82020",
|
||||||
|
// "--accent": "#c83030"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
//
|
||||||
|
// TRIGGER TYPES:
|
||||||
|
// "manual" — user picks it from the theme switcher in the nav, persisted
|
||||||
|
// to localStorage ('sunscope_theme')
|
||||||
|
// "date" — auto-applies between MM-DD dates (wraps year-end correctly)
|
||||||
|
// user can still override manually; auto resets when out of range
|
||||||
|
// "alert" — activates when daySummary (compute.js) fires a matching alert;
|
||||||
|
// subtle, temporary — reverts when alert clears
|
||||||
|
// "system" — mirrors the OS dark/light preference (prefers-color-scheme)
|
||||||
|
//
|
||||||
|
// HOW IT WORKS IN JS:
|
||||||
|
// 1. Fetch /assets/themes.json once on load (or bundle it as an import)
|
||||||
|
// 2. Walk the trigger rules to find the highest-priority active theme
|
||||||
|
// Priority: alert > manual > date > system
|
||||||
|
// 3. Apply the winning theme's colors as inline CSS variables on <body>
|
||||||
|
// Object.entries(theme.colors).forEach(([k,v]) =>
|
||||||
|
// document.body.style.setProperty(k, v))
|
||||||
|
// 4. Store active theme id in localStorage for manual overrides
|
||||||
|
// 5. A small theme-picker icon (🎨 or ☀️/🌙) in the top nav lets the
|
||||||
|
// user browse and manually select any theme
|
||||||
|
//
|
||||||
|
// const [theme, setTheme] = useState(() =>
|
||||||
|
// localStorage.getItem('sunscope_theme') || 'system');
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// (FREE_DAYS, FILTER_PROFILES and OUTDOORS_VARIANTS now live in ./config.js.)
|
// (FREE_DAYS, FILTER_PROFILES and OUTDOORS_VARIANTS now live in ./config.js.)
|
||||||
|
|
||||||
// Current active filter profile
|
// Current active filter profile
|
||||||
@@ -510,6 +626,24 @@ export function UTCIForecast() {
|
|||||||
glob=${currentRow?.glob ?? 0}
|
glob=${currentRow?.glob ?? 0}
|
||||||
activeEvent=${lensEvent}
|
activeEvent=${lensEvent}
|
||||||
/>
|
/>
|
||||||
|
{/* ── FUTURE FEATURE v2: Historical Context Line ─────────────────
|
||||||
|
Show a subtle single line directly below the dial readout:
|
||||||
|
e.g. "3.2°C above the May average for this location"
|
||||||
|
"Near normal for late August"
|
||||||
|
|
||||||
|
Design notes:
|
||||||
|
• Must be visually elegant — same Fraunces/Manrope type pairing
|
||||||
|
as the rest of the dial area, small and muted
|
||||||
|
• Positive delta: warm amber tone; negative delta: cool blue tone
|
||||||
|
• Source: Open-Meteo has a free /climate endpoint that returns
|
||||||
|
monthly climate normals (ERA5 reanalysis) for any lat/lon.
|
||||||
|
Fetch once on location change, cache in a ref. Compare today's
|
||||||
|
peak air temp (or UTCI) to that month's normal.
|
||||||
|
• Could also show min/max historical context for the week:
|
||||||
|
"Warmest day forecast this week" / "Coolest night since March"
|
||||||
|
• The fetch is separate from the main forecast — handle its own
|
||||||
|
loading/error state independently so it doesn't block the UI.
|
||||||
|
─────────────────────────────────────────────────────────────── */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
@@ -1117,6 +1251,54 @@ ${isPro && (activeProfile === 'custom' || activeCols['air']) && html`<button cla
|
|||||||
show, remove the visibleCols check around it. To rename a
|
show, remove the visibleCols check around it. To rename a
|
||||||
heading, edit the text inside the matching <th>.
|
heading, edit the text inside the matching <th>.
|
||||||
-->
|
-->
|
||||||
|
{/* ── FUTURE FEATURE v2: Sticky "Now" Row + Resizable Table ────────
|
||||||
|
TWO related ideas for the table UX:
|
||||||
|
|
||||||
|
1. AUTO-SCROLL TO NOW ON LOAD
|
||||||
|
On first render (or day change to today), scroll the tbody
|
||||||
|
so the current-hour row is visible — ideally centred or at
|
||||||
|
the top third of the viewport. Use a ref on the now-row <tr>
|
||||||
|
and call scrollIntoView({ block: 'center', behavior: 'smooth' })
|
||||||
|
inside a useEffect that fires when days[selectedDay] changes.
|
||||||
|
The now row already has `currentRow` identified — just need
|
||||||
|
to attach a ref and fire the scroll.
|
||||||
|
|
||||||
|
2. VERTICALLY RESIZABLE TABLE (extends existing drag-to-scroll)
|
||||||
|
The table already has horizontal drag-to-scroll (useTableScroll).
|
||||||
|
For v2, make the tbody height resizable — a drag handle on the
|
||||||
|
bottom edge of the table, similar to how devtools panels resize.
|
||||||
|
Store the preferred height in localStorage ('sunscope_table_height').
|
||||||
|
Combined with the now-row auto-scroll, the user sets the table to
|
||||||
|
exactly the number of rows they want to see at once and it always
|
||||||
|
opens at the current hour. Very clean UX.
|
||||||
|
────────────────────────────────────────────────────────────────── */}
|
||||||
|
{/* ── FUTURE FEATURE v2: Mobile Card Layout ─────────────────────────
|
||||||
|
On small screens the wide table is painful. Consider a responsive
|
||||||
|
breakpoint (e.g. < 640px) that switches from the table to a
|
||||||
|
vertical stack of hour cards:
|
||||||
|
|
||||||
|
┌──────────────────────────────────┐
|
||||||
|
│ 14:00 🌤️ SkyScope porthole │
|
||||||
|
│ Air 24°C Wind 12 km/h SW │
|
||||||
|
│ UTCI+P ████░░░░ 28.4°C Warm │
|
||||||
|
│ UV 6 · Burn 35 min · Precip – │
|
||||||
|
└──────────────────────────────────┘
|
||||||
|
|
||||||
|
Each card shows only the columns that are active in the current
|
||||||
|
profile — same visibility logic, different layout. The card design
|
||||||
|
should stay true to the brass/parchment aesthetic.
|
||||||
|
|
||||||
|
Implementation approach:
|
||||||
|
• CSS media query switches .utci-table-wrap to display:none
|
||||||
|
and shows a .utci-card-list instead (same data, different markup)
|
||||||
|
• OR: render the cards in JS from the same hourlyRows array,
|
||||||
|
conditionally based on a useWindowWidth() hook
|
||||||
|
• Needs careful thought on which columns to show in "summary"
|
||||||
|
view vs an expandable "detail" tap — don't want to overwhelm
|
||||||
|
the card but also don't want to hide too much.
|
||||||
|
• General spacing and layout passes needed for mobile regardless
|
||||||
|
of whether the card view is implemented in v2.
|
||||||
|
────────────────────────────────────────────────────────────────── */}
|
||||||
<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>
|
||||||
<span class="utci-scroll-chevron right" aria-hidden="true">›</span>
|
<span class="utci-scroll-chevron right" aria-hidden="true">›</span>
|
||||||
|
|||||||
@@ -81,6 +81,46 @@ export function buildHourlyRows({ forecast, airQuality, location, vehicleType, v
|
|||||||
const mugwortPollen= getAq('mugwort_pollen', iso);
|
const mugwortPollen= getAq('mugwort_pollen', iso);
|
||||||
const olivePollen = getAq('olive_pollen', iso);
|
const olivePollen = getAq('olive_pollen', iso);
|
||||||
const ragweedPollen= getAq('ragweed_pollen', iso);
|
const ragweedPollen= getAq('ragweed_pollen', iso);
|
||||||
|
// ── FUTURE FEATURE: Activity "What If" Modifier ───────────────────────────
|
||||||
|
// Add two extra columns driven by a user-selected activity level. These are
|
||||||
|
// intentionally kept SEPARATE from the core columns above so that baseline
|
||||||
|
// profile data stays consistent and comparable across profiles.
|
||||||
|
//
|
||||||
|
// The user picks an activity from a simple UI picker (no live data needed —
|
||||||
|
// this is a forecast/planning tool, not a tracker):
|
||||||
|
// Resting → Walking → Cycling → Running → Sport/Intense
|
||||||
|
//
|
||||||
|
// Two output columns only (keep it clean):
|
||||||
|
//
|
||||||
|
// adjustedSafeTime — baseline UV safe exposure time × an activity multiplier.
|
||||||
|
// Higher activity = shorter safe time, because:
|
||||||
|
// • metabolic heat raises core body temp
|
||||||
|
// • sweating washes away sunscreen faster
|
||||||
|
// • more skin blood flow = higher UV sensitivity
|
||||||
|
// Suggested multipliers (tune with real data):
|
||||||
|
// Resting: 1.0 (no change)
|
||||||
|
// Walking: 0.85
|
||||||
|
// Cycling: 0.75
|
||||||
|
// Running: 0.60
|
||||||
|
// Sport: 0.50
|
||||||
|
//
|
||||||
|
// heatStressLevel — a simple label: 'Low' | 'Moderate' | 'High' | 'Very High'
|
||||||
|
// Derived from UTCI + activity heat load. A runner at
|
||||||
|
// UTCI 28°C should read 'High' even if a resting person
|
||||||
|
// would read 'Moderate' at the same UTCI.
|
||||||
|
// Colour code in the UI: 🟢 🟡 🟠 🔴
|
||||||
|
//
|
||||||
|
// Implementation sketch:
|
||||||
|
// 1. Accept `activityLevel` as a new param to buildHourlyRows() alongside
|
||||||
|
// vehicleType, buildingType etc.
|
||||||
|
// 2. Define ACTIVITY_PRESETS in utils.js (multiplier + utciOffset per level).
|
||||||
|
// 3. Compute adjustedSafeTime = baseSafeTime * preset.multiplier
|
||||||
|
// 4. Compute heatStressLevel from (utci + preset.utciOffset) banded into labels.
|
||||||
|
// 5. Add both fields to the returned row object below.
|
||||||
|
// 6. In components.js, render these as optional columns that only appear when
|
||||||
|
// an activity other than 'Resting' is selected — keeps the default table clean.
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
return {
|
return {
|
||||||
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,
|
||||||
@@ -114,5 +154,45 @@ export function buildHourlyRows({ forecast, airQuality, location, vehicleType, v
|
|||||||
day.rows.push(row);
|
day.rows.push(row);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── FUTURE FEATURE v2: Today Summary + Alert System ──────────────────────────
|
||||||
|
// After grouping rows into days, generate a per-day summary object that powers
|
||||||
|
// a stylish "Today at a Glance" panel shown above or below the main dial.
|
||||||
|
//
|
||||||
|
// The summary is NOT a live alert/push system — it's a forecast digest that
|
||||||
|
// refreshes with the forecast data. Think of it as a smart briefing card.
|
||||||
|
//
|
||||||
|
// WHAT TO COMPUTE (per day, from that day's rows):
|
||||||
|
// • Peak UTCI+P and time it occurs → heat stress headline
|
||||||
|
// • Min UTCI+P and time → cold stress headline
|
||||||
|
// • Peak UV index and time → UV warning
|
||||||
|
// • Max precipitation rate and time → rain/ice warning
|
||||||
|
// • Max vehicle cabin temp → "dangerous to leave pets/children in car"
|
||||||
|
// • Max pollen level + type → pollen advisory
|
||||||
|
// • Road condition risk (low air temp + precip → ice risk)
|
||||||
|
//
|
||||||
|
// ALERT CATEGORIES (each generates a styled warning card if threshold exceeded):
|
||||||
|
// 🌡️ Heat warning UTCI+P > 32°C
|
||||||
|
// 🥶 Cold warning UTCI+P < 0°C
|
||||||
|
// ☀️ UV warning UV index > 6
|
||||||
|
// 🌧️ Heavy rain precip > 4mm/h
|
||||||
|
// 🧊 Ice/road risk Ta < 3°C + any precip (or recent precip overnight)
|
||||||
|
// 🚗 Vehicle danger vehicleT > 35°C ("don't leave pets or children in car")
|
||||||
|
// 🌿 High pollen any pollen type > 50 grains/m³
|
||||||
|
//
|
||||||
|
// DESIGN NOTES:
|
||||||
|
// • Cards should be concise — one line of bold text + a short explanation
|
||||||
|
// • Colour-coded to match the existing UTCI stress band palette
|
||||||
|
// • Collapsible — show top 2-3 alerts by default, expand for full list
|
||||||
|
// • For today only (days[0]); optionally extend to day tabs in a later pass
|
||||||
|
// • The "X°C above seasonal norm" historical context line (see app.js comment)
|
||||||
|
// could live here too, as a subtle subheading under the dial temperature
|
||||||
|
//
|
||||||
|
// Suggested return shape — add to the return value below:
|
||||||
|
// daySummaries: days.map(day => buildDaySummary(day.rows))
|
||||||
|
//
|
||||||
|
// where buildDaySummary() is a new helper in this file (or a separate
|
||||||
|
// summary.js module if it grows large).
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
return { hourlyRows, days, utcOffsetMs };
|
return { hourlyRows, days, utcOffsetMs };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,33 @@ export const FILTER_PROFILES = {
|
|||||||
icon: '⚙️',
|
icon: '⚙️',
|
||||||
proOnly: true,
|
proOnly: true,
|
||||||
cols: { hour: true, air: true, rh: false, dew: false, wind: true, 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: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: false, aqi: false, pollen: false },
|
cols: { hour: true, air: true, rh: false, dew: false, wind: true, 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: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: false, aqi: false, pollen: false },
|
||||||
|
// ── FUTURE FEATURE v2: Saved Custom Profiles (Pro) ───────────────────────
|
||||||
|
// Expand 'Custom' into a fully user-owned profile system. Pro users can:
|
||||||
|
//
|
||||||
|
// • CREATE named profiles — give them a name and icon, choose any columns
|
||||||
|
// • SAVE multiple profiles — stored in localStorage (or user account if we
|
||||||
|
// add auth), each appearing as an extra pill in the profile button bar
|
||||||
|
// • REORDER columns — drag-and-drop within the custom profile editor.
|
||||||
|
// Column order is just an array of keys; shuffle the array, re-render.
|
||||||
|
// The existing col visibility flags stay, reordering is a separate
|
||||||
|
// `colOrder: ['hour', 'air', 'wind', ...]` array on the profile object.
|
||||||
|
// • DELETE / RENAME profiles in a small management modal
|
||||||
|
//
|
||||||
|
// The 'Custom' button becomes "+ New Profile" when in profile-creation mode.
|
||||||
|
// Existing preset profiles (Basic, Home, Vehicle…) remain read-only.
|
||||||
|
//
|
||||||
|
// Data shape per saved profile:
|
||||||
|
// {
|
||||||
|
// id: 'profile_abc123', // generated uuid
|
||||||
|
// label: 'My Beach Days',
|
||||||
|
// icon: '🏖️', // user picks from a small emoji set
|
||||||
|
// cols: { ...standard col flags },
|
||||||
|
// colOrder: ['hour', 'air', 'burn', 'wind', ...], // user's preferred order
|
||||||
|
// createdAt: ISO string,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Storage: JSON.stringify array in localStorage under 'sunscope_custom_profiles'.
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -292,6 +292,41 @@ export function calcVehicleInteriorTemp(Ta, globalRad, solElev, vehicleType = 'c
|
|||||||
return Math.max(Ta, Math.min(Ti, 90));
|
return Math.max(Ta, Math.min(Ti, 90));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── FUTURE FEATURE v2: Pets Profile — Fur Temperature & Heat Stress ──────────
|
||||||
|
// Dogs and cats experience heat very differently from humans:
|
||||||
|
//
|
||||||
|
// FUR SURFACE TEMPERATURE
|
||||||
|
// Dark/thick fur absorbs solar radiation and can run significantly hotter
|
||||||
|
// than air temperature — similar in principle to calcConcreteTemp() but
|
||||||
|
// with fur-specific albedo values:
|
||||||
|
// Black fur: albedo ~0.05 (almost all radiation absorbed)
|
||||||
|
// Brown fur: albedo ~0.15
|
||||||
|
// Golden fur: albedo ~0.25
|
||||||
|
// White fur: albedo ~0.40
|
||||||
|
// Add a calcFurSurfaceTemp(Ta, globalRad, windSpeed, furAlbedo) function
|
||||||
|
// mirroring calcConcreteTemp but with appropriate convective coefficients
|
||||||
|
// for fur (lower hc than concrete — fur insulates).
|
||||||
|
//
|
||||||
|
// PAW BURN RISK
|
||||||
|
// Paw pads are highly sensitive to surface temperature. Use calcConcreteTemp()
|
||||||
|
// output directly — the existing concrete surface temp is already the right
|
||||||
|
// number. Threshold guidance:
|
||||||
|
// < 40 °C — safe
|
||||||
|
// 40–52 °C — discomfort / possible burn (hold-your-hand-for-7-seconds test)
|
||||||
|
// > 52 °C — burns within 60 seconds
|
||||||
|
// Display as a simple traffic-light column in the Pets profile.
|
||||||
|
//
|
||||||
|
// BREED-SPECIFIC HEAT STRESS
|
||||||
|
// Brachycephalic breeds (Bulldogs, Pugs, French Bulldogs, Persians) have
|
||||||
|
// severely impaired thermoregulation — their safe UTCI ceiling is much lower.
|
||||||
|
// A breed selector (Normal / Brachycephalic / Senior) applies a risk multiplier
|
||||||
|
// to the UTCI thresholds, similar to the planned activity modifier.
|
||||||
|
//
|
||||||
|
// SUGGESTED COLUMNS FOR PETS PROFILE
|
||||||
|
// Hour | Air Temp | UTCI+P | Fur Surface Temp | Paw Burn Risk | Shade Advised
|
||||||
|
//
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// ── FUTURE FEATURE: Vehicle-at-speed thermal model ────────────────────────────
|
// ── FUTURE FEATURE: Vehicle-at-speed thermal model ────────────────────────────
|
||||||
// Idea: extend calcVehicleInteriorTemp (or add a companion function) to model
|
// Idea: extend calcVehicleInteriorTemp (or add a companion function) to model
|
||||||
// cabin temperature for a vehicle travelling at speed, not just parked.
|
// cabin temperature for a vehicle travelling at speed, not just parked.
|
||||||
@@ -314,6 +349,23 @@ export function calcVehicleInteriorTemp(Ta, globalRad, solElev, vehicleType = 'c
|
|||||||
//
|
//
|
||||||
// This would be a useful companion output column (e.g. "Vehicle (moving)")
|
// This would be a useful companion output column (e.g. "Vehicle (moving)")
|
||||||
// for road-trip planning, dog-in-car safety at a rest stop vs. motorway, etc.
|
// for road-trip planning, dog-in-car safety at a rest stop vs. motorway, etc.
|
||||||
|
//
|
||||||
|
// ── CYCLIST AT SPEED (companion to the above) ────────────────────────────────
|
||||||
|
// A cyclist generates their own headwind, so the felt temperature (UTCI) is
|
||||||
|
// very different from a stationary person — even without ambient wind.
|
||||||
|
// Could share the same speed-class approach as the vehicle model:
|
||||||
|
// Slow (10 mph) / Moderate (15 mph) / Fast (25 mph)
|
||||||
|
//
|
||||||
|
// Key differences from the vehicle model:
|
||||||
|
// • The cyclist IS the exposed person — use the UTCI polynomial directly
|
||||||
|
// with va = max(ambientWind, cyclingSpeed * conversionFactor)
|
||||||
|
// • No cabin heating effect — the cyclist gets wind chill, not solar entrapment
|
||||||
|
// • High metabolic heat generation raises core temp (links to the activity
|
||||||
|
// modifier planned in compute.js — cycling at speed is a combined effect)
|
||||||
|
// • UVA/UVB exposure is unchanged — still fully exposed to the sun
|
||||||
|
//
|
||||||
|
// Could be a sub-option within the existing Cycling activity variant rather
|
||||||
|
// than a separate column — e.g. a speed picker in the variant controls.
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════
|
||||||
|
|||||||
Reference in New Issue
Block a user