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:
jtricerolph 2026-08-31 12:05:11 +00:00
parent 335e6e31a8
commit b4f1e81eed
4 changed files with 69 additions and 16 deletions

View file

@ -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;
}

View file

@ -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>
)}

View file

@ -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'