From e373b91a71b143fc41fd70710a84e167a1902201 Mon Sep 17 00:00:00 2001 From: tordtst Date: Wed, 30 Sep 2026 10:20:31 +0200 Subject: [PATCH 1/4] style(css): legg til felles designvariabler og grunnstiler Gi alle visninger samme farger, typografi, avstander og synlig tastaturfokus. Fargeparene er kontrollert mot WCAG-kravene. Refs #5 Co-Authored-By: birkjr --- src/main.tsx | 2 ++ src/styles/base.css | 60 +++++++++++++++++++++++++++++++++++++++++++ src/styles/tokens.css | 56 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 src/styles/base.css create mode 100644 src/styles/tokens.css diff --git a/src/main.tsx b/src/main.tsx index 19c52f5..cbc41b1 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,6 +1,8 @@ import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import App from './App.tsx'; +import './styles/tokens.css'; +import './styles/base.css'; const rootElement = document.getElementById('root'); diff --git a/src/styles/base.css b/src/styles/base.css new file mode 100644 index 0000000..8e7766a --- /dev/null +++ b/src/styles/base.css @@ -0,0 +1,60 @@ +/* Grunnstiler for hele appen. Komponentspesifikk stil ligger i egne CSS-moduler. */ + +*, +*::before, +*::after { + box-sizing: border-box; +} + +body { + margin: 0; + background: var(--color-bg); + color: var(--color-text); + font-family: var(--font-family); + font-size: var(--font-size-md); + line-height: var(--line-height-body); +} + +h1, +h2, +h3 { + margin: 0 0 var(--space-4); + line-height: var(--line-height-tight); +} + +h1 { + font-size: var(--font-size-2xl); +} + +h2 { + font-size: var(--font-size-xl); +} + +h3 { + font-size: var(--font-size-lg); +} + +p { + margin: 0 0 var(--space-4); +} + +a { + color: var(--color-accent); + text-underline-offset: 0.15em; +} + +a:hover { + color: var(--color-accent-hover); +} + +img { + display: block; + max-width: 100%; + height: auto; +} + +/* Synlig fokus bare ved tastaturnavigasjon, slik at museklikk ikke gir ring. */ +:focus-visible { + outline: var(--focus-ring); + outline-offset: var(--focus-offset); +} diff --git a/src/styles/tokens.css b/src/styles/tokens.css new file mode 100644 index 0000000..537cee7 --- /dev/null +++ b/src/styles/tokens.css @@ -0,0 +1,56 @@ +/* + * Felles designvariabler. Bruk disse i stedet for faste verdier i komponentene, + * slik at farger, avstander og tekst holder seg like i hele appen. + * Kontrasttall for fargeparene står i docs/design.md. + */ + +:root { + /* Farger */ + --color-bg: #faf8f4; + --color-surface: #ffffff; + --color-surface-muted: #f1ede5; + --color-text: #1f2421; + --color-text-muted: #5b625d; + --color-border: #ddd8ce; + --color-border-strong: #767c78; + --color-accent: #2f6b4f; + --color-accent-hover: #24543e; + --color-on-accent: #ffffff; + --color-free-bg: #e3f0e8; + --color-free-text: #1e5239; + --color-error: #b3261e; + + /* Typografi */ + --font-family: + system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, + sans-serif; + --font-size-sm: 0.875rem; + --font-size-md: 1rem; + --font-size-lg: 1.25rem; + --font-size-xl: 1.5rem; + --font-size-2xl: 2rem; + --font-weight-normal: 400; + --font-weight-bold: 600; + --line-height-tight: 1.25; + --line-height-body: 1.5; + + /* Avstander, i steg på 4 px */ + --space-1: 0.25rem; + --space-2: 0.5rem; + --space-3: 0.75rem; + --space-4: 1rem; + --space-6: 1.5rem; + --space-8: 2rem; + --space-12: 3rem; + + /* Form og layout */ + --radius-sm: 4px; + --radius-md: 8px; + --radius-pill: 999px; + --content-width: 72rem; + --tap-target: 44px; + + /* Fokus */ + --focus-ring: 3px solid var(--color-accent); + --focus-offset: 2px; +} From 84ca237c825dddb2de87da2077566e6a53722103 Mon Sep 17 00:00:00 2001 From: tordtst Date: Wed, 30 Sep 2026 10:20:31 +0200 Subject: [PATCH 2/4] feat(src): lag prismerke og referanse for annonsekort MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vis pris og gratis utlån likt overalt, med tekst og ikke bare farge. Annonsekortet viser bilde eller plassholder, tittel, område og pris som utgangspunkt for katalogen. Refs #5 Co-Authored-By: sebasti --- .../ListingCard/ListingCard.module.css | 48 +++++++++++++++++++ src/components/ListingCard/ListingCard.tsx | 41 ++++++++++++++++ src/components/PriceTag/PriceTag.module.css | 19 ++++++++ src/components/PriceTag/PriceTag.tsx | 19 ++++++++ 4 files changed, 127 insertions(+) create mode 100644 src/components/ListingCard/ListingCard.module.css create mode 100644 src/components/ListingCard/ListingCard.tsx create mode 100644 src/components/PriceTag/PriceTag.module.css create mode 100644 src/components/PriceTag/PriceTag.tsx diff --git a/src/components/ListingCard/ListingCard.module.css b/src/components/ListingCard/ListingCard.module.css new file mode 100644 index 0000000..daebee3 --- /dev/null +++ b/src/components/ListingCard/ListingCard.module.css @@ -0,0 +1,48 @@ +.card { + display: flex; + flex-direction: column; + overflow: hidden; + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius-md); +} + +.image, +.imagePlaceholder { + width: 100%; + aspect-ratio: 4 / 3; +} + +.image { + object-fit: cover; +} + +.imagePlaceholder { + display: grid; + place-items: center; + background: var(--color-surface-muted); + color: var(--color-text-muted); + font-size: var(--font-size-sm); +} + +.body { + display: flex; + flex: 1; + flex-direction: column; + align-items: flex-start; + gap: var(--space-2); + padding: var(--space-4); +} + +.title { + margin: 0; + font-size: var(--font-size-lg); + /* Lange titler brytes i stedet for å gi horisontal scrolling. */ + overflow-wrap: anywhere; +} + +.meta { + margin: 0; + color: var(--color-text-muted); + font-size: var(--font-size-sm); +} diff --git a/src/components/ListingCard/ListingCard.tsx b/src/components/ListingCard/ListingCard.tsx new file mode 100644 index 0000000..bbed0ae --- /dev/null +++ b/src/components/ListingCard/ListingCard.tsx @@ -0,0 +1,41 @@ +import type { Listing } from '../../types/listing.ts'; +import { categoryLabels } from '../../utils/listingFormat.ts'; +import PriceTag from '../PriceTag/PriceTag.tsx'; +import styles from './ListingCard.module.css'; + +interface ListingCardProps { + listing: Listing; +} + +/** + * Visuell referanse for et annonsekort. Katalogen kan bygge videre på denne, + * for eksempel ved å gjøre tittelen til en lenke til detaljvisningen. + */ +function ListingCard({ listing }: ListingCardProps) { + return ( +
+ {listing.imageUrl ? ( + // Tom alt-tekst fordi tittelen rett under beskriver det samme. + + ) : ( +
Ingen bilde
+ )} +
+

{listing.title}

+

+ {categoryLabels[listing.category]} · {listing.area} +

+ +
+
+ ); +} + +export default ListingCard; diff --git a/src/components/PriceTag/PriceTag.module.css b/src/components/PriceTag/PriceTag.module.css new file mode 100644 index 0000000..47a6cc5 --- /dev/null +++ b/src/components/PriceTag/PriceTag.module.css @@ -0,0 +1,19 @@ +.paid, +.free { + display: inline-block; + padding: var(--space-1) var(--space-3); + border-radius: var(--radius-pill); + font-weight: var(--font-weight-bold); + white-space: nowrap; +} + +.paid { + background: var(--color-surface-muted); + color: var(--color-text); +} + +/* Gratis utlån skilles med både tekst og farge, ikke bare farge. */ +.free { + background: var(--color-free-bg); + color: var(--color-free-text); +} diff --git a/src/components/PriceTag/PriceTag.tsx b/src/components/PriceTag/PriceTag.tsx new file mode 100644 index 0000000..e312e5b --- /dev/null +++ b/src/components/PriceTag/PriceTag.tsx @@ -0,0 +1,19 @@ +import { formatDailyPrice } from '../../utils/listingFormat.ts'; +import styles from './PriceTag.module.css'; + +interface PriceTagProps { + /** Pris i hele kroner per døgn. 0 vises som «Gratis utlån». */ + dailyPrice: number; +} + +function PriceTag({ dailyPrice }: PriceTagProps) { + const isFree = dailyPrice === 0; + + return ( + + {formatDailyPrice(dailyPrice)} + + ); +} + +export default PriceTag; From 39899c07368e453d2a2ef320a9bfe9e0c5894498 Mon Sep 17 00:00:00 2001 From: tordtst Date: Wed, 30 Sep 2026 10:20:32 +0200 Subject: [PATCH 3/4] feat(src): legg til designreferanse for utvikling Gi gruppen en felles visuell referanse med farger, tekst og komponenter. Siden er bare tilgjengelig med npm run dev og er ikke med i produksjonsbygget. Refs #5 Co-Authored-By: andretg --- design.html | 17 +++++ src/design/DesignPreview.module.css | 79 ++++++++++++++++++++ src/design/DesignPreview.tsx | 109 ++++++++++++++++++++++++++++ src/design/main.tsx | 17 +++++ 4 files changed, 222 insertions(+) create mode 100644 design.html create mode 100644 src/design/DesignPreview.module.css create mode 100644 src/design/DesignPreview.tsx create mode 100644 src/design/main.tsx diff --git a/design.html b/design.html new file mode 100644 index 0000000..5933a92 --- /dev/null +++ b/design.html @@ -0,0 +1,17 @@ + + + + + + + + Designreferanse – Verktøyutlån Trondheim + + +
+ + + diff --git a/src/design/DesignPreview.module.css b/src/design/DesignPreview.module.css new file mode 100644 index 0000000..86ac7c7 --- /dev/null +++ b/src/design/DesignPreview.module.css @@ -0,0 +1,79 @@ +.page { + max-width: var(--content-width); + margin: 0 auto; + padding: var(--space-8) var(--space-4); +} + +.lead { + max-width: 40rem; + color: var(--color-text-muted); +} + +.section { + margin-top: var(--space-12); +} + +.muted { + color: var(--color-text-muted); + font-size: var(--font-size-sm); +} + +.sizeXl { + font-size: var(--font-size-2xl); + font-weight: var(--font-weight-bold); + line-height: var(--line-height-tight); +} + +.sizeLg { + font-size: var(--font-size-lg); + font-weight: var(--font-weight-bold); +} + +.swatches, +.spaces { + margin: 0; + padding: 0; + list-style: none; +} + +.swatches { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); + gap: var(--space-4); +} + +.swatch { + display: flex; + flex-direction: column; + gap: var(--space-1); +} + +.swatchColor { + height: 3rem; + border: 1px solid var(--color-border-strong); + border-radius: var(--radius-md); +} + +.space { + display: flex; + align-items: center; + gap: var(--space-4); + margin-bottom: var(--space-2); +} + +.spaceBar { + height: var(--space-4); + background: var(--color-accent); +} + +.row { + display: flex; + flex-wrap: wrap; + gap: var(--space-4); +} + +.cardGrid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)); + gap: var(--space-6); +} diff --git a/src/design/DesignPreview.tsx b/src/design/DesignPreview.tsx new file mode 100644 index 0000000..33000a3 --- /dev/null +++ b/src/design/DesignPreview.tsx @@ -0,0 +1,109 @@ +import ListingCard from '../components/ListingCard/ListingCard.tsx'; +import PriceTag from '../components/PriceTag/PriceTag.tsx'; +import { mockListings } from '../data/mockListings.ts'; +import styles from './DesignPreview.module.css'; + +const colorTokens = [ + { token: '--color-bg', usage: 'Sidebakgrunn' }, + { token: '--color-surface', usage: 'Kort og felt' }, + { token: '--color-surface-muted', usage: 'Prismerke, bildeplassholder' }, + { token: '--color-text', usage: 'Brødtekst og overskrifter' }, + { token: '--color-text-muted', usage: 'Sekundær tekst' }, + { token: '--color-border', usage: 'Dekorative rammer' }, + { token: '--color-border-strong', usage: 'Rammer på skjemafelt' }, + { token: '--color-accent', usage: 'Hovedknapp, lenker, fokus' }, + { token: '--color-free-bg', usage: 'Gratismerke, bakgrunn' }, + { token: '--color-free-text', usage: 'Gratismerke, tekst' }, + { token: '--color-error', usage: 'Feilmeldinger' }, +]; + +const spaceTokens = [ + '--space-1', + '--space-2', + '--space-3', + '--space-4', + '--space-6', + '--space-8', + '--space-12', +]; + +// Viser betalt annonse med bilde, gratis annonse og annonse uten bilde og med lang tittel. +const exampleIds = ['listing-001', 'listing-003', 'listing-004', 'listing-008']; +const exampleListings = mockListings.filter((listing) => + exampleIds.includes(listing.id), +); + +/** Felles designreferanse for gruppen. Kun tilgjengelig under utvikling. */ +function DesignPreview() { + return ( +
+

Designreferanse

+

+ Felles farger, tekst og komponenter for Verktøyutlån Trondheim. Siden + finnes bare under utvikling. Innholdet er eksempeldata. +

+ +
+

Farger

+
    + {colorTokens.map(({ token, usage }) => ( +
  • + + {token} + {usage} +
  • + ))} +
+
+ +
+

Typografi

+

Overskrift, 2xl

+

Kortittel, lg

+

Brødtekst, md. Systemfont gir rask lasting og kjent utseende.

+

Sekundær tekst, sm og dempet farge.

+

+ Eksempel på lenke +

+
+ +
+

Avstander

+
    + {spaceTokens.map((token) => ( +
  • + + {token} +
  • + ))} +
+
+ +
+

Pris og gratis utlån

+
+ + + +
+
+ +
+

Annonsekort

+
+ {exampleListings.map((listing) => ( + + ))} +
+
+
+ ); +} + +export default DesignPreview; diff --git a/src/design/main.tsx b/src/design/main.tsx new file mode 100644 index 0000000..31a28db --- /dev/null +++ b/src/design/main.tsx @@ -0,0 +1,17 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import '../styles/tokens.css'; +import '../styles/base.css'; +import DesignPreview from './DesignPreview.tsx'; + +const rootElement = document.getElementById('root'); + +if (!rootElement) { + throw new Error('Fant ikke #root i design.html'); +} + +createRoot(rootElement).render( + + + , +); From e9b556a4826d76292f69e63d3bf049b9c747a61e Mon Sep 17 00:00:00 2001 From: tordtst Date: Wed, 30 Sep 2026 10:20:32 +0200 Subject: [PATCH 4/4] docs(docs): dokumenter designgrunnlag og mappestruktur Beskriv farger med kontrasttall, typografi, avstander, komponentbruk og mappestruktur, slik at alle bygger likt videre. Refs #5 Co-Authored-By: birkjr --- README.md | 23 ++++++++++ docs/design.md | 120 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 docs/design.md diff --git a/README.md b/README.md index f7286ca..511f667 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,29 @@ ESLint finner feil i koden, mens Prettier bestemmer formateringen. ESLint-regler som ellers ville kollidert med Prettier. Byggfiler, avhengigheter og `package-lock.json` formateres ikke. +## Mappestruktur + +``` +src/ + types/ Felles datatyper, for eksempel Listing + data/ Mockdata. Byttes med GraphQL-backend senere + utils/ Formatering av pris og etiketter + assets/ Illustrasjoner til eksempelannonsene + styles/ Designvariabler og grunnstiler + components/ Gjenbrukbare komponenter, én mappe per komponent + design/ Designreferanse, bare under utvikling +``` + +Komponenter i `components/` får data gjennom props og importerer ikke +`data/` direkte. Da kan datakilden byttes uten at visningene må skrives om. + +## Design + +Felles farger, typografi, avstander og komponenter er beskrevet i +[docs/design.md](docs/design.md). Under utvikling viser + en designreferanse med eksempler. Den er +ikke med i produksjonsbygget. + ## Datamodell og mockdata Alle annonser følger typen `Listing` i `src/types/listing.ts`: diff --git a/docs/design.md b/docs/design.md new file mode 100644 index 0000000..860cd2f --- /dev/null +++ b/docs/design.md @@ -0,0 +1,120 @@ +# Design + +Felles visuelt grunnlag for Verktøyutlån Trondheim. Målet er et rolig og +tydelig uttrykk der pris, gratis utlån og område er lett å se. + +## Designreferanse + +Kjør `npm run dev` og åpne . Siden viser +farger, typografi, avstander, prismerking og annonsekort med eksempeldata. +Den finnes bare under utvikling og er ikke med i `npm run build`. + +## Retning + +- Lys, varm bakgrunn, hvite flater og nesten svart tekst. +- Én dempet grønn aksentfarge for hovedhandlinger, lenker og fokus. +- Diskrete rammer, 8 px avrunding og god luft. +- Systemfont for rask lasting og kjent utseende. +- Status og pris vises alltid med tekst, ikke bare med farge. + +## Farger + +Variablene ligger i `src/styles/tokens.css`. Kontrasten er regnet ut etter +WCAG 2.1. Krav: 4,5:1 for vanlig tekst og 3:1 for rammer på skjemafelt og +fokusmarkering. + +| Variabel | Verdi | Bruk | +| ----------------------- | --------- | ----------------------------- | +| `--color-bg` | `#faf8f4` | Sidebakgrunn | +| `--color-surface` | `#ffffff` | Kort og felt | +| `--color-surface-muted` | `#f1ede5` | Prismerke og bildeplassholder | +| `--color-text` | `#1f2421` | Brødtekst og overskrifter | +| `--color-text-muted` | `#5b625d` | Sekundær tekst | +| `--color-border` | `#ddd8ce` | Dekorative rammer | +| `--color-border-strong` | `#767c78` | Rammer på skjemafelt | +| `--color-accent` | `#2f6b4f` | Hovedknapp, lenker og fokus | +| `--color-accent-hover` | `#24543e` | Hover på knapp og lenke | +| `--color-on-accent` | `#ffffff` | Tekst på aksentfarge | +| `--color-free-bg` | `#e3f0e8` | Gratismerke, bakgrunn | +| `--color-free-text` | `#1e5239` | Gratismerke, tekst | +| `--color-error` | `#b3261e` | Feilmeldinger | + +Kontrollerte fargepar: + +| Forgrunn | Bakgrunn | Kontrast | Krav | +| ----------------------- | ----------------------- | -------- | ----- | +| `--color-text` | `--color-bg` | 14,9:1 | 4,5:1 | +| `--color-text-muted` | `--color-bg` | 5,9:1 | 4,5:1 | +| `--color-text-muted` | `--color-surface-muted` | 5,4:1 | 4,5:1 | +| `--color-on-accent` | `--color-accent` | 6,3:1 | 4,5:1 | +| `--color-on-accent` | `--color-accent-hover` | 8,7:1 | 4,5:1 | +| `--color-accent` | `--color-bg` | 5,9:1 | 4,5:1 | +| `--color-free-text` | `--color-free-bg` | 7,7:1 | 4,5:1 | +| `--color-text` | `--color-surface-muted` | 13,5:1 | 4,5:1 | +| `--color-error` | `--color-surface` | 6,5:1 | 4,5:1 | +| `--color-border-strong` | `--color-surface` | 4,3:1 | 3:1 | + +`--color-border` har lav kontrast (1,3:1) og brukes bare til dekorative rammer, +der innholdet er forståelig uten rammen. + +## Typografi + +| Variabel | Størrelse | Bruk | +| ----------------- | --------- | -------------------------- | +| `--font-size-sm` | 14 px | Sekundær tekst og metadata | +| `--font-size-md` | 16 px | Brødtekst | +| `--font-size-lg` | 20 px | Korttitler, `h3` | +| `--font-size-xl` | 24 px | `h2` | +| `--font-size-2xl` | 32 px | `h1` | + +Brødtekst har linjehøyde 1,5 og overskrifter 1,25. Fet skrift er vekt 600. + +## Avstander, form og fokus + +- Avstander går i steg på 4 px: `--space-1` (4 px) til `--space-12` (48 px). + Bruk `--space-4` (16 px) som vanlig sidemarg på mobil. +- `--radius-md` (8 px) for kort og felt, `--radius-pill` for merker. +- `--content-width` (72rem) er maks bredde på innholdet. +- `--tap-target` (44 px) er minste høyde for knapper og andre trykkflater. +- Fokus vises med `:focus-visible` som en 3 px grønn ring med 2 px avstand. + Ikke fjern `outline` uten å erstatte den med noe like tydelig. + +## Komponenter + +Komponentene får data gjennom props og bruker CSS-moduler, slik at klassenavn +ikke kolliderer mellom filer. Hver komponent har sin egen mappe med komponent, +stil og etter hvert tester: + +``` +src/components/PriceTag/ + PriceTag.tsx + PriceTag.module.css +``` + +### PriceTag + +Viser døgnpris, eller «Gratis utlån» når prisen er 0. Gratis utlån får grønn +bakgrunn, men teksten alene er nok til å skille dem. + +```tsx + // Gratis utlån + // 150 kr/døgn +``` + +### ListingCard + +Visuell referanse for et annonsekort med bilde, tittel, kategori, område og +pris. Når `imageUrl` er `null`, vises en plassholder med teksten «Ingen bilde». +Bildet har tom alt-tekst fordi tittelen rett under beskriver det samme. + +```tsx + +``` + +Tittelen er en `h2`. Katalogen kan gjøre tittelen til en lenke til +detaljvisningen når den finnes. + +## Ikke laget ennå + +- Knapp, skjemafelt, sidemal og navigasjon kommer i egen oppgave. +- Mørk modus er ikke planlagt for første innlevering.