diff --git a/app/api/weather/route.ts b/app/api/weather/route.ts
index 8b8477b..3ba0a41 100644
--- a/app/api/weather/route.ts
+++ b/app/api/weather/route.ts
@@ -49,7 +49,7 @@ export async function GET(req: Request) {
latitude: lat,
longitude: lon,
current: "temperature_2m,relative_humidity_2m,apparent_temperature,weather_code,wind_speed_10m",
- daily: "weather_code,temperature_2m_max,temperature_2m_min",
+ daily: "weather_code,temperature_2m_max,temperature_2m_min,apparent_temperature_max,apparent_temperature_min,precipitation_probability_max,wind_speed_10m_max,relative_humidity_2m_max",
timezone: "Europe/Moscow",
forecast_days: "7",
});
@@ -73,6 +73,11 @@ export async function GET(req: Request) {
minTemp: String(Math.round(daily.temperature_2m_min[i])),
desc: wmoToDesc(daily.weather_code[i]),
weatherCode: wmoToWttrCode(daily.weather_code[i]),
+ feelsLikeMax: String(Math.round(daily.apparent_temperature_max?.[i] ?? 0)),
+ feelsLikeMin: String(Math.round(daily.apparent_temperature_min?.[i] ?? 0)),
+ precipProb: String(daily.precipitation_probability_max?.[i] ?? 0),
+ windSpeed: String(Math.round((daily.wind_speed_10m_max?.[i] ?? 0) / 3.6)),
+ humidity: String(daily.relative_humidity_2m_max?.[i] ?? 0),
}));
return NextResponse.json({
diff --git a/app/page.tsx b/app/page.tsx
index 42e9abc..ab1fd13 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -19,7 +19,7 @@ interface WeatherData {
humidity: string
windSpeed: string
feelsLike: string
- forecast?: { date: string; maxTemp: string; minTemp: string; desc: string }[]
+ forecast?: { date: string; maxTemp: string; minTemp: string; desc: string; feelsLikeMax?: string; feelsLikeMin?: string; precipProb?: string; windSpeed?: string; humidity?: string }[]
}
interface SensorData {
@@ -285,12 +285,91 @@ function LockScreen({ onUnlock }: { onUnlock: () => void }) {
}
// ————— Home Tab —————
+// ————— Weather Day Detail Modal —————
+function WeatherDayModal({ day, current, onClose }: {
+ day: { date: string; maxTemp: string; minTemp: string; desc: string; feelsLikeMax?: string; feelsLikeMin?: string; precipProb?: string; windSpeed?: string; humidity?: string }
+ current: WeatherData | null
+ onClose: () => void
+}) {
+ const d = new Date(day.date)
+ const isToday = d.toDateString() === new Date().toDateString()
+ const dayLabel = isToday ? 'Сегодня' : d.toLocaleDateString('ru-RU', { weekday: 'long', day: 'numeric', month: 'long' })
+
+ return (
+
+
e.stopPropagation()}>
+
+ {/* Hero */}
+
+
+
+
+
+
{dayLabel}
+
+
+ {day.maxTemp}°
+ {day.minTemp}°
+
+
{day.desc}
+
+
+
+ {/* Details grid */}
+
+
+ {[
+ { icon:
, bg: 'rgba(251,146,60,0.1)', label: 'Ощущается', value: `${day.feelsLikeMax || '—'}° / ${day.feelsLikeMin || '—'}°` },
+ { icon:
, bg: 'rgba(59,130,246,0.1)', label: 'Влажность', value: `${day.humidity || (isToday && current ? current.humidity : '—')}%` },
+ { icon:
, bg: 'rgba(34,211,238,0.1)', label: 'Ветер', value: `${day.windSpeed || (isToday && current ? current.windSpeed : '—')} м/с` },
+ { icon:
🌧️, bg: 'rgba(99,102,241,0.1)', label: 'Вероятность осадков', value: `${day.precipProb || '0'}%` },
+ ].map(item => (
+
+
{item.icon}
+
{item.value}
+
{item.label}
+
+ ))}
+
+
+
+
+
+
+ )
+}
+
+
function HomeTab({ weather, sensors }: { weather: WeatherData | null; sensors: SensorData | null }) {
const [todayEvents, setTodayEvents] = useState([])
const [tomorrowEvents, setTomorrowEvents] = useState([])
const [calLoading, setCalLoading] = useState(true)
const [greeting, setGreeting] = useState(getGreeting())
const [pinnedNotes, setPinnedNotes] = useState([])
+ const [selectedDay, setSelectedDay] = useState(null)
useEffect(() => {
fetch('/api/calendar?range=today')
@@ -376,7 +455,7 @@ function HomeTab({ weather, sensors }: { weather: WeatherData | null; sensors: S
{/* Current */}
-
+
weather?.forecast?.[0] && setSelectedDay(weather.forecast[0])} style={{ display: 'flex', alignItems: 'center', gap: 14, flexShrink: 0, position: 'relative', zIndex: 1, cursor: 'pointer' }}>
{weather.temp}°
@@ -394,10 +473,11 @@ function HomeTab({ weather, sensors }: { weather: WeatherData | null; sensors: S
const d = new Date(day.date)
const isToday = idx === 0
return (
-
setSelectedDay(day)} style={{
flex: 1, minWidth: 0, textAlign: 'center', padding: '4px 2px',
- borderRadius: 10,
+ borderRadius: 10, cursor: 'pointer',
background: isToday ? 'rgba(99,102,241,0.1)' : 'transparent',
+ transition: 'background 0.2s ease',
}}>
{isToday ? 'Сей' : d.toLocaleDateString('ru-RU', { weekday: 'short' }).slice(0, 2)}
@@ -523,6 +603,11 @@ function HomeTab({ weather, sensors }: { weather: WeatherData | null; sensors: S
)}
+
+ {/* Weather day detail modal */}
+ {selectedDay && (
+
setSelectedDay(null)} />
+ )}
)
}
@@ -848,7 +933,7 @@ function HomePageInner() {
-
+
{tab === 'home' && (
diff --git a/components/TopBar.tsx b/components/TopBar.tsx
index 589e399..3a8ef8f 100644
--- a/components/TopBar.tsx
+++ b/components/TopBar.tsx
@@ -1,17 +1,7 @@
'use client'
import { useState, useEffect } from 'react'
-import { Droplets, Wind, Thermometer, X } from 'lucide-react'
-import WeatherAnimation from '@/components/WeatherAnimation'
-
-interface WeatherData {
- temp: string
- desc: string
- humidity: string
- windSpeed: string
- feelsLike: string
- forecast?: { date: string; maxTemp: string; minTemp: string; desc: string }[]
-}
+import { Droplets, Wind, Thermometer } from 'lucide-react'
interface SensorData {
temperature: number
@@ -20,23 +10,10 @@ interface SensorData {
}
interface TopBarProps {
- weather: WeatherData | null
sensors: SensorData | null
haConnected?: boolean
}
-function getWeatherIcon(desc: string): string {
- const d = desc?.toLowerCase() || ''
- if (d.includes('ясно') || d.includes('солнеч')) return '☀️'
- if (d.includes('облач') || d.includes('перем')) return '⛅'
- if (d.includes('пасмурн')) return '☁️'
- if (d.includes('морос')) return '🌦️'
- if (d.includes('дождь') || d.includes('ливен')) return '🌧️'
- if (d.includes('снег')) return '🌨️'
- if (d.includes('гроз')) return '⛈️'
- if (d.includes('туман')) return '🌫️'
- return '🌤️'
-}
function formatTime(date: Date): string {
return date.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' })
@@ -49,27 +26,15 @@ function formatDate(date: Date): string {
return `${weekday}, ${day} ${month}`
}
-function getWindDesc(ms: number): string {
- if (ms <= 1) return 'Штиль'
- if (ms <= 3) return 'Тихий'
- if (ms <= 5) return 'Лёгкий'
- if (ms <= 8) return 'Умеренный'
- if (ms <= 11) return 'Свежий'
- if (ms <= 14) return 'Сильный'
- return 'Шторм'
-}
-export default function TopBar({ weather, sensors, haConnected }: TopBarProps) {
+export default function TopBar({ sensors, haConnected }: TopBarProps) {
const [time, setTime] = useState(() => new Date())
- const [showModal, setShowModal] = useState(false)
useEffect(() => {
const t = setInterval(() => setTime(new Date()), 1000)
return () => clearInterval(t)
}, [])
- const windMs = weather ? parseInt(weather.windSpeed) || 0 : 0
-
return (
<>
)}
- {weather && (
-
- )}
- {/* Weather Modal */}
- {showModal && weather && (
-
setShowModal(false)}
- style={{
- position: 'fixed', inset: 0,
- background: 'rgba(0,0,0,0.65)', backdropFilter: 'blur(12px)',
- zIndex: 100, display: 'flex', alignItems: 'center', justifyContent: 'center',
- overflowY: 'auto', padding: 20,
- }}
- >
-
e.stopPropagation()}
- style={{
- background: 'rgba(16,16,30,0.97)', backdropFilter: 'blur(40px)',
- border: '1px solid rgba(255,255,255,0.07)', borderRadius: 28,
- width: 480, maxWidth: '95vw', maxHeight: '90vh', overflow: 'auto',
- boxShadow: '0 30px 90px rgba(0,0,0,0.6)',
- }}
- >
- {/* Hero section */}
-
- {/* Background emoji */}
-
-
-
-
-
-
-
-
-
- {weather.temp}°
-
-
- {weather.desc}
-
-
-
-
-
-
-
- {/* Details */}
-
-
- {/* Stats grid */}
-
- {[
- {
- icon:
,
- bg: 'rgba(251,146,60,0.1)',
- label: 'Ощущается',
- value: `${weather.feelsLike}°`,
- },
- {
- icon:
,
- bg: 'rgba(59,130,246,0.1)',
- label: 'Влажность',
- value: `${weather.humidity}%`,
- },
- {
- icon:
,
- bg: 'rgba(34,211,238,0.1)',
- label: getWindDesc(windMs),
- value: `${weather.windSpeed} м/с`,
- },
- ].map(item => (
-
-
- {item.icon}
-
-
{item.value}
-
{item.label}
-
- ))}
-
-
- {/* Forecast */}
- {weather.forecast && weather.forecast.length > 0 && (
- <>
-
- Прогноз на неделю
-
-
- {weather.forecast.map(day => {
- const d = new Date(day.date)
- const isToday = d.toDateString() === new Date().toDateString()
- const weekday = d.toLocaleDateString('ru-RU', { weekday: 'long' })
- const dateStr = d.toLocaleDateString('ru-RU', { day: 'numeric', month: 'short' })
- return (
-
- {/* Icon */}
-
-
- {/* Day info */}
-
-
- {isToday ? 'Сегодня' : weekday}
-
-
- {dateStr} · {day.desc}
-
-
-
- {/* Temps */}
-
-
- {day.maxTemp}°
-
-
- {day.minTemp}°
-
-
-
- )
- })}
-
- >
- )}
-
-
-
- )}
>
)
}