diff --git a/.reasonix/desktop-topic-created-at.json b/.reasonix/desktop-topic-created-at.json new file mode 100644 index 0000000..dead4bb --- /dev/null +++ b/.reasonix/desktop-topic-created-at.json @@ -0,0 +1,3 @@ +{ + "topic_20260629-150638_cda0606c7e7a865d": 1782745598290 +} \ No newline at end of file diff --git a/.reasonix/desktop-topic-title-sources.json b/.reasonix/desktop-topic-title-sources.json new file mode 100644 index 0000000..5ac49c0 --- /dev/null +++ b/.reasonix/desktop-topic-title-sources.json @@ -0,0 +1,3 @@ +{ + "topic_20260629-150638_cda0606c7e7a865d": "auto" +} \ No newline at end of file diff --git a/.reasonix/desktop-topic-titles.json b/.reasonix/desktop-topic-titles.json new file mode 100644 index 0000000..716f05b --- /dev/null +++ b/.reasonix/desktop-topic-titles.json @@ -0,0 +1,3 @@ +{ + "topic_20260629-150638_cda0606c7e7a865d": "新的会话" +} \ No newline at end of file diff --git a/assets/css/ui.css b/assets/css/ui.css index b3a184b..ca52c82 100644 --- a/assets/css/ui.css +++ b/assets/css/ui.css @@ -1122,6 +1122,57 @@ line-height: 1; } +/* Recent locations — quick-click stacked list beneath the last-update line. */ +.utci-recent-locs { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 3px; + margin-top: 0.5rem; +} +.utci-recent-label { + font-family: Manrope, sans-serif; + font-size: 9px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.08em; + color: #b09870; + margin-bottom: 1px; +} +.utci-recent-chip { + display: flex; + align-items: center; + gap: 5px; + width: 100%; + max-width: 220px; + padding: 4px 10px 4px 8px; + font-family: Manrope, sans-serif; + font-size: 0.74rem; + font-weight: 600; + text-align: left; + color: #7a5e2a; + background: #fdf8ee; + border: 1.5px solid #d9c39b; + border-radius: 8px; + cursor: pointer; + transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease; +} +.utci-recent-chip:hover { + background: #f6ecd6; + border-color: #c8922a; + color: #6a4f1e; +} +/* Keep long names on one line with an ellipsis rather than wrapping. */ +.utci-recent-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.utci-recent-pin { + flex-shrink: 0; + color: #c8922a; +} + /* ── 10. LEGEND (thermal-stress band colours at the bottom) ────────── */ .utci-legend { @@ -1306,6 +1357,13 @@ text-align: center; } + .utci-recent-locs { + align-items: center; + } + .utci-recent-chip { + max-width: 260px; + } + .utci-search-wrap { width: 100%; } diff --git a/assets/js/app.js b/assets/js/app.js index c5cd41f..510bf9e 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -175,7 +175,7 @@ export function UTCIForecast() { // All useState, useEffect, useCallback and useRef logic lives in // useAppState. See hooks/useAppState.js for the full reading order. const { - location, setLocationAndSave, + location, setLocationAndSave, recentLocations, forecast, airQuality, loading, error, now, fetchedAt, searchQuery, setSearchQuery, searchResults, setSearchResults, searching, selectedDay, setSelectedDay, @@ -675,6 +675,35 @@ export function UTCIForecast() {
${isStale ? html`` : ''}Last update: ${fetchedAt.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
`} + ${(() => { + const curKey = `${location.lat.toFixed(3)},${location.lon.toFixed(3)}`; + const recents = (recentLocations || []) + .filter((l) => `${l.lat.toFixed(3)},${l.lon.toFixed(3)}` !== curKey) + .slice(0, 3); + if (!recents.length) return null; + return html` +
+ Recent + ${recents.map((l) => html` + `)} +
`; + })()} ${(searchOpen || searchClosing) && html`
{ + try { + const saved = localStorage.getItem('sunscope_recent_locations'); + if (saved) return JSON.parse(saved); + } catch (e) { /* ignore */ } + return []; + }); + + const locKey = (l) => `${l.lat.toFixed(3)},${l.lon.toFixed(3)}`; + const setLocationAndSave = (loc) => { try { localStorage.setItem('sunscope_last_location', JSON.stringify(loc)); } catch (e) { /* ignore */ } setLocation(loc); + setRecentLocations((prev) => { + // Keep the current spot at the front and the 3 previous distinct + // ones behind it (4 total, so 3 chips remain after excluding current). + const next = [loc, ...prev.filter((l) => locKey(l) !== locKey(loc))].slice(0, 4); + try { localStorage.setItem('sunscope_recent_locations', JSON.stringify(next)); } catch (e) { /* ignore */ } + return next; + }); }; // Pro tier flag is read early so useForecast can pick its refresh cadence @@ -589,7 +609,7 @@ export function useAppState() { // ── RETURN ALL STATE + HANDLERS ─────────────────────────────────────── return { // location - location, setLocationAndSave, + location, setLocationAndSave, recentLocations, // forecast forecast, airQuality, loading, error, now, fetchedAt, // search diff --git a/data/tracking.json b/data/tracking.json index 15df994..c908494 100644 --- a/data/tracking.json +++ b/data/tracking.json @@ -115,5 +115,27 @@ "farming": 3, "basic": 1 } + }, + "2026-06-29": { + "visits": 6, + "profiles": { + "basic": 6, + "home": 6, + "vehicle": 4, + "alltemps": 3, + "showall": 4, + "custom": 1 + } + }, + "2026-07-04": { + "visits": 4, + "profiles": { + "home": 3, + "vehicle": 3, + "alltemps": 4, + "showall": 4, + "custom": 2, + "basic": 2 + } } } \ No newline at end of file