From 60869ee127c05b27e50ba1cbcaf5f4cae89730e9 Mon Sep 17 00:00:00 2001 From: olestang Date: Wed, 16 Sep 2026 07:43:36 +0200 Subject: [PATCH 1/9] refactor: reorganize WeatherCard into component directory --- web/src/components/WeatherCard/WaterCard.css | 0 web/src/{ => components/WeatherCard}/WeatherCard.tsx | 5 +++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 web/src/components/WeatherCard/WaterCard.css rename web/src/{ => components/WeatherCard}/WeatherCard.tsx (95%) diff --git a/web/src/components/WeatherCard/WaterCard.css b/web/src/components/WeatherCard/WaterCard.css new file mode 100644 index 0000000..e69de29 diff --git a/web/src/WeatherCard.tsx b/web/src/components/WeatherCard/WeatherCard.tsx similarity index 95% rename from web/src/WeatherCard.tsx rename to web/src/components/WeatherCard/WeatherCard.tsx index 71a375e..81a2f5a 100644 --- a/web/src/WeatherCard.tsx +++ b/web/src/components/WeatherCard/WeatherCard.tsx @@ -1,5 +1,6 @@ -import type { City } from './features/weather/cities'; -import type { CityWeather } from './features/weather/weather'; +import type { City } from '../../features/weather/cities'; +import type { CityWeather } from '../../features/weather/weather'; +import './WaterCard.css'; export type WeatherCardProps = { city: City; From bc66ffa61e712e6a289ef7e39b35d4fa2b99112d Mon Sep 17 00:00:00 2001 From: olestang Date: Wed, 16 Sep 2026 09:25:22 +0200 Subject: [PATCH 2/9] refactor: organize global styles and separate app-specific CSS --- web/src/App.css | 5 + web/src/styles.css | 398 ++++++++++++++++++++------------------------- 2 files changed, 177 insertions(+), 226 deletions(-) create mode 100644 web/src/App.css diff --git a/web/src/App.css b/web/src/App.css new file mode 100644 index 0000000..a7aae1e --- /dev/null +++ b/web/src/App.css @@ -0,0 +1,5 @@ +.weather-list { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); + gap: 1rem; +} \ No newline at end of file diff --git a/web/src/styles.css b/web/src/styles.css index 02670f1..9d12dd4 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -1,247 +1,193 @@ -:root { - font-family: system-ui, sans-serif; - line-height: 1.6; - color: #173d35; - background: #f2f5f0; -} + +/********************************************************************/ +/* Includes: CSS resets, CSS variables, Utilities, and Base Layout */ +/********************************************************************/ + + +/**************/ +/* CSS resets */ +/**************/ + * { box-sizing: border-box; } + body { margin: 0; + line-height: 1.5; + min-height: 100dvh; } -main { - width: min(1120px, 100% - 2rem); - margin: 2rem auto 4rem; -} -h1 { - font-size: clamp(2rem, 6vw, 3rem); - line-height: 1.1; -} -.app-header { - display: flex; - justify-content: space-between; - gap: 2rem; - align-items: end; - margin-bottom: 2rem; -} -.eyebrow { - margin: 0; - color: #9a491c; - font-size: 0.8rem; - font-weight: 700; - letter-spacing: 0.08em; - text-transform: uppercase; -} -.controls { - display: flex; - flex-wrap: wrap; - gap: 0.75rem; -} -label { - display: grid; - gap: 0.25rem; - font-size: 0.85rem; - font-weight: 600; + +/* Responsive media by default */ +img, +picture, +video, +canvas, +svg { + display: block; + max-width: 100%; } -select { - min-height: 44px; - padding: 0.5rem 2rem 0.5rem 0.65rem; - border: 1px solid #bacac1; - border-radius: 0.25rem; - background: #fff; - color: inherit; + +/* Make form controls inherit font */ +button, +input, +select, +textarea { font: inherit; } -section { - border: 1px solid #bacac1; - background: #fff; - padding: clamp(1rem, 4vw, 2rem); - border-radius: 0.5rem; -} -.main-weather { - margin-bottom: 1.5rem; -} -.section-heading { - display: flex; - align-items: start; - justify-content: space-between; - gap: 1rem; + +/* Avoid text overflowing containers */ +p, +h1, +h2, +h3, +h4 { + overflow-wrap: break-word; } -.section-heading h2 { - margin: 0.25rem 0 0; + +/* Remove default margins */ +h1, +h2, +h3, +h4, +p, +figure, +blockquote, +dl, +dd { + margin: 0; } -.resource-navigation { - display: flex; - gap: 0.5rem; + +/*****************/ +/* CSS variables */ +/*****************/ + +:root { + /* Surfaces */ + --color-background: #f2f5f0; + --color-surface: #ffffff; + --color-surface-hover: #f8fafc; + --color-surface-selected: #f1f5f9; + + /* Text Color */ + --color-text: #173d35; + --color-text-secondary: #475569; + --color-text-muted: #64748b; + --color-text-inverse: #ffffff; + + /* Borders */ + --color-border: #e2e8f0; + --color-border-hover: #cbd5e1; + --color-border-focus: #3b82f6; + + /* Primary */ + --color-primary: #398d4b; + --color-primary-hover: #327d42; + --color-primary-active: #296c38; + --color-primary-selected: #398d4b; + + --color-primary-focus: #2563eb; + --color-primary-text: #ffffff; + + /* Secondary */ + --color-action-surface: #ddeaf7; + --color-action-surface-hover: #bdcadb; + --color-action-surface-active: #cbd5e1; + + --color-action-surface-border: #b2cbe4; + + --color-action-text: #1e293b; + --color-action-focus: #2563eb; + + /* Cards */ + --color-card-background: #ffffff; + --color-card-hover: #f8fafc; + --color-card-selected: #f3f9fc; + + --color-card-border: #e2e8f0; + --color-card-border-hover: #cbd5e1; + --color-card-border-selected: #94a3b8; + --color-card-focus: #2563eb; + + /* Spacing */ + --space-xs: clamp(0.25rem, 0.2rem + 0.2vw, 0.5rem); + --space-sm: clamp(0.5rem, 0.4rem + 0.3vw, 0.75rem); + --space-md: clamp(1rem, 0.8rem + 0.5vw, 1.5rem); + --space-lg: clamp(1.5rem, 1.1rem + 1vw, 2.5rem); + --space-xl: clamp(2rem, 1.2rem + 2vw, 4rem); + --space-2xl: clamp(3rem, 1.5rem + 4vw, 7rem); + + /* Padding */ + --padding-xs: clamp(0.375rem, 0.3rem + 0.2vw, 0.5rem); + --padding-sm: clamp(0.5rem, 0.4rem + 0.3vw, 0.75rem); + --padding-md: clamp(0.75rem, 0.5rem + 0.7vw, 1.25rem); + --padding-lg: clamp(1rem, 0.5rem + 1.5vw, 2rem); + --padding-xl: clamp(1.5rem, 0.5rem + 3vw, 4rem); + + /* Font Size */ + --font-xs: clamp(0.75rem, 0.72rem + 0.1vw, 0.8rem); + --font-sm: clamp(0.875rem, 0.84rem + 0.1vw, 0.95rem); + --font-md: clamp(1rem, 0.95rem + 0.2vw, 1.125rem); + --font-lg: clamp(1.125rem, 1rem + 0.4vw, 1.375rem); + --font-xl: clamp(1.5rem, 1.2rem + 1vw, 2rem); + --font-2xl: clamp(2rem, 1.4rem + 2vw, 3.25rem); + --font-3xl: clamp(2.5rem, 1.5rem + 4vw, 5rem); + + /* Font Weight */ + --weight-normal: 400; + --weight-medium: 500; + --weight-semibold: 600; + --weight-bold: 700; + + /* Radius */ + --radius-xs: 0.25rem; + --radius-sm: 0.375rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-xl: 1rem; + --radius-2xl: 1.5rem; + --radius-full: 9999px; + + /* Interactice Controls Font Size */ + --control-font-sm: 0.875rem; + --control-font-md: 1rem; + --control-font-lg: 1.25rem; + --control-font-xl: 1.5rem; + --control-font-2xl: 2rem; + --control-font-3xl: 2.7rem; } -.main-weather-summary { + +.app-header { display: flex; justify-content: space-between; + gap: 2rem; align-items: end; - border-block: 1px solid #d5e2dc; - margin: 1.5rem 0; - padding: 1rem 0; -} -.weather-type, -.update-time { - margin: 0; -} -.weather-type { - font-weight: 700; -} -.temperature { - margin: 0.25rem 0 0; - font-size: clamp(3rem, 8vw, 5rem); - line-height: 1; - font-weight: 700; -} -.weather-details { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 1rem; -} -.weather-details dd { - font-size: 1.3rem; -} -.weather-list { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); - gap: 1rem; -} -.weather-card-slot { - min-width: 0; -} -.weather-card-slot-expanded { - position: relative; - z-index: 10; -} -.expanded-weather { - width: 100%; - margin: 0; -} -.details-overlay { - position: fixed; - inset: 0; - z-index: 100; - display: grid; - place-items: center; - padding: 1rem; - background: rgb(23 61 53 / 35%); + margin-bottom: 2rem; } -.details-backdrop { + +/*************/ +/* Utilities */ +/*************/ +.sr-only { position: absolute; - inset: 0; - width: 100%; - height: 100%; - min-height: 0; - padding: 0; - border: 0; - background: transparent; - cursor: default; -} -.details-dialog { - position: relative; - z-index: 1; - width: min(42rem, 100%); - max-height: calc(100vh - 2rem); - overflow: auto; - border-radius: 0.5rem; - box-shadow: 0 1rem 2.5rem rgb(23 61 53 / 25%); -} -.expanded-weather .resource-navigation { - flex-wrap: wrap; - justify-content: flex-end; -} -.weather-card { - border: 1px solid #bacac1; - border-radius: 0.5rem; - padding: 1rem; - background: #fff; - cursor: pointer; -} -.weather-card button + button { - margin-left: 0.5rem; -} -.weather-card:hover, -.weather-card:focus-visible, -.weather-card-selected { - border-color: #173d35; - box-shadow: 0 0 0 3px #d5e2dc; -} -.weather-card h3 { - margin-top: 0; -} -.weather-rank { - margin: -0.5rem 0 0.75rem; - color: #9a491c; - font-size: 0.8rem; - font-weight: 700; -} -.weather-card dl { - gap: 0.75rem 1rem; -} -.weather-card dd { - font-size: 1.1rem; -} -dl { - display: flex; - flex-wrap: wrap; - gap: 1.5rem; -} -dt { - font-size: 0.85rem; -} -dd { - margin: 0.25rem 0; - font-size: 1.5rem; - font-weight: 600; -} -button { - font: inherit; - min-height: 44px; - padding: 0.5rem 1rem; - background: #173d35; - color: white; - border: 0; - border-radius: 0.25rem; - cursor: pointer; + width: 1px; + height: 1px; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; } -button[aria-pressed='true'] { - background: #9a491c; -} -a { - color: inherit; - text-underline-offset: 0.2em; -} -:focus-visible { - outline: 3px solid #9a491c; - outline-offset: 4px; -} -footer { - margin-top: 2rem; - font-size: 0.85rem; +/***************/ +/* Base layout */ +/***************/ + +main { + width: min(1120px, 100% - 2rem); + margin: 2rem auto 4rem; } -@media (max-width: 760px) { - .app-header, - .section-heading, - .main-weather-summary { - align-items: stretch; - flex-direction: column; - } - .controls, - .controls label, - .controls select { - width: 100%; - } - .weather-details { - grid-template-columns: 1fr; - } - .resource-navigation button { - flex: 1; - } - .details-dialog { - width: 100%; - } + +:root { + font-family: system-ui, sans-serif; + color: var(--color-text); + background: var(--color-background); } From b7a01e0b4b3b740efa73525fb3e7a2c957e7d3a6 Mon Sep 17 00:00:00 2001 From: olestang Date: Wed, 16 Sep 2026 10:18:39 +0200 Subject: [PATCH 3/9] feat: add weather card styling --- web/src/components/WeatherCard/WaterCard.css | 124 ++++++++++++++++++ .../components/WeatherCard/WeatherCard.tsx | 99 ++++++++------ 2 files changed, 181 insertions(+), 42 deletions(-) diff --git a/web/src/components/WeatherCard/WaterCard.css b/web/src/components/WeatherCard/WaterCard.css index e69de29..ca3873f 100644 --- a/web/src/components/WeatherCard/WaterCard.css +++ b/web/src/components/WeatherCard/WaterCard.css @@ -0,0 +1,124 @@ +h3 { + font-size: var(--font-xl) +} + +.weather-card { + display: grid; + gap: var(--space-sm); + + background-color: var(--color-card-background); + + padding-inline: var(--space-lg); + padding-block-start: var(--space-sm); + padding-block-end: var(--space-lg); + + border-radius: var(--radius-lg); + border: 1px solid var(--color-card-border); +} + +.weather-card:hover, +.weather-card:focus-visible { + border-color: var(--color-card-border-hover); + background-color: var(--color-card-hover); +} + +.weather-card-selected { + border-color: var(--color-card-border-selected); + background-color: var(--color-card-selected); + + border-width: 2px; +} + +.weather-card__header { + display: flex; + flex-wrap: wrap; + + justify-content: space-between; + align-items: center; +} + +.weather-card__favorite-button { + background-color: transparent; + border: 0; + + font-size: var(--control-font-2xl); +} + +.weather-card__favorite-button:hover, +.weather-card__favorite-button:focus-visible { + border-radius: var(--radius-lg); + + background-color: var(--color-action-surface-hover); + + border: 1px solid var(--color-border-hover); +} + +.weather-card__favorite-button:active { + background-color: var(--color-action-surface-active); +} + +.weather-card__button-full-width { + width: 100%; + + padding: var(--space-xs); + + border-radius: var(--radius-md); + + background-color: var(--color-action-surface); + border: 1px solid var(--color-action-surface-border); +} + +.weather-card__button-full-width:hover, +.weather-card__button-full-width:focus-visible { + background-color: var(--color-action-surface-hover); +} + +.weather-card__button-full-width:active { + background-color: var(--color-action-surface-active); +} + +.weather-card__temperature-condition { + display: flex; + flex-direction: column; +} + +.weather-card__temperature-condition > div:first-child { + font-size: var(--font-lg); + font-weight: var(--weight-semibold); +} + +.weather-card__temperature-condition > div:last-child { + color: var(--color-text-muted); + font-weight: var(--weight-medium); +} + +.weather-card__details { + display: grid; + gap: var(--space-sm); +} + +.weather-card__detail-row { + display: flex; + gap: var(--space-xs); +} + +.weather-card__detail-row dd { + font-weight: var(--weight-medium); +} + +.weather-card__detail-row dt { + color: var(--color-text-secondary); +} + +.weather-card__rank { + padding: var(--space-xs); + + background-color: var(--color-action-surface); + + border-radius: var(--radius-md); +} + +.weather-card__weather-error { + display: grid; + gap: var(--space-sm); +} \ No newline at end of file diff --git a/web/src/components/WeatherCard/WeatherCard.tsx b/web/src/components/WeatherCard/WeatherCard.tsx index 81a2f5a..59b0aa0 100644 --- a/web/src/components/WeatherCard/WeatherCard.tsx +++ b/web/src/components/WeatherCard/WeatherCard.tsx @@ -64,40 +64,32 @@ export function WeatherCard({ isSelected ? 'weather-card weather-card-selected' : 'weather-card' } > -

{city.name}

+
+

{city.name}

+ + +
+ {rank !== null && ( -

#{rank} for valgt aktivitet

+

#{rank} for valgt aktivitet

)} - - - + {isLoading &&

Henter værdata…

} + {hasError && ( -
+

Kunne ikke hente værdata.

)} {weather && ( -
-
-
Værtype
-
{weatherType}
-
-
-
Temperatur
-
{weather.current.temperature_2m} °C
+
+
+
+
Temperatur
+
{weather.current.temperature_2m} °C
+
+ +
+
Værtype
+
{weatherType}
+
-
-
Nedbør
+ +
+
Nedbør:
{weather.current.precipitation} mm
-
-
Vind
+
+
Vind:
{weather.current.wind_speed_10m} m/s
)} + + + + ); } From 6f452ede914f48fc008dc19da4289d06c98bc366 Mon Sep 17 00:00:00 2001 From: olestang Date: Wed, 16 Sep 2026 10:44:16 +0200 Subject: [PATCH 4/9] refactor: move WeatherList to components and apply global styles --- web/src/App.css | 5 --- web/src/App.tsx | 3 +- .../components/WeatherList/WeatherList.css | 6 ++++ .../WeatherList}/WeatherList.tsx | 7 ++-- web/src/styles.css | 35 +++++++++++++++++++ 5 files changed, 47 insertions(+), 9 deletions(-) create mode 100644 web/src/components/WeatherList/WeatherList.css rename web/src/{ => components/WeatherList}/WeatherList.tsx (89%) diff --git a/web/src/App.css b/web/src/App.css index a7aae1e..e69de29 100644 --- a/web/src/App.css +++ b/web/src/App.css @@ -1,5 +0,0 @@ -.weather-list { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); - gap: 1rem; -} \ No newline at end of file diff --git a/web/src/App.tsx b/web/src/App.tsx index effcc9f..8622b5b 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -10,7 +10,8 @@ import { } from './features/weather/cities'; import { weatherQuery, type CityWeather } from './features/weather/weather'; import { useStoredState } from './shared/storage'; -import { WeatherList, type WeatherListItem } from './WeatherList'; +import { WeatherList, type WeatherListItem } from './components/WeatherList/WeatherList'; +import './App.css'; const activities = ['Gåtur', 'Løpetur', 'Skitur'] as const; type Activity = (typeof activities)[number]; diff --git a/web/src/components/WeatherList/WeatherList.css b/web/src/components/WeatherList/WeatherList.css new file mode 100644 index 0000000..e9f2296 --- /dev/null +++ b/web/src/components/WeatherList/WeatherList.css @@ -0,0 +1,6 @@ +.weather-list { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(var(--weather-card-min-width), 1fr)); + + gap: var(--space-md); +} \ No newline at end of file diff --git a/web/src/WeatherList.tsx b/web/src/components/WeatherList/WeatherList.tsx similarity index 89% rename from web/src/WeatherList.tsx rename to web/src/components/WeatherList/WeatherList.tsx index 865b301..ceba542 100644 --- a/web/src/WeatherList.tsx +++ b/web/src/components/WeatherList/WeatherList.tsx @@ -1,7 +1,8 @@ import type { ReactNode } from 'react'; -import type { City } from './features/weather/cities'; -import type { CityWeather } from './features/weather/weather'; -import { WeatherCard } from './WeatherCard'; +import type { City } from '../../features/weather/cities'; +import type { CityWeather } from '../../features/weather/weather'; +import { WeatherCard } from '../WeatherCard/WeatherCard'; +import './WeatherList.css'; export type WeatherListItem = { city: City; diff --git a/web/src/styles.css b/web/src/styles.css index 9d12dd4..26c94d6 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -109,6 +109,10 @@ dd { --color-card-border-selected: #94a3b8; --color-card-focus: #2563eb; + /* Weather Card */ + --weather-card-min-width: 17rem; + + /* Spacing */ --space-xs: clamp(0.25rem, 0.2rem + 0.2vw, 0.5rem); --space-sm: clamp(0.5rem, 0.4rem + 0.3vw, 0.75rem); @@ -168,6 +172,7 @@ dd { /*************/ /* Utilities */ /*************/ + .sr-only { position: absolute; width: 1px; @@ -177,6 +182,36 @@ dd { white-space: nowrap; } +.hidden { + display: none; +} + +/* Margin block */ +.margin-block-start-sm { + margin-block-start: var(--space-sm); +} + +.margin-block-start-md { + margin-block-start: var(--space-md); +} + +.margin-block-start-lg { + margin-block-start: var(--space-lg); +} + +.margin-block-end-sm { + margin-block-end: var(--space-sm); +} + +.margin-block-end-md { + margin-block-end: var(--space-md); +} + +.margin-block-end-lg { + margin-block-end: var(--space-lg); +} + + /***************/ /* Base layout */ /***************/ From dd56901a3bfeb5a8b0001cb4ae402df743c8d271 Mon Sep 17 00:00:00 2001 From: olestang Date: Wed, 16 Sep 2026 14:28:50 +0200 Subject: [PATCH 5/9] feature: update weather details dialog styling and small restructuring of content --- web/src/App.css | 114 +++++++++ web/src/App.tsx | 47 ++-- web/src/WeatherList.test.tsx | 2 +- .../WeatherCard/TempPriorWeather.css | 220 ++++++++++++++++++ web/src/styles.css | 20 +- 5 files changed, 369 insertions(+), 34 deletions(-) create mode 100644 web/src/components/WeatherCard/TempPriorWeather.css diff --git a/web/src/App.css b/web/src/App.css index e69de29..b45ec69 100644 --- a/web/src/App.css +++ b/web/src/App.css @@ -0,0 +1,114 @@ + +/*******************/ +/* Weather Details */ +/*******************/ + +.details-overlay { + position: fixed; + inset: 0; + z-index: 100; + + display: grid; + place-items: center; + + padding: var(--space-md); + + background: rgb(23 61 53 / 35%); +} + +.details-backdrop { + position: absolute; + inset: 0; + + width: 100%; + height: 100%; + border: 0; + padding: 0; + + background: transparent; +} + +.details-dialog { + position: relative; + z-index: 1; + + width: min(42rem, calc(100vw - 2rem)); + max-height: calc(100vh - 2rem); + overflow: auto; + + background-color: var(--color-background); + + padding: var(--space-lg); + + border-radius: var(--radius-lg); + + border: 1px solid var(--color-border); + + box-shadow: var(--shadow-lg); +} + +.details-dialog__close-button { + width: 100%; + + padding: var(--space-xs); + + border-radius: var(--radius-md); + + background-color: var(--color-action-surface); + border: 1px solid var(--color-action-surface-border); +} + +.details-dialog__close-button:hover, +.details-dialog__close-button:focus-visible { + background-color: var(--color-action-surface-hover); +} + +.details-dialog__close-button:active { + background-color: var(--color-action-surface-active); +} + +.details-dialog h2 { + font-size: var(--font-xl); +} + + + + +.details-dialog__temperature-condition { + display: flex; + flex-direction: column; +} + +.details-dialog__temperature-condition > p:first-child { + font-size: var(--font-lg); + font-weight: var(--weight-semibold); +} + +.details-dialog__temperature-condition > p:last-child { + color: var(--color-text-muted); + font-weight: var(--weight-medium); +} + +.details-dialog__detail-row { + display: flex; + gap: var(--space-xs); +} + +.details-dialog__detail-row dd { + font-weight: var(--weight-medium); +} + +.details-dialog__detail-row dt { + color: var(--color-text-secondary); +} + +.details-dialog__update-time { + color: var(--color-text-muted); + + font-size: var(--font-sm); +} + +.details-dialog__section { + display: grid; + gap: var(--space-sm) +} \ No newline at end of file diff --git a/web/src/App.tsx b/web/src/App.tsx index 8622b5b..80fad85 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -250,17 +250,14 @@ function MainWeather({ }) { return (
-
+
-

Valgt by

+

Valgt by

Været i {city.name}

-

Vurdert for {activity.toLowerCase()}. @@ -276,36 +273,36 @@ function MainWeather({ )} {weather && ( <> -

-
-

+

+
+

{weather.current.temperature_2m}°

+

{weatherTypes[weather.current.weather_code] ?? 'Ukjent værtype'}

-

{weather.current.temperature_2m}°

-

- Oppdatert{' '} - -

-
-
-
Nedbør
+
+
+
Nedbør:
{weather.current.precipitation} mm
-
-
Vind
+
+
Vind:
{weather.current.wind_speed_10m} m/s
-
-
Intervall
-
{weather.current.interval / 60} min
-
+

+ Oppdatert{' '} + +

)} + +
); } diff --git a/web/src/WeatherList.test.tsx b/web/src/WeatherList.test.tsx index 2c58173..c243951 100644 --- a/web/src/WeatherList.test.tsx +++ b/web/src/WeatherList.test.tsx @@ -1,6 +1,6 @@ import { render, screen } from '@testing-library/react'; import { vi, expect, it } from 'vitest'; -import { WeatherList } from './WeatherList'; +import { WeatherList } from './components/WeatherList/WeatherList'; import { exampleCity } from './features/weather/cities'; it('shows feedback when there are no weather cards to display', () => { diff --git a/web/src/components/WeatherCard/TempPriorWeather.css b/web/src/components/WeatherCard/TempPriorWeather.css new file mode 100644 index 0000000..9435506 --- /dev/null +++ b/web/src/components/WeatherCard/TempPriorWeather.css @@ -0,0 +1,220 @@ +.eyebrow { + margin: 0; + color: #9a491c; + font-size: 0.8rem; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; +} +.controls { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; +} +label { + display: grid; + gap: 0.25rem; + font-size: 0.85rem; + font-weight: 600; +} +select { + min-height: 44px; + padding: 0.5rem 2rem 0.5rem 0.65rem; + border: 1px solid #bacac1; + border-radius: 0.25rem; + background: #fff; + color: inherit; + font: inherit; +} +section { + border: 1px solid #bacac1; + background: #fff; + padding: clamp(1rem, 4vw, 2rem); + border-radius: 0.5rem; +} +.main-weather { + margin-bottom: 1.5rem; +} +.section-heading { + display: flex; + align-items: start; + justify-content: space-between; + gap: 1rem; +} +.section-heading h2 { + margin: 0.25rem 0 0; +} +.resource-navigation { + display: flex; + gap: 0.5rem; +} +.main-weather-summary { + display: flex; + justify-content: space-between; + align-items: end; + border-block: 1px solid #d5e2dc; + margin: 1.5rem 0; + padding: 1rem 0; +} +.weather-type, +.update-time { + margin: 0; +} +.weather-type { + font-weight: 700; +} +.temperature { + margin: 0.25rem 0 0; + font-size: clamp(3rem, 8vw, 5rem); + line-height: 1; + font-weight: 700; +} +.weather-details { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1rem; +} +.weather-details dd { + font-size: 1.3rem; +} +.weather-list { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); + gap: 1rem; +} +.weather-card-slot { + min-width: 0; +} +.weather-card-slot-expanded { + position: relative; + z-index: 10; +} +.expanded-weather { + width: 100%; + margin: 0; +} +.details-overlay { + position: fixed; + inset: 0; + z-index: 100; + display: grid; + place-items: center; + padding: 1rem; + background: rgb(23 61 53 / 35%); +} +.details-backdrop { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + min-height: 0; + padding: 0; + border: 0; + background: transparent; + cursor: default; +} +.details-dialog { + position: relative; + z-index: 1; + width: min(42rem, 100%); + max-height: calc(100vh - 2rem); + overflow: auto; + border-radius: 0.5rem; + box-shadow: 0 1rem 2.5rem rgb(23 61 53 / 25%); +} +.expanded-weather .resource-navigation { + flex-wrap: wrap; + justify-content: flex-end; +} +.weather-card { + border: 1px solid #bacac1; + border-radius: 0.5rem; + padding: 1rem; + background: #fff; + cursor: pointer; +} +.weather-card button + button { + margin-left: 0.5rem; +} +.weather-card:hover, +.weather-card:focus-visible, +.weather-card-selected { + border-color: #173d35; + box-shadow: 0 0 0 3px #d5e2dc; +} +.weather-card h3 { + margin-top: 0; +} +.weather-rank { + margin: -0.5rem 0 0.75rem; + color: #9a491c; + font-size: 0.8rem; + font-weight: 700; +} +.weather-card dl { + gap: 0.75rem 1rem; +} +.weather-card dd { + font-size: 1.1rem; +} +dl { + display: flex; + flex-wrap: wrap; + gap: 1.5rem; +} +dt { + font-size: 0.85rem; +} +dd { + margin: 0.25rem 0; + font-size: 1.5rem; + font-weight: 600; +} +button { + font: inherit; + min-height: 44px; + padding: 0.5rem 1rem; + background: #173d35; + color: white; + border: 0; + border-radius: 0.25rem; + cursor: pointer; +} + +button[aria-pressed='true'] { + background: #9a491c; +} +a { + color: inherit; + text-underline-offset: 0.2em; +} +:focus-visible { + outline: 3px solid #9a491c; + outline-offset: 4px; +} +footer { + margin-top: 2rem; + font-size: 0.85rem; +} +@media (max-width: 760px) { + .app-header, + .section-heading, + .main-weather-summary { + align-items: stretch; + flex-direction: column; + } + .controls, + .controls label, + .controls select { + width: 100%; + } + .weather-details { + grid-template-columns: 1fr; + } + .resource-navigation button { + flex: 1; + } + .details-dialog { + width: 100%; + } +} diff --git a/web/src/styles.css b/web/src/styles.css index 26c94d6..615a2b0 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -112,7 +112,6 @@ dd { /* Weather Card */ --weather-card-min-width: 17rem; - /* Spacing */ --space-xs: clamp(0.25rem, 0.2rem + 0.2vw, 0.5rem); --space-sm: clamp(0.5rem, 0.4rem + 0.3vw, 0.75rem); @@ -159,14 +158,11 @@ dd { --control-font-xl: 1.5rem; --control-font-2xl: 2rem; --control-font-3xl: 2.7rem; -} -.app-header { - display: flex; - justify-content: space-between; - gap: 2rem; - align-items: end; - margin-bottom: 2rem; + /* Bow Shadow */ + --shadow-sm: 0 1px 3px rgb(0 0 0 / 10%); + --shadow-md: 0 4px 12px rgb(0 0 0 / 15%); + --shadow-lg: 0 12px 32px rgb(0 0 0 / 20%); } /*************/ @@ -226,3 +222,11 @@ main { color: var(--color-text); background: var(--color-background); } + +.app-header { + display: flex; + justify-content: space-between; + gap: 2rem; + align-items: end; + margin-bottom: 2rem; +} \ No newline at end of file From 90e0e340c19aa2e052328c68011bfce9dca90033 Mon Sep 17 00:00:00 2001 From: olestang Date: Wed, 16 Sep 2026 15:16:47 +0200 Subject: [PATCH 6/9] style(header): update header structure and styling --- web/src/App.css | 66 +++++++++++++++++++++++++++++-- web/src/App.tsx | 97 ++++++++++++++++++++++++---------------------- web/src/styles.css | 11 ++---- 3 files changed, 117 insertions(+), 57 deletions(-) diff --git a/web/src/App.css b/web/src/App.css index b45ec69..d5d85f6 100644 --- a/web/src/App.css +++ b/web/src/App.css @@ -1,4 +1,67 @@ +/**********/ +/* Header */ +/**********/ + +.app-header { + background-color: var(--color-header-background); + + border-radius: var(--radius-md); + + box-shadow: var(--shadow-md); + + padding: var(--space-md); + + margin-block-start: var(--space-sm); + margin-block-end: var(--space-md); + + display: grid; + + grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr)); + + gap: var(--space-lg); +} + +.app-header__title-container { + display: flex; + flex-direction: column; +} + +.app-header__weather-controls-section { + display: flex; + flex-direction: column; + + gap: var(--space-md); +} + +.app-header__weather-controls-title { + font-weight: var(--weight-medium); + font-size: var(--font-md); +} + +.app-header__title-container p { + color: var(--color-text-muted); +} + +.app-header__weather-controls { + display: flex; + flex-wrap: wrap; + gap: var(--space-md); +} + +.app-header__weather-controls label { + display: flex; + flex-direction: column; + gap: var(--space-xs); + + flex: 1 1 10rem; +} + + +.app-header__weather-controls select { + width: 100%; +} + /*******************/ /* Weather Details */ /*******************/ @@ -71,9 +134,6 @@ font-size: var(--font-xl); } - - - .details-dialog__temperature-condition { display: flex; flex-direction: column; diff --git a/web/src/App.tsx b/web/src/App.tsx index 80fad85..f717c04 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -41,52 +41,57 @@ function Header({ onSortChange: (sortMode: SortMode) => void; }) { return ( -
-

IT2810 · Gruppe 31

-

Turvær Norge

-

Finn været for din neste tur.

-
- - - +
+
+

Turvær Norge

+

IT2810 · Gruppe 31

+
+ +
+

Finn været for din neste tur.

+
+ + + +
); diff --git a/web/src/styles.css b/web/src/styles.css index 615a2b0..d904372 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -99,6 +99,9 @@ dd { --color-action-text: #1e293b; --color-action-focus: #2563eb; + /* Header */ + --color-header-background: #f3f9ff; + /* Cards */ --color-card-background: #ffffff; --color-card-hover: #f8fafc; @@ -221,12 +224,4 @@ main { font-family: system-ui, sans-serif; color: var(--color-text); background: var(--color-background); -} - -.app-header { - display: flex; - justify-content: space-between; - gap: 2rem; - align-items: end; - margin-bottom: 2rem; } \ No newline at end of file From ecd606d8c3962edd749cda21f474ed056b2b1a8f Mon Sep 17 00:00:00 2001 From: olestang Date: Wed, 16 Sep 2026 15:37:15 +0200 Subject: [PATCH 7/9] style(footer): styled footer and removed layout breaking outdated code from stlyes --- web/src/App.css | 21 ++++++++++++++++++- web/src/App.tsx | 10 ++++++--- .../components/WeatherList/WeatherList.tsx | 2 +- web/src/styles.css | 12 +++++++++-- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/web/src/App.css b/web/src/App.css index d5d85f6..148c1e1 100644 --- a/web/src/App.css +++ b/web/src/App.css @@ -1,4 +1,23 @@ +/**********/ +/* Footer */ +/**********/ + +.app-footer { + background-color: var(--color-footer-background); + width: 100%; + + margin-block-start: var(--space-lg); + + padding-block: var(--space-md); +} + +.app-footer > div { + width: var(--main-wrapper); + + margin-inline: auto; +} + /**********/ /* Header */ /**********/ @@ -13,7 +32,7 @@ padding: var(--space-md); margin-block-start: var(--space-sm); - margin-block-end: var(--space-md); + margin-block-end: var(--space-lg); display: grid; diff --git a/web/src/App.tsx b/web/src/App.tsx index f717c04..59ddd5c 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -399,6 +399,7 @@ export function App() { ); return ( + <>
-
+
+ - +
+ + ); } diff --git a/web/src/components/WeatherList/WeatherList.tsx b/web/src/components/WeatherList/WeatherList.tsx index ceba542..77d2dc0 100644 --- a/web/src/components/WeatherList/WeatherList.tsx +++ b/web/src/components/WeatherList/WeatherList.tsx @@ -36,7 +36,7 @@ export function WeatherList({ }: WeatherListProps) { return (
-

Vær i norske byer

+

Været i norske byer

{items.length === 0 ? (

Ingen værkort å vise.

diff --git a/web/src/styles.css b/web/src/styles.css index d904372..8304e61 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -102,6 +102,9 @@ dd { /* Header */ --color-header-background: #f3f9ff; + /* Footer */ + --color-footer-background: #afc4d5; + /* Cards */ --color-card-background: #ffffff; --color-card-hover: #f8fafc; @@ -166,6 +169,9 @@ dd { --shadow-sm: 0 1px 3px rgb(0 0 0 / 10%); --shadow-md: 0 4px 12px rgb(0 0 0 / 15%); --shadow-lg: 0 12px 32px rgb(0 0 0 / 20%); + + /* Wrappers */ + --main-wrapper: min(1120px, 100% - 2rem); } /*************/ @@ -216,8 +222,10 @@ dd { /***************/ main { - width: min(1120px, 100% - 2rem); - margin: 2rem auto 4rem; + width: var(--main-wrapper); + margin-inline: auto; + + margin-block-start: var(--space-md); } :root { From 713780efc16d34d674dc8cd6a3131f738fd635cb Mon Sep 17 00:00:00 2001 From: olestang Date: Wed, 16 Sep 2026 15:43:00 +0200 Subject: [PATCH 8/9] fix: deleted temporary failing containing outdated styling --- .../WeatherCard/TempPriorWeather.css | 220 ------------------ 1 file changed, 220 deletions(-) delete mode 100644 web/src/components/WeatherCard/TempPriorWeather.css diff --git a/web/src/components/WeatherCard/TempPriorWeather.css b/web/src/components/WeatherCard/TempPriorWeather.css deleted file mode 100644 index 9435506..0000000 --- a/web/src/components/WeatherCard/TempPriorWeather.css +++ /dev/null @@ -1,220 +0,0 @@ -.eyebrow { - margin: 0; - color: #9a491c; - font-size: 0.8rem; - font-weight: 700; - letter-spacing: 0.08em; - text-transform: uppercase; -} -.controls { - display: flex; - flex-wrap: wrap; - gap: 0.75rem; -} -label { - display: grid; - gap: 0.25rem; - font-size: 0.85rem; - font-weight: 600; -} -select { - min-height: 44px; - padding: 0.5rem 2rem 0.5rem 0.65rem; - border: 1px solid #bacac1; - border-radius: 0.25rem; - background: #fff; - color: inherit; - font: inherit; -} -section { - border: 1px solid #bacac1; - background: #fff; - padding: clamp(1rem, 4vw, 2rem); - border-radius: 0.5rem; -} -.main-weather { - margin-bottom: 1.5rem; -} -.section-heading { - display: flex; - align-items: start; - justify-content: space-between; - gap: 1rem; -} -.section-heading h2 { - margin: 0.25rem 0 0; -} -.resource-navigation { - display: flex; - gap: 0.5rem; -} -.main-weather-summary { - display: flex; - justify-content: space-between; - align-items: end; - border-block: 1px solid #d5e2dc; - margin: 1.5rem 0; - padding: 1rem 0; -} -.weather-type, -.update-time { - margin: 0; -} -.weather-type { - font-weight: 700; -} -.temperature { - margin: 0.25rem 0 0; - font-size: clamp(3rem, 8vw, 5rem); - line-height: 1; - font-weight: 700; -} -.weather-details { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 1rem; -} -.weather-details dd { - font-size: 1.3rem; -} -.weather-list { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); - gap: 1rem; -} -.weather-card-slot { - min-width: 0; -} -.weather-card-slot-expanded { - position: relative; - z-index: 10; -} -.expanded-weather { - width: 100%; - margin: 0; -} -.details-overlay { - position: fixed; - inset: 0; - z-index: 100; - display: grid; - place-items: center; - padding: 1rem; - background: rgb(23 61 53 / 35%); -} -.details-backdrop { - position: absolute; - inset: 0; - width: 100%; - height: 100%; - min-height: 0; - padding: 0; - border: 0; - background: transparent; - cursor: default; -} -.details-dialog { - position: relative; - z-index: 1; - width: min(42rem, 100%); - max-height: calc(100vh - 2rem); - overflow: auto; - border-radius: 0.5rem; - box-shadow: 0 1rem 2.5rem rgb(23 61 53 / 25%); -} -.expanded-weather .resource-navigation { - flex-wrap: wrap; - justify-content: flex-end; -} -.weather-card { - border: 1px solid #bacac1; - border-radius: 0.5rem; - padding: 1rem; - background: #fff; - cursor: pointer; -} -.weather-card button + button { - margin-left: 0.5rem; -} -.weather-card:hover, -.weather-card:focus-visible, -.weather-card-selected { - border-color: #173d35; - box-shadow: 0 0 0 3px #d5e2dc; -} -.weather-card h3 { - margin-top: 0; -} -.weather-rank { - margin: -0.5rem 0 0.75rem; - color: #9a491c; - font-size: 0.8rem; - font-weight: 700; -} -.weather-card dl { - gap: 0.75rem 1rem; -} -.weather-card dd { - font-size: 1.1rem; -} -dl { - display: flex; - flex-wrap: wrap; - gap: 1.5rem; -} -dt { - font-size: 0.85rem; -} -dd { - margin: 0.25rem 0; - font-size: 1.5rem; - font-weight: 600; -} -button { - font: inherit; - min-height: 44px; - padding: 0.5rem 1rem; - background: #173d35; - color: white; - border: 0; - border-radius: 0.25rem; - cursor: pointer; -} - -button[aria-pressed='true'] { - background: #9a491c; -} -a { - color: inherit; - text-underline-offset: 0.2em; -} -:focus-visible { - outline: 3px solid #9a491c; - outline-offset: 4px; -} -footer { - margin-top: 2rem; - font-size: 0.85rem; -} -@media (max-width: 760px) { - .app-header, - .section-heading, - .main-weather-summary { - align-items: stretch; - flex-direction: column; - } - .controls, - .controls label, - .controls select { - width: 100%; - } - .weather-details { - grid-template-columns: 1fr; - } - .resource-navigation button { - flex: 1; - } - .details-dialog { - width: 100%; - } -} From cccdfc951d2cf1d1a8a0c63da65a073790f53115 Mon Sep 17 00:00:00 2001 From: olestang Date: Thu, 17 Sep 2026 13:10:47 +0200 Subject: [PATCH 9/9] fix(header): fixed floating header on big screens and renamed to weathercard --- .../WeatherCard/{WaterCard.css => WeatherCard.css} | 0 web/src/components/WeatherCard/WeatherCard.tsx | 2 +- web/src/styles.css | 13 ++++++++++++- 3 files changed, 13 insertions(+), 2 deletions(-) rename web/src/components/WeatherCard/{WaterCard.css => WeatherCard.css} (100%) diff --git a/web/src/components/WeatherCard/WaterCard.css b/web/src/components/WeatherCard/WeatherCard.css similarity index 100% rename from web/src/components/WeatherCard/WaterCard.css rename to web/src/components/WeatherCard/WeatherCard.css diff --git a/web/src/components/WeatherCard/WeatherCard.tsx b/web/src/components/WeatherCard/WeatherCard.tsx index 59b0aa0..1114a80 100644 --- a/web/src/components/WeatherCard/WeatherCard.tsx +++ b/web/src/components/WeatherCard/WeatherCard.tsx @@ -1,6 +1,6 @@ import type { City } from '../../features/weather/cities'; import type { CityWeather } from '../../features/weather/weather'; -import './WaterCard.css'; +import './WeatherCard.css'; export type WeatherCardProps = { city: City; diff --git a/web/src/styles.css b/web/src/styles.css index 8304e61..fe88d11 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -226,10 +226,21 @@ main { margin-inline: auto; margin-block-start: var(--space-md); + + /* Fill any unused vertical space so the footer is pushed down */ + flex: 1; } :root { font-family: system-ui, sans-serif; color: var(--color-text); background: var(--color-background); -} \ No newline at end of file +} + +/* Make the React app at least as tall as the viewport, and stack main + footer vertically */ +#root { + min-height: 100dvh; + display: flex; + flex-direction: column; +} +