Tap a Coming up card -> hourly-detail modal, 20s idle auto-close
Backend now stores hourly points for every day the provider returns (not just today) -- for Open-Meteo this is free, the hourly= block already covers the full forecast_days=8 window in the one call, today's strip just discarded everything past today. Met Office's hourly product window isn't verified this far out (only daily has been exercised live so far); an empty result for a given date just means "no hourly detail", handled as a graceful fallback, not an error. New GET /api/public/forecast/hourly?date=YYYY-MM-DD backs the modal. The main /api/public/forecast payload's `hourly` field is now its own scoped query (today only) rather than filtering the much bigger stored set in JS. Frontend: tapping any "Coming up" card (tomorrow/day-after or days 4-7) opens DayModal with that day's hourly points, falling back to the already-known 3-hourly points (or the single daily summary) if the provider doesn't have hourly detail that far out. Auto-closes after 20s of no interaction (pointerdown/keydown/wheel/scroll all reset the timer), with a depleting progress bar so the countdown isn't a surprise. Extracted PointMeta/AstroRow and the date/format helpers out of Display.tsx into shared modules since DayModal needs them too. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
c6be990f6b
commit
13fb30d9a3
10 changed files with 370 additions and 75 deletions
|
|
@ -239,6 +239,76 @@ html, body, #root { height: 100%; margin: 0; font-size: 14px; }
|
|||
gap: 12px; background: var(--navy-dark); color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Every "Coming up" card opens the hourly-detail modal on tap — see DayModal. */
|
||||
.wx-tappable { cursor: pointer; transition: background .12s, transform .08s; }
|
||||
.wx-tappable:hover { background: var(--surface-2); }
|
||||
.wx-tappable:active { transform: scale(0.97); }
|
||||
|
||||
/* ── Day-detail modal (opened by tapping a "Coming up" card) ────────────── */
|
||||
.wx-modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(15,15,32,0.72);
|
||||
backdrop-filter: blur(2px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24px;
|
||||
z-index: 500;
|
||||
}
|
||||
.wx-modal {
|
||||
background: var(--navy);
|
||||
border: 1px solid var(--surface-2);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow-md);
|
||||
width: min(92vw, 920px);
|
||||
max-height: 85vh;
|
||||
padding: clamp(18px, 2.6vh, 32px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
color: var(--text);
|
||||
overflow: hidden;
|
||||
}
|
||||
.wx-modal-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
|
||||
.wx-modal-title { font-size: clamp(1.3rem, 2.4vw, 2rem); font-weight: 700; color: var(--gold); }
|
||||
.wx-modal-close {
|
||||
background: none; border: none; color: var(--text-muted); cursor: pointer;
|
||||
padding: 4px; display: flex; flex-shrink: 0;
|
||||
}
|
||||
.wx-modal-close:hover { color: var(--text); }
|
||||
.wx-modal-note { color: var(--text-muted); font-size: clamp(0.8rem, 1vw, 0.95rem); margin: 0; }
|
||||
.wx-modal-empty {
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
gap: 10px; color: var(--text-muted); padding: 40px 0;
|
||||
}
|
||||
.wx-modal-hourly {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: clamp(8px, 1.2vw, 16px);
|
||||
overflow-x: auto;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
.wx-modal-point {
|
||||
flex: 1 0 clamp(64px, 7vw, 90px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 6px;
|
||||
padding: clamp(10px, 1.4vh, 16px) 6px;
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface-2);
|
||||
}
|
||||
.wx-modal-point-time { font-size: clamp(0.72rem, 0.85vw, 0.9rem); color: var(--text-muted); }
|
||||
.wx-modal-point-temp { font-size: clamp(1.1rem, 1.3vw, 1.4rem); font-weight: 600; }
|
||||
|
||||
/* Idle-timeout progress bar — depletes over the 20s auto-close window, restarting
|
||||
(via the DayModal's resetTick key, which remounts this element) on interaction. */
|
||||
.wx-modal-timeout-track { height: 3px; background: var(--surface-2); border-radius: 2px; overflow: hidden; flex-shrink: 0; }
|
||||
.wx-modal-timeout-bar { height: 100%; background: var(--gold); animation: wx-timeout-deplete 20s linear forwards; }
|
||||
@keyframes wx-timeout-deplete { from { width: 100%; } to { width: 0%; } }
|
||||
|
||||
/* ── Admin (authed) page ──────────────────────────────────────────────── */
|
||||
.wx-admin-shell { min-height: 100vh; background: var(--body-bg); display: flex; justify-content: center; padding: 32px 16px; }
|
||||
.wx-admin-panel { width: 100%; max-width: 560px; }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue