Future Idea Comments

This commit is contained in:
fraxle
2026-05-18 20:38:19 +01:00
parent 9e810e2f8d
commit 31233c713f
4 changed files with 341 additions and 0 deletions
+182
View File
@@ -143,6 +143,122 @@ export function UTCIForecast() {
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 2531",
// "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.)
// Current active filter profile
@@ -510,6 +626,24 @@ export function UTCIForecast() {
glob=${currentRow?.glob ?? 0}
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 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
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}>
<span class="utci-scroll-chevron left" aria-hidden="true"></span>
<span class="utci-scroll-chevron right" aria-hidden="true"></span>