diff --git a/app/globals.css b/app/globals.css index 6e0cbbd..c3f2b26 100644 --- a/app/globals.css +++ b/app/globals.css @@ -4,14 +4,42 @@ :root { color-scheme: light; + --background: 210 32% 95%; + --foreground: 214 35% 16%; + --surface: 210 35% 99%; + --surface-muted: 210 26% 92%; + --surface-raised: 0 0% 100%; + --border: 214 20% 82%; + --muted: 215 14% 43%; + --accent: 207 48% 34%; + --accent-foreground: 0 0% 100%; + --warning: 38 58% 42%; + --chart-temperature: 207 48% 36%; + --chart-feels-like: 216 24% 48%; + --chart-rainfall: 202 38% 45%; + --chart-probability: 214 28% 38%; } .dark { color-scheme: dark; + --background: 214 37% 9%; + --foreground: 210 31% 94%; + --surface: 214 33% 13%; + --surface-muted: 215 27% 17%; + --surface-raised: 214 29% 16%; + --border: 214 19% 27%; + --muted: 214 15% 70%; + --accent: 204 44% 66%; + --accent-foreground: 214 37% 9%; + --warning: 39 64% 63%; + --chart-temperature: 204 44% 66%; + --chart-feels-like: 216 22% 73%; + --chart-rainfall: 202 42% 68%; + --chart-probability: 214 26% 76%; } * { - border-color: rgba(148, 163, 184, 0.2); + border-color: hsl(var(--border) / 0.72); } html { @@ -21,14 +49,14 @@ html { body { min-height: 100vh; - background: #eef5fb; - color: #102238; + background: hsl(var(--background)); + color: hsl(var(--foreground)); -webkit-font-smoothing: antialiased; } .dark body { - background: #07111f; - color: #edf7ff; + background: hsl(var(--background)); + color: hsl(var(--foreground)); } button, @@ -40,11 +68,19 @@ select { @layer utilities { .glass { - @apply border border-white/35 bg-white/45 shadow-glass backdrop-blur-2xl dark:border-white/10 dark:bg-slate-950/30; + @apply border border-border/70 bg-surface/80 shadow-card backdrop-blur-xl dark:bg-surface/75; } .glass-subtle { - @apply border border-white/25 bg-white/25 backdrop-blur-xl dark:border-white/10 dark:bg-slate-950/20; + @apply border border-border/60 bg-surface-muted/65 backdrop-blur-xl dark:bg-surface-muted/55; + } + + .surface-control { + @apply border border-border/70 bg-surface/80 shadow-soft backdrop-blur-xl dark:bg-surface-muted/70; + } + + .section-kicker { + @apply text-xs font-semibold uppercase tracking-[0.16em] text-accent; } .text-balance { @@ -58,7 +94,7 @@ select { @supports (-moz-appearance: none) { .weather-scrollbar { - scrollbar-color: rgba(8, 145, 178, 0.72) rgba(14, 116, 144, 0.1); + scrollbar-color: hsl(var(--accent) / 0.62) hsl(var(--border) / 0.32); scrollbar-width: thin; } } @@ -70,28 +106,27 @@ select { .weather-scrollbar::-webkit-scrollbar-track { border-radius: 9999px; - background: rgba(14, 116, 144, 0.1); + background: hsl(var(--border) / 0.28); } .weather-scrollbar::-webkit-scrollbar-thumb { - border: 2px solid rgba(255, 255, 255, 0.3); + border: 2px solid hsl(var(--surface) / 0.78); border-radius: 9999px; - background: linear-gradient(90deg, rgba(8, 145, 178, 0.78), rgba(14, 116, 144, 0.88)); + background: hsl(var(--accent) / 0.68); background-clip: padding-box; - box-shadow: 0 1px 5px rgba(8, 47, 73, 0.25); } .weather-scrollbar::-webkit-scrollbar-thumb:hover { - background: linear-gradient(90deg, rgba(6, 182, 212, 0.9), rgba(3, 105, 161, 0.95)); + background: hsl(var(--accent) / 0.82); background-clip: padding-box; } .dark .weather-scrollbar::-webkit-scrollbar-track { - background: rgba(255, 255, 255, 0.1); + background: hsl(var(--border) / 0.35); } .dark .weather-scrollbar::-webkit-scrollbar-thumb { - border-color: rgba(255, 255, 255, 0.18); - background: linear-gradient(90deg, rgba(34, 211, 238, 0.72), rgba(56, 189, 248, 0.82)); + border-color: hsl(var(--surface) / 0.72); + background: hsl(var(--accent) / 0.7); background-clip: padding-box; } diff --git a/app/layout.tsx b/app/layout.tsx index fea1e93..db247cc 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -36,8 +36,8 @@ export const viewport: Viewport = { initialScale: 1, viewportFit: "cover", themeColor: [ - { media: "(prefers-color-scheme: light)", color: "#e8f4fb" }, - { media: "(prefers-color-scheme: dark)", color: "#07111f" }, + { media: "(prefers-color-scheme: light)", color: "#eef3f7" }, + { media: "(prefers-color-scheme: dark)", color: "#0e1722" }, ], }; diff --git a/app/offline/page.tsx b/app/offline/page.tsx index 04729e2..3e17d2f 100644 --- a/app/offline/page.tsx +++ b/app/offline/page.tsx @@ -7,11 +7,11 @@ import { useI18n } from "@/lib/i18n"; export default function OfflinePage() { const { t } = useI18n(); return ( -
-
+
+

{t("offline.title")}

-

{t("offline.description")}

- {t("offline.back")} +

{t("offline.description")}

+ {t("offline.back")}
); } diff --git a/components/charts/day-forecast-charts.tsx b/components/charts/day-forecast-charts.tsx index 4fa1428..30f35cf 100644 --- a/components/charts/day-forecast-charts.tsx +++ b/components/charts/day-forecast-charts.tsx @@ -7,6 +7,16 @@ import { formatForecastRainfall, formatForecastTemperature } from "@/lib/forecas import type { HourlyForecast } from "@/types/forecast"; const INITIAL_CHART_DIMENSION = { width: 1, height: 1 }; +const CHART_COLORS = { + grid: "hsl(var(--border) / 0.65)", + tooltipBorder: "hsl(var(--border) / 0.75)", + tooltipBackground: "hsl(var(--surface-raised) / 0.96)", + tooltipText: "hsl(var(--foreground))", + temperature: "hsl(var(--chart-temperature))", + feelsLike: "hsl(var(--chart-feels-like))", + rainfall: "hsl(var(--chart-rainfall))", + probability: "hsl(var(--chart-probability))", +}; function formatHour(value: string) { return value.slice(11, 16); @@ -30,16 +40,16 @@ export function DayForecastCharts({ hours }: { hours: HourlyForecast[] }) {
- + [formatForecastTemperature(typeof value === "number" ? value : null, language)]} /> - - + +
@@ -51,12 +61,12 @@ export function DayForecastCharts({ hours }: { hours: HourlyForecast[] }) {
- + [ name === t("forecast.precipitation") ? formatForecastRainfall(typeof value === "number" ? value : null, language) @@ -65,8 +75,8 @@ export function DayForecastCharts({ hours }: { hours: HourlyForecast[] }) { ]} /> - - + +
diff --git a/components/charts/snapshot-chart.tsx b/components/charts/snapshot-chart.tsx index edc7e2c..e9104e4 100644 --- a/components/charts/snapshot-chart.tsx +++ b/components/charts/snapshot-chart.tsx @@ -6,26 +6,31 @@ import { Card } from "@/components/ui/card"; import { useI18n } from "@/lib/i18n"; const INITIAL_CHART_DIMENSION = { width: 1, height: 1 }; +const SNAPSHOT_COLORS = [ + "hsl(var(--chart-temperature))", + "hsl(var(--chart-feels-like))", + "hsl(var(--chart-rainfall))", +]; export function SnapshotChart({ station }: { station: SynopStation }) { const { t } = useI18n(); const rows = [ - { name: t("weather.humidity"), value: station.humidity, unit: "%", max: 100, color: "#38bdf8" }, - { name: t("weather.wind"), value: station.windSpeed, unit: "m/s", max: 20, color: "#818cf8" }, - { name: t("weather.rainfall"), value: station.rainfall, unit: "mm", max: 30, color: "#22d3ee" }, + { name: t("weather.humidity"), value: station.humidity, unit: "%", max: 100, color: SNAPSHOT_COLORS[0] }, + { name: t("weather.wind"), value: station.windSpeed, unit: "m/s", max: 20, color: SNAPSHOT_COLORS[1] }, + { name: t("weather.rainfall"), value: station.rainfall, unit: "mm", max: 30, color: SNAPSHOT_COLORS[2] }, ].filter((row) => row.value !== null).map((row) => ({ ...row, normalized: Math.min(100, ((row.value ?? 0) / row.max) * 100) })); return ( -

{t("snapshot.label")}

+

{t("snapshot.label")}

{t("snapshot.title")}

-

{t("snapshot.description")}

+

{t("snapshot.description")}

- [`${item.payload.value} ${item.payload.unit}`, item.payload.name]} /> + [`${item.payload.value} ${item.payload.unit}`, item.payload.name]} /> {rows.map((row) => )} diff --git a/components/forecast/day-forecast-modal.tsx b/components/forecast/day-forecast-modal.tsx index 009e32e..e238977 100644 --- a/components/forecast/day-forecast-modal.tsx +++ b/components/forecast/day-forecast-modal.tsx @@ -32,9 +32,9 @@ function getMaximumWind(hours: HourlyForecast[]) { function DayMetric({ icon: Icon, label, value }: { icon: typeof Droplets; label: string; value: string }) { return ( -
- -

{label}

+
+ +

{label}

{value}

); @@ -84,7 +84,7 @@ export function DayForecastModal({ {day ? (
-

+

{t("forecast.dayDetails")}

{locationName}

-

{formattedDate}

+

{formattedDate}

- +
-

{getForecastCondition(day.weatherCode, language)}

+

{getForecastCondition(day.weatherCode, language)}

- +

{formatForecastTemperature(day.temperatureMax, language)}

-

{formatForecastTemperature(day.temperatureMin, language)}

+

{formatForecastTemperature(day.temperatureMin, language)}

@@ -151,15 +151,15 @@ export function DayForecastModal({
  • -

    {formatHour(hour.time)}

    - +

    {formatHour(hour.time)}

    +

    {formatForecastTemperature(hour.temperature, language)}

    -

    +

    {hour.precipitationProbability === null ? "—" : `${hour.precipitationProbability}%`}

    diff --git a/components/forecast/forecast-panel.tsx b/components/forecast/forecast-panel.tsx index 4fc6d8b..3af8020 100644 --- a/components/forecast/forecast-panel.tsx +++ b/components/forecast/forecast-panel.tsx @@ -49,9 +49,9 @@ function getTotal(values: Array) { function HourlySummaryMetric({ icon: Icon, label, value }: { icon: LucideIcon; label: string; value: string }) { return ( -
    - -

    {label}

    +
    + +

    {label}

    {value}

    ); @@ -69,8 +69,8 @@ function HourlyForecastSummary({ hours }: { hours: HourlyForecast[] }) { : `${formatForecastTemperature(minimumTemperature, language)} / ${formatForecastTemperature(maximumTemperature, language)}`; return ( -
    -

    {t("forecast.nextHoursOverview")}

    +
    +

    {t("forecast.nextHoursOverview")}

    @@ -89,23 +89,23 @@ function DailyForecastRow({ day, index, onSelect }: { day: DailyForecast; index: initial={{ opacity: 0, y: 8 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: Math.min(index * 0.04, 0.24), duration: 0.28 }} - className="border-t border-white/30 first:border-t-0 dark:border-white/10" + className="border-t border-border/65 first:border-t-0" > onSelect(day)} >

    {label}

    - - {getForecastCondition(day.weatherCode, language)} · {formatForecastRainfall(day.precipitation, language)} + + {getForecastCondition(day.weatherCode, language)} · {formatForecastRainfall(day.precipitation, language)}
    - {day.precipitationProbability === null ? "—" : `${day.precipitationProbability}%`} -

    {formatForecastTemperature(day.temperatureMax, language)}{formatForecastTemperature(day.temperatureMin, language)}

    - + {day.precipitationProbability === null ? "—" : `${day.precipitationProbability}%`} +

    {formatForecastTemperature(day.temperatureMax, language)}{formatForecastTemperature(day.temperatureMin, language)}

    +
    ); @@ -123,9 +123,9 @@ export function ForecastPanel({ latitude, longitude, locationName }: { latitude? return (
    -

    {t("forecast.label")}

    +

    {t("forecast.label")}

    {t("forecast.title")}

    -

    {t("forecast.description", { location: locationName })}

    +

    {t("forecast.description", { location: locationName })}

    {!Number.isFinite(latitude) || !Number.isFinite(longitude) || isPending ? ( @@ -144,7 +144,7 @@ export function ForecastPanel({ latitude, longitude, locationName }: { latitude?
    -

    {t("forecast.hourly")}

    +

    {t("forecast.hourly")}

      {upcomingHours.map((hour, index) => ( @@ -153,24 +153,24 @@ export function ForecastPanel({ latitude, longitude, locationName }: { latitude? initial={{ opacity: 0, y: 8 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: Math.min(index * 0.018, 0.3), duration: 0.25 }} - className="w-[4.6rem] rounded-2xl border border-white/35 bg-white/25 px-2 py-3 text-center dark:border-white/10 dark:bg-white/5 lg:w-[5.5rem] lg:py-4" + className="w-[4.6rem] rounded-card border border-border/60 bg-surface-muted/55 px-2 py-3 text-center lg:w-[5.5rem] lg:py-4" title={getForecastCondition(hour.weatherCode, language)} > -

      {formatHour(hour.time)}

      - +

      {formatHour(hour.time)}

      +

      {formatForecastTemperature(hour.temperature, language)}

      -

      {hour.precipitationProbability === null ? "—" : `${hour.precipitationProbability}%`}

      -
      +

      {hour.precipitationProbability === null ? "—" : `${hour.precipitationProbability}%`}

      +

      - + {formatForecastTemperature(hour.feelsLike, language)}

      - + {formatForecastWind(hour.windSpeed, language)}

      - + {formatForecastRainfall(hour.precipitation, language)}

      @@ -181,7 +181,7 @@ export function ForecastPanel({ latitude, longitude, locationName }: { latitude? -

      {t("forecast.daily")}

      +

      {t("forecast.daily")}

        {forecast.daily.map((day, index) => )}
      diff --git a/components/forecast/forecast-sources.tsx b/components/forecast/forecast-sources.tsx index e6a4710..5ca8a76 100644 --- a/components/forecast/forecast-sources.tsx +++ b/components/forecast/forecast-sources.tsx @@ -9,17 +9,17 @@ export function ForecastSources({ sources }: { sources: ForecastSource[] }) { const hasImgw = sources.includes("imgw-alaro"); return ( -

      +

      {t("forecast.source")}{" "} {hasImgw && ( <> - + IMGW ALARO {", "} )} - + Open-Meteo . {t(hasImgw ? "forecast.sourceCombinedDescription" : "forecast.sourceFallbackDescription")} diff --git a/components/hydro/hydro-page.tsx b/components/hydro/hydro-page.tsx index 9b2afc6..bee2f2d 100644 --- a/components/hydro/hydro-page.tsx +++ b/components/hydro/hydro-page.tsx @@ -27,16 +27,16 @@ export function HydroPage() { return (

      -

      {t("hydro.section")}

      +

      {t("hydro.section")}

      {t("hydro.title")}

      -

      {t("hydro.description")}

      +

      {t("hydro.description")}

      -