revert(home): restore weather hero with anim + details, drop Countdown from home
All checks were successful
Deploy / deploy (push) Successful in 3m24s

Focus/Countdown не лежат по high-density. На Home вернул старый
weather-hero: WeatherAnimation (фон + иконка), feelsLike/humidity/wind,
76px display-цифра (чуть крупнее прежних 64). FocusCard и CountdownCard
файлы оставляем для будущего, но на главной не подключаем. Убрал
сопутствующие state/fetch (tramNext, countdowns, nextEvent).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cosmo
2026-04-23 19:35:01 +00:00
parent e328055851
commit a97dd11f25

View File

@@ -14,8 +14,6 @@ import WeatherAnimation from '@/components/WeatherAnimation'
import VoiceOverlay from '@/components/VoiceOverlay'
import TimerWidget from '@/components/TimerWidget'
import TimerHomeWidget from '@/components/TimerHomeWidget'
import FocusCard from '@/components/FocusCard'
import CountdownCard from '@/components/CountdownCard'
type Tab = 'home' | 'devices' | 'calendar' | 'notes' | 'settings'
@@ -448,8 +446,6 @@ function HomeTab({ weather, sensors }: { weather: WeatherData | null; sensors: S
const [calLoading, setCalLoading] = useState(true)
const [pinnedNotes, setPinnedNotes] = useState<any[]>([])
const [selectedDay, setSelectedDay] = useState<any>(null)
const [countdowns, setCountdowns] = useState<{ label: string; date: string }[]>([])
const [tramNext, setTramNext] = useState<{ route: string; minutes: number; direction: string } | null>(null)
useEffect(() => {
fetch('/api/calendar?range=today')
@@ -489,57 +485,8 @@ function HomeTab({ weather, sensors }: { weather: WeatherData | null; sensors: S
})
.catch(() => {})
// Countdowns
fetch('/api/countdowns')
.then(r => r.json())
.then(d => setCountdowns((d.countdowns || []).map((c: any) => ({ label: c.label, date: c.date }))))
.catch(() => {})
}, [])
// Nearest upcoming tram — refresh every 30s so FocusCard stays current
useEffect(() => {
let cancelled = false
const STOP_IDS = [
{ id: '16226', direction: 'в центр' },
{ id: '16354', direction: 'от центра' },
]
const load = async () => {
try {
const results = await Promise.all(
STOP_IDS.map(s =>
fetch(`/api/transport?stopId=${s.id}`)
.then(r => r.json())
.then(j => (j.arrivals || []).map((a: any) => ({ ...a, direction: s.direction })))
.catch(() => [])
)
)
const all = results.flat().filter(a => typeof a.minutes === 'number' && a.minutes >= 0)
all.sort((a: any, b: any) => a.minutes - b.minutes)
if (!cancelled) {
setTramNext(all[0] ? {
route: all[0].route,
minutes: all[0].minutes,
direction: all[0].direction,
} : null)
}
} catch {}
}
load()
const t = setInterval(load, 30_000)
return () => { cancelled = true; clearInterval(t) }
}, [])
// Next event (today or tomorrow) — сейчас or nearest upcoming within next 24h
const nextEvent = (() => {
const now = Date.now()
const pool = [...todayEvents, ...tomorrowEvents]
.filter(e => !e.allDay)
.map(e => ({ e, t: new Date(e.start).getTime() }))
.filter(({ t }) => t >= now - 5 * 60_000)
.sort((a, b) => a.t - b.t)
return pool[0]?.e || null
})()
return (
@@ -548,19 +495,82 @@ function HomeTab({ weather, sensors }: { weather: WeatherData | null; sensors: S
padding: '18px 22px 24px',
display: 'flex', flexDirection: 'column', gap: 14,
}}>
{/* ───── Bento row: Focus hero + Tram ───── */}
{/* ───── Bento row: Hero weather + Tram ───── */}
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1.1fr)', gap: 14, alignItems: 'stretch' }}>
{/* Focus — контекст-hero */}
<FocusCard
weather={weather ? { temp: weather.temp, desc: weather.desc, feelsLike: weather.feelsLike } : null}
tramNext={tramNext}
nextEvent={nextEvent ? {
id: nextEvent.id, title: nextEvent.title, start: nextEvent.start,
allDay: nextEvent.allDay, ownerName: nextEvent.ownerName, color: nextEvent.color,
} : null}
countdowns={countdowns}
/>
{/* Hero weather card */}
{weather ? (
<div
className="card-hero"
onClick={() => weather?.forecast?.[0] && setSelectedDay(weather.forecast[0])}
style={{
padding: '24px 26px',
display: 'flex', flexDirection: 'column',
position: 'relative', overflow: 'hidden',
cursor: 'pointer',
}}
>
{/* Decorative animation, large, behind */}
<div style={{
position: 'absolute', top: -24, right: -12,
opacity: 0.14, pointerEvents: 'none',
}}>
<WeatherAnimation condition={weather.desc} size={180} />
</div>
<div className="eyebrow" style={{ marginBottom: 10, position: 'relative', zIndex: 1 }}>
Сейчас
</div>
<div style={{
display: 'flex', alignItems: 'center', gap: 14,
position: 'relative', zIndex: 1, marginBottom: 8,
}}>
<div className="num-display" style={{
fontSize: 76, color: 'var(--text-primary)',
}}>
{weather.temp}°
</div>
<WeatherAnimation condition={weather.desc} size={60} />
</div>
<div style={{
fontSize: 17, color: 'var(--text-primary)', fontWeight: 600,
position: 'relative', zIndex: 1, marginBottom: 16,
textTransform: 'capitalize',
}}>
{weather.desc}
</div>
<div style={{
display: 'flex', gap: 22, flexWrap: 'wrap',
marginTop: 'auto', position: 'relative', zIndex: 1,
}}>
{weather.feelsLike && (
<div>
<div className="eyebrow">Ощущается</div>
<div className="num" style={{ fontSize: 16, color: 'var(--text-primary)', fontWeight: 700, marginTop: 3 }}>{weather.feelsLike}°</div>
</div>
)}
{weather.humidity && (
<div>
<div className="eyebrow">Влажность</div>
<div className="num" style={{ fontSize: 16, color: 'var(--text-primary)', fontWeight: 700, marginTop: 3 }}>{weather.humidity}</div>
</div>
)}
{weather.windSpeed && (
<div>
<div className="eyebrow">Ветер</div>
<div className="num" style={{ fontSize: 16, color: 'var(--text-primary)', fontWeight: 700, marginTop: 3 }}>{weather.windSpeed}</div>
</div>
)}
</div>
</div>
) : (
<div className="card-hero" style={{ padding: '24px 26px', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--text-tertiary)', fontSize: 13 }}>
Загрузка погоды...
</div>
)}
{/* Tram */}
<TransportWidget />
@@ -701,16 +711,6 @@ function HomeTab({ weather, sensors }: { weather: WeatherData | null; sensors: S
<TimerHomeWidget />
</div>
{/* ───── Row 4: Countdown ───── */}
<div style={{
display: 'grid',
gridTemplateColumns: 'minmax(260px, 1fr) minmax(0, 2fr)',
gap: 14,
}}>
<CountdownCard />
<div /> {/* место под будущий виджет */}
</div>
{/* Weather day detail modal */}
{selectedDay && (
<WeatherDayModal