Future Idea Comments
This commit is contained in:
@@ -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 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.)
|
||||
|
||||
// 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>
|
||||
|
||||
Reference in New Issue
Block a user