/** * 오늘의 날씨. * * ★ 온도 두 조각으로는 아무 값이 없다 (2026-09-04, 사장님: "컨텐츠가 있어야 하지 않나") * `27.9°C 구름많음` 은 손님 폰 잠금화면에 이미 있다. 이 섹션이 답해야 하는 건 * 날씨가 아니라 **"그래서 오늘 이 집에서 뭘 하지"** 다. 그래서 셋을 얹는다 — * ① 날씨별 한 줄(사장님이 미리 적어 둔 것) ② 관측 시각 ③ 그 날씨의 그림. * ★ 문장을 하나만 두지 않는다. 발행본은 정적인데 날씨는 브라우저에서 갱신되므로, * 굽는 순간의 문장을 박으면 비 오는 날 "마당에 앉기 좋습니다"가 뜬다. * ★ 관측 시각을 반드시 낸다. 시각 없는 숫자는 못 믿는다 — 캐시가 어긋나면 어제 값이다. */ import {Cloud, CloudRain, CloudSnow, Sun} from 'lucide-react'; import type {WeatherSky} from '@o2o/shared'; import {useSite} from '@site/lib/site-context'; import {useLiveWeather} from '@site/lib/use-live-weather'; import {useWeatherNotes} from '@site/lib/use-weather-note'; import {weatherBand, weatherMood} from '@site/lib/derive'; import {Card, Section} from '@site/lib/ui'; const MOOD_ICON = {맑음: Sun, 흐림: Cloud, 비: CloudRain, 눈: CloudSnow} as const; /** "2026-09-03T12:30" → "9월 3일 12:30". 날짜가 오늘이면 시각만 남긴다. */ function observedText(iso: string | undefined): string | undefined { const m = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})/.exec(iso ?? ''); if (!m) return undefined; const [, , month, day, hour, minute] = m; return `${Number(month)}월 ${Number(day)}일 ${hour}:${minute} 관측`; } export function WeatherSection() { const payload = useSite(); const weather = useLiveWeather({ initial: payload.local.weather, regionCode: payload.place.regionCode, latitude: payload.place.latitude, longitude: payload.place.longitude, }); const mood = weatherMood(weather?.condition ?? ''); const band = weatherBand(weather?.temperature ?? 0); const notes = payload.local.weather; // 하늘 문구는 아홉 갈래로 고른다(WeatherSky). 옛 payload 처럼 그 칸이 없으면 그림 넷(mood)으로 떨어진다. const condition = (weather?.condition ?? '') as WeatherSky; const skyKey: WeatherSky = notes?.noteSets?.[condition] ? condition : mood; /* * ★ 기온 줄 (2026-09-09, 사장님: "기온에 따라서 문구 다르게, 펜션지역의 관광에 맞춰서") * 하늘 줄은 집 안 이야기(마당·CAFÉ)라 "그래서 오늘 어디를 가지"에는 답하지 않았다. * 34도의 맑음과 3도의 맑음이 같은 문장을 받는 것도 이 때문이었다. * ★ 없으면 줄 자체를 그리지 않는다 — 기온대는 항상 계산되지만 문장은 사장님이 적은 * 것뿐이라, 빈 칸을 우리가 채우면 지어낸 관광 안내가 된다. * ★ 둘을 한 타이머로 같이 뽑는다 — 따로 뽑으면 두 줄이 각자 다른 순간에 바뀐다. */ const [line, bandLine] = useWeatherNotes( notes?.noteSets?.[skyKey], notes?.notes?.[mood] ?? weather?.note, notes?.tempNoteSets?.[band], notes?.tempNotes?.[band], ); if (!weather) return null; const observed = observedText(weather.observedAt); const SkyIcon = MOOD_ICON[mood]; return (
{/* 아이콘은 글 뒤에 깔린다. aria-hidden — 낭독기가 읽을 것이 없다. */}

{Math.round(weather.temperature)} °C {weather.condition}

{observed && ( {observed} {weather.stale && ' · 최근 관측값'} )}
{/* 사장님이 자기 집을 두고 쓴 한 줄. 날씨 예보가 아니다. */} {line && (

{line}

)} {/* 기온 줄은 하늘 줄 아래에 둔다 — 손님이 먼저 보는 건 "비 오나"이고 "그럼 어디 가지"는 그다음이다. 활자를 한 급 낮춰 둘의 순서를 눈에도 보이게 한다. */} {bandLine && (

{band} {bandLine}

)}
); }