Bigger tip chip(s), soft umbrella tier, and a jacket tip
- Umbrella tip now bigger (larger padding/font/icon) and there can be more than one chip shown at once (e.g. cold + rainy). - Added a softer 'You might want an umbrella' tier: shown when the existing strong threshold isn't met but any remaining hour today still has a real (>10%) rain chance -- previously that case showed nothing at all. - Added a jacket tip: shown when today's forecast high is below 12°C. Both new thresholds are named constants at the top of summary.js, easy to retune without touching the logic. summary.js's umbrella boolean is replaced by a tips[] array so future nudges (e.g. a UV/sun-cream tip, if we add UV index data) slot in the same way without another one-off boolean + span. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
335e6e31a8
commit
b4f1e81eed
4 changed files with 69 additions and 16 deletions
|
|
@ -10,6 +10,11 @@
|
|||
const RAIN_ICONS = new Set(['drizzle', 'rain-light', 'rain-heavy', 'sleet', 'hail'])
|
||||
const SNOW_ICONS = new Set(['snow-light', 'snow-heavy'])
|
||||
|
||||
// Tunable thresholds for the "now" panel's tip chips — all easy to adjust here
|
||||
// without touching the logic that uses them.
|
||||
const UMBRELLA_SOFT_PRECIP_PCT = 10 // any remaining hour above this -> "might want"
|
||||
const JACKET_HIGH_TEMP_C = 12 // today's forecast high below this -> jacket tip
|
||||
|
||||
// summarizeToday(points, nowHour) — `points` is today's hourly forecast_points rows
|
||||
// (already icon-normalized), `nowHour` is 0-23 in Europe/London. Returns null if
|
||||
// there's nothing to summarize (no data yet).
|
||||
|
|
@ -73,16 +78,37 @@ export function summarizeToday(points, nowHour) {
|
|||
? 'Thunderstorms possible'
|
||||
: null
|
||||
|
||||
// Umbrella nudge: real rain chance either right now or coming up later today.
|
||||
const umbrella = laterWetFraction > 0.25 || rainFraction > 0.25
|
||||
// Tip chips — small, actionable nudges under the headline. Each is
|
||||
// independent (a cold rainy day can show both), ordered by how likely
|
||||
// reception actually wants a guest to see it.
|
||||
const tips = []
|
||||
|
||||
// Umbrella: the existing "real rain chance" threshold gets the strong
|
||||
// wording; falling short of that but with a real (if lesser) chance
|
||||
// still on the cards for later today gets a softer nudge instead of
|
||||
// nothing at all.
|
||||
const umbrellaStrong = laterWetFraction > 0.25 || rainFraction > 0.25
|
||||
const umbrellaSoft = !umbrellaStrong &&
|
||||
laterPoints.some(p => p.precip_prob != null && p.precip_prob > UMBRELLA_SOFT_PRECIP_PCT)
|
||||
if (umbrellaStrong) {
|
||||
tips.push({ key: 'umbrella', text: "Don't forget your umbrella" })
|
||||
} else if (umbrellaSoft) {
|
||||
tips.push({ key: 'umbrella-soft', text: 'You might want an umbrella' })
|
||||
}
|
||||
|
||||
// Jacket: keyed off today's forecast high, not "right now" -- same framing
|
||||
// as the big temp above it.
|
||||
if (highPoint.temp_c < JACKET_HIGH_TEMP_C) {
|
||||
tips.push({ key: 'jacket', text: "Don't forget your jacket" })
|
||||
}
|
||||
|
||||
return {
|
||||
headline,
|
||||
note,
|
||||
umbrella,
|
||||
highTemp: highPoint.temp_c,
|
||||
highFeelsLike: highPoint.feels_like_c,
|
||||
rainChance: rainPeakPoint ? { prob: rainPeakPoint.precip_prob, ts: rainPeakPoint.ts } : null,
|
||||
tips,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -94,18 +94,28 @@ html, body, #root { height: 100%; margin: 0; font-size: 14px; }
|
|||
.wx-now-meta { display: flex; flex-direction: column; gap: 4px; color: var(--text-muted); font-size: clamp(0.8rem, 1.05vw, 1.05rem); }
|
||||
.wx-summary-headline { color: var(--text); font-weight: 600; font-size: clamp(0.95rem, 1.3vw, 1.3rem); }
|
||||
.wx-summary-note { color: #fbbf24; }
|
||||
.wx-umbrella-tip {
|
||||
/* Tip chips (umbrella/jacket/...) -- can be more than one at once (e.g. a cold
|
||||
rainy day), so this wraps multiple .wx-tip pills rather than a single fixed
|
||||
span. Sized up from the old single-tip version to read clearly at a glance
|
||||
on the kiosk tablet. */
|
||||
.wx-tips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: clamp(8px, 1vw, 12px);
|
||||
margin-left: auto;
|
||||
}
|
||||
.wx-tip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-left: auto;
|
||||
padding: clamp(6px, 0.9vh, 10px) clamp(12px, 1.4vw, 18px);
|
||||
gap: 10px;
|
||||
padding: clamp(8px, 1.2vh, 13px) clamp(14px, 1.7vw, 22px);
|
||||
border-radius: 999px;
|
||||
background: rgba(125, 211, 252, 0.14);
|
||||
border: 1px solid rgba(125, 211, 252, 0.35);
|
||||
color: #7dd3fc;
|
||||
font-weight: 600;
|
||||
font-size: clamp(0.75rem, 0.95vw, 0.95rem);
|
||||
font-size: clamp(0.9rem, 1.15vw, 1.15rem);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,20 @@
|
|||
import { useEffect, useRef, useState } from 'react'
|
||||
import { CloudOff, Umbrella } from 'lucide-react'
|
||||
import { CloudOff, Umbrella, Shirt } from 'lucide-react'
|
||||
import { fetchForecast, fetchDisplayConfig } from '../api'
|
||||
import { WeatherIcon } from '../lib/icons'
|
||||
import { PointMeta } from '../components/PointMeta'
|
||||
import { AstroRow } from '../components/AstroRow'
|
||||
import { DayModal } from '../components/DayModal'
|
||||
import { TZ, londonNow, hourOf, dateKeyOf, formatHour, formatDayName, roundTemp } from '../lib/format'
|
||||
import type { ForecastPayload, ForecastPoint, Astro } from '../types'
|
||||
import type { ForecastPayload, ForecastPoint, Astro, TipKey } from '../types'
|
||||
|
||||
// One icon per tip key -- 'umbrella-soft' reuses the umbrella icon, just with
|
||||
// softer wording (see summary.js), so it doesn't need its own entry here.
|
||||
const TIP_ICONS: Record<TipKey, typeof Umbrella> = {
|
||||
umbrella: Umbrella,
|
||||
'umbrella-soft': Umbrella,
|
||||
jacket: Shirt,
|
||||
}
|
||||
|
||||
const POLL_MS = 5 * 60 * 1000 // client polls more often than the server's hourly
|
||||
// refresh just so a stale tab catches up quickly —
|
||||
|
|
@ -142,11 +150,18 @@ export default function Display() {
|
|||
</span>
|
||||
{data.summary?.note && <span className="wx-summary-note">{data.summary.note}</span>}
|
||||
</div>
|
||||
{data.summary?.umbrella && (
|
||||
<span className="wx-umbrella-tip">
|
||||
<Umbrella size={18} strokeWidth={1.75} />
|
||||
Don't forget your umbrella
|
||||
</span>
|
||||
{!!data.summary?.tips.length && (
|
||||
<div className="wx-tips">
|
||||
{data.summary.tips.map(tip => {
|
||||
const Icon = TIP_ICONS[tip.key]
|
||||
return (
|
||||
<span className="wx-tip" key={tip.key}>
|
||||
<Icon size={22} strokeWidth={1.75} />
|
||||
{tip.text}
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -23,13 +23,15 @@ export interface Astro {
|
|||
sunset: string | null
|
||||
}
|
||||
|
||||
export type TipKey = 'umbrella' | 'umbrella-soft' | 'jacket'
|
||||
|
||||
export interface Summary {
|
||||
headline: string // "Scattered showers today", "Rain likely later", ...
|
||||
note: string | null // e.g. "Thunderstorms possible", alongside the headline
|
||||
umbrella: boolean // real rain chance now or later today
|
||||
highTemp: number // today's forecast high (°C)
|
||||
highFeelsLike: number | null // feels-like at the same hour as highTemp, not "now"
|
||||
rainChance: { prob: number; ts: string } | null // today's peak precip% + when it falls
|
||||
tips: { key: TipKey; text: string }[] // umbrella/jacket/etc nudge chips, 0+ at once
|
||||
}
|
||||
|
||||
export type Provider = 'met_office' | 'open_meteo'
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue