Compare commits
4 Commits
4e66fb549a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b9de13360 | |||
| 747868867f | |||
| a4f02d711a | |||
| 1eff0c258b |
@@ -2,7 +2,7 @@
|
||||
|
||||
**Pogoda z danych IMGW. Prosto. Pięknie. Aktualnie.**
|
||||
|
||||
`wtr.` to nowoczesna pogodowa PWA dla Polski oparta o publiczne dane IMGW i jawnie oznaczoną prognozę modelową łączącą IMGW ALARO z Open-Meteo. Aplikacja prezentuje bieżącą analizę pogody IMGW Hybrid, odczyty synoptyczne, prognozę godzinową i 7-dniową, stacje hydrologiczne oraz ostrzeżenia w spokojnym, mobilnym interfejsie z gradientami, kartami glassmorphism i subtelnymi animacjami. Dashboard pokazuje rozszerzony desktopowy podgląd godzin z podsumowaniem najbliższej doby, a także wykresy temperatury i opadu dla bieżącego dnia. Każdy dzień prognozy można także otworzyć w animowanym widoku szczegółowym z przebiegiem godzinowym oraz wykresami.
|
||||
`wtr.` to nowoczesna pogodowa PWA dla Polski oparta o publiczne dane IMGW i jawnie oznaczoną prognozę modelową łączącą IMGW ALARO z Open-Meteo. Aplikacja prezentuje bieżącą analizę pogody IMGW Hybrid, odczyty synoptyczne, prognozę godzinową i 7-dniową, stacje hydrologiczne oraz ostrzeżenia w spokojnym, mobilnym interfejsie z czytelną typografią, opaque surfaces i subtelnymi animacjami. Dashboard pokazuje rozszerzony desktopowy podgląd godzin z podsumowaniem najbliższej doby, a także wykresy temperatury i opadu dla bieżącego dnia. Każdy dzień prognozy można także otworzyć w animowanym widoku szczegółowym z przebiegiem godzinowym oraz wykresami.
|
||||
|
||||
Interfejs jest dostępny po polsku i angielsku. Wybrany język jest zapisywany lokalnie w przeglądarce. Oryginalne treści ostrzeżeń oraz nazwy stacji pochodzą bezpośrednio z API IMGW i nie są automatycznie tłumaczone.
|
||||
|
||||
@@ -92,7 +92,7 @@ Prognoza godzinowa i dzienna rozpoznaje następujące stany warunków pogodowych
|
||||
|
||||
Bieżąca analiza IMGW Hybrid rozpoznaje bezpośrednio opad deszczu, śnieg i burzę. Gdy żadne z tych zjawisk nie występuje, hero może pokazać pomocniczy opis: `Silny wiatr`, `Wilgotne warunki` albo `Spokojne warunki`.
|
||||
|
||||
Gradient hero i część animacji korzystają z uproszczonego nastroju wizualnego:
|
||||
Hero aktualnej pogody korzysta z uproszczonego nastroju wizualnego do wyboru ikony, tekstu i małego akcentu stanu. Nie steruje już pełnoekranowym gradientem.
|
||||
|
||||
| Mood | Obecna reguła |
|
||||
| --- | --- |
|
||||
@@ -103,7 +103,7 @@ Gradient hero i część animacji korzystają z uproszczonego nastroju wizualneg
|
||||
| `warm` | temperatura od `20°C` |
|
||||
| `mild` | pozostałe przypadki |
|
||||
|
||||
Warstwa efektów wizualnych dodaje miękkie chmury dla `cloudy`, poświatę dla `warm`, gwiazdy nocą, smugi przy silnym wietrze, krople przy lokalnym opadzie, błyski przy burzy oraz chłodną poświatę dla `cold`. Mood hero jest obecnie heurystyką opartą o porę dnia, temperaturę, wilgotność i wiatr. Nie jest jeszcze pełną klasyfikacją sterowaną kodem warunków IMGW Hybrid.
|
||||
Warstwa efektów wizualnych jest ograniczona do subtelnych efektów informacyjnych: kropli przy lokalnym opadzie oraz błysku przy burzy. Mood hero jest obecnie heurystyką opartą o porę dnia, temperaturę, wilgotność i wiatr. Nie jest jeszcze pełną klasyfikacją sterowaną kodem warunków IMGW Hybrid.
|
||||
|
||||
## Struktura projektu
|
||||
|
||||
|
||||
@@ -90,6 +90,9 @@ select {
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
inset: -1px 0 0;
|
||||
min-height: calc(100dvh + 1px);
|
||||
background: hsl(var(--foreground) / 0.55);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { CloudSun, Droplets, Sunrise, Sunset, Wind, X } from "lucide-react";
|
||||
import { DayForecastCharts } from "@/components/charts/day-forecast-charts";
|
||||
@@ -55,6 +56,7 @@ export function DayForecastModal({
|
||||
}) {
|
||||
const { language, locale, t } = useI18n();
|
||||
const closeButtonRef = useRef<HTMLButtonElement>(null);
|
||||
const portalRoot = typeof document === "undefined" ? null : document.body;
|
||||
const maximumWind = useMemo(() => getMaximumWind(hours), [hours]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -80,11 +82,11 @@ export function DayForecastModal({
|
||||
? new Intl.DateTimeFormat(locale, { weekday: "long", day: "numeric", month: "long", timeZone: "UTC" }).format(new Date(`${day.date}T12:00:00Z`))
|
||||
: "";
|
||||
|
||||
return (
|
||||
const modal = (
|
||||
<AnimatePresence>
|
||||
{day ? (
|
||||
<motion.div
|
||||
className="modal-overlay fixed inset-0 z-[90] flex items-center justify-center p-0 sm:p-4 lg:p-8"
|
||||
className="modal-overlay z-[90] flex items-center justify-center p-0 sm:p-4 lg:p-8"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
@@ -179,4 +181,6 @@ export function DayForecastModal({
|
||||
) : null}
|
||||
</AnimatePresence>
|
||||
);
|
||||
|
||||
return portalRoot ? createPortal(modal, portalRoot) : null;
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ export function ForecastPanel({ latitude, longitude, locationName }: { latitude?
|
||||
</div>
|
||||
) : isError || !forecast ? (
|
||||
<Card className="flex min-h-40 flex-col items-center justify-center p-6 text-center">
|
||||
<p className="text-sm text-slate-600 dark:text-slate-300">{t("forecast.error")}</p>
|
||||
<p className="text-sm text-muted">{t("forecast.error")}</p>
|
||||
<Button variant="glass" className="mt-4" onClick={() => refetch()}><RefreshCw className="size-4" />{t("common.retry")}</Button>
|
||||
</Card>
|
||||
) : !forecast.hourly.length || !forecast.daily.length ? (
|
||||
|
||||
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 6.9 KiB |
@@ -1,12 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<defs>
|
||||
<linearGradient id="g" x1="70" y1="44" x2="450" y2="484" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#38bdf8"/>
|
||||
<stop offset=".52" stop-color="#2563eb"/>
|
||||
<stop offset="1" stop-color="#312e81"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="512" height="512" rx="142" fill="url(#g)"/>
|
||||
<circle cx="388" cy="138" r="78" fill="#fff" fill-opacity=".12"/>
|
||||
<text x="62" y="318" fill="white" font-family="Arial, Helvetica, sans-serif" font-size="186" font-weight="700" letter-spacing="-28">wtr.</text>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="wtr.">
|
||||
<rect width="512" height="512" rx="120" fill="#171d25"/>
|
||||
<rect x="22" y="22" width="468" height="468" rx="102" fill="none" stroke="#eef3f7" stroke-opacity=".16" stroke-width="2"/>
|
||||
<text x="62" y="318" fill="#eef3f7" font-family="Arial, Helvetica, sans-serif" font-size="186" font-weight="700" letter-spacing="-28">wtr</text>
|
||||
<circle cx="382" cy="295" r="18" fill="#8fb4ce"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 606 B After Width: | Height: | Size: 482 B |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 6.6 KiB |
@@ -1,12 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<defs>
|
||||
<linearGradient id="g" x1="40" y1="20" x2="480" y2="500" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#38bdf8"/>
|
||||
<stop offset=".55" stop-color="#2563eb"/>
|
||||
<stop offset="1" stop-color="#312e81"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="512" height="512" fill="url(#g)"/>
|
||||
<circle cx="392" cy="124" r="90" fill="#fff" fill-opacity=".12"/>
|
||||
<text x="62" y="318" fill="white" font-family="Arial, Helvetica, sans-serif" font-size="186" font-weight="700" letter-spacing="-28">wtr.</text>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="wtr.">
|
||||
<rect width="512" height="512" fill="#171d25"/>
|
||||
<rect x="44" y="44" width="424" height="424" rx="96" fill="#1f2630"/>
|
||||
<rect x="68" y="68" width="376" height="376" rx="78" fill="none" stroke="#eef3f7" stroke-opacity=".14" stroke-width="2"/>
|
||||
<text x="84" y="318" fill="#eef3f7" font-family="Arial, Helvetica, sans-serif" font-size="172" font-weight="700" letter-spacing="-26">wtr</text>
|
||||
<circle cx="386" cy="296" r="17" fill="#8fb4ce"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 597 B After Width: | Height: | Size: 544 B |
@@ -4,8 +4,8 @@
|
||||
"description": "Pogoda z danych IMGW. Prosto. Pięknie. Aktualnie.",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#07111f",
|
||||
"theme_color": "#0c4a6e",
|
||||
"background_color": "#171d25",
|
||||
"theme_color": "#171d25",
|
||||
"lang": "pl",
|
||||
"orientation": "portrait-primary",
|
||||
"icons": [
|
||||
|
||||