teecup/DESIGN_SYSTEM.md
Erol Haagenrud 6bfd986964 Ja, den gir mye mening — og jeg sjekket to av påstandene direkte mot koden i stedet for å ta dem på tro:
Bekreftet, reelt: "Deg Deg"-duplikaten er ekte. playerLabel() erstatter selve NAVNET med teksten "Deg" for din egen rad, OG kortet legger på en egen "Deg"-badge ved siden av — du ser den bokstavelig to ganger, akkurat som i skjermbildet du delte tidligere. Samme sted i koden (spillerkortet OG den nye scoringslisten).

Også bekreftet: score-sirkelen i den nye listen er rounded-full + grønn UANSETT om resultatet er under, over eller på par — den følger ikke golfscore-språket i det hele tatt (sirkel skal bety under par, firkant over par). Det forklarer noe jeg ikke helt klarte å sette fingeren på selv: fargen grønn er kraftig overbrukt — aktiv hull-fane, valgt tall, denne sirkelen, "Deg"-badgen, rangeringsmerket for lederen, primærknapper — alt er grønt, uansett om det faktisk betyr "aktiv/positivt" eller bare er standard-stilen. Det er en skarpere diagnose enn noe jeg selv har formulert denne økten, og den forklarer trolig mye av "fortsatt rotete"-følelsen din utover selve tettheten.

Resten (tetthetsprinsippet, "verktøy ikke nettside", scorekort-som-grid) er en sammenhengende, godt begrunnet retning — men det er en STØRRE strukturell endring (bytte fra dagens "faner + kompakt liste" til et fullt scorekort-grid som primærvisning, med veiviseren åpnet fra en hvilken som helst celle).

Jeg foreslår å dele dette i to:

Compliance-passet (seksjon 3) nå — konkrete, allerede bekreftede feil, lav risiko, rask gevinst.
Grid-redesignet som egen, større runde — fortjener et bevisst ja fra deg først, siden det er en helt annen informasjonsarkitektur enn det jeg bygde i går.
2026-07-27 09:03:15 +02:00

233 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# TeeCup — designsystem (referanse)
> Dette dokumentet beskriver det VISUELLE grunnlaget for alt frontend-
> arbeid i TeeCup — fargetoken, typografi, avstand, komponentmønstre og
> tilgjengelighetsregler. Det er en beskrivelse av hva som ER etablert i
> koden (`frontend/app/globals.css`, `frontend/components/ui/*`, og
> mønstrene brukt på tvers av `components/*.tsx`), ikke et nytt forslag —
> hensikten er at både V0-prompter og håndkodet arbeid skal kunne
> forankres i akkurat dette, i stedet for å gjettes på nytt hver gang.
>
> Ufravikelig grunnregel (fra CLAUDE.md, gjelder ALT, eksisterende og
> fremtidig): appen skal være lesbar, forståelig og betjenbar for noen med
> noe redusert syn UTEN briller. God kontrast, stor nok skrift, store nok
> trykkflater, ikke ikon-only uten tekstlabel for viktige handlinger, ikke
> avhengig av finmotorikk/skarpt syn. Enhver visuell beslutning under
> veies mot dette først.
## Merkevare-opprinnelse
Fargeformen/-tonen er hentet fra Teeoff-logoen (samme merkevarefamilie,
egne farger — se ADR-009/ADR-016), IKKE navn eller logo. Basisfargene ble
regnet ut som presise OKLCH-verdier fra de opprinnelige hex-fargene:
- Grønn (primær): `#8bc24a`
- Oransje (sekundær/"brand-orange"): `#ff5722`
Disse to er de ENESTE to kjernefargene i merkevaren. Et forslag om en
tredje ("informasjons"-)farge ble vurdert 2026-07-25 og IKKE besluttet —
se FEATURE_BACKLOG.md. Anbefalingen der var å gjenbruke `--chart-3`
(allerede en blåtone i statistikk-skalaen) fremfor å finne på noe nytt.
## Fargetoken (`frontend/app/globals.css`)
Alle farger er definert som OKLCH-variabler, med egne verdier for lys og
mørk modus (`:root` / `.dark` / `@media (prefers-color-scheme: dark)`,
alle tre holdt i synk). Bruk ALLTID token-navnene under (Tailwind-klasser
som `bg-primary`, `text-muted-foreground` osv.) — aldri rå hex/oklch i ny
kode.
| Token | Lys modus | Rolle |
|---|---|---|
| `background` / `foreground` | nesten hvit / mørk grønn-grå | sideflate / brødtekst |
| `card` / `card-foreground` | hvit / mørk grønn-grå | kort, paneler, seksjoner |
| `primary` / `primary-foreground` | `oklch(0.7512 0.1613 130.33)` (grønn) / mørk | hovedhandling, valgt tilstand, "under par"/positiv |
| `secondary` / `secondary-foreground` | lys grønn-tint | sekundær vekt, sjeldent brukt alene |
| `muted` / `muted-foreground` | lys grå-grønn / dempet | bakgrunnstoner, sekundær tekst/bildetekst |
| `accent` / `accent-foreground` | lys grønn-tint | hover-tilstand på nøytrale flater |
| `destructive` | rød-oransje | slett/fare-handlinger |
| `border` / `input` | lys grå-grønn | kant på kort, felt, delelinjer |
| `ring` | samme som `primary` | fokus-ring (tastatur-tilgjengelighet) |
| `brand-orange` / `brand-orange-foreground` | `oklch(0.6792 0.2128 36.53)` | sekundærfarge — "over par"/advarsel, IKKE feil (det er `destructive`) |
| `chart-1``chart-6` | grønn→blå→gul→oransje→rød-skala | statistikk-/fordelingsvisualiseringer (ADR-033), ALDRI brukt for kategoriske UI-tilstander |
**Farge-semantikk, konsekvent på tvers av hele appen:**
- `primary` (grønn) = valgt/aktiv, positivt resultat (under par, ledende,
bekreftet), primærhandling.
- `brand-orange` = nest viktigst/kontrast-pol i et par (over par, "Poeng"
i noen sammenhenger), IKKE en feilfarge.
- `destructive` = KUN faktiske slette-/fare-handlinger (Slett runde,
fjern spiller), aldri "over par" eller lignende domenetilstand.
- `muted-foreground` = all sekundær/bildetekst-tekst, aldri brødtekstens
hovedfarge.
**Aldri farge alene.** Hver gang farge bærer betydning (over/under par,
leder, valgt/uvalgt), følges den av FORM (sirkel vs. firkant, fylt vs.
bordered), tekst (+N/N/E, "Deg"/"Eier"), eller et ikon — se
"Golfscore-språket" under. Dette er ikke en stilistisk preferanse, det
er et direkte utslag av tilgjengelighetsregelen øverst.
## Typografi
- **Skrifttype:** Nunito (`next/font/google`, lastet i `app/layout.tsx`,
eksponert som `--font-sans`) — rund, vennlig, god lesbarhet på små
trykkflater. Ingen sekundær/monospace-font i UI-et (tabular-nums
brukes for tallinnretting i stedet for en egen tallfont — se under).
- **Skala** (Tailwind-klasser, brukt konsekvent):
- `text-sm` — bildetekst, sekundær informasjon, knapp-tekst i tette
kontekster
- `text-base` — brødtekst, standard knapp-/felt-tekst, listeelementer
- `text-lg` / `text-xl` — kortoverskrifter, seksjonstitler
- `text-2xl` / `text-3xl` — sideoverskrifter, hull-header, store
resultat-tall
- **Vekt:** `font-semibold`/`font-bold` for all vektlagt tekst,
`font-extrabold` for tall og hovedoverskrifter. Vanlig (regular) vekt
brukes nesten aldri i UI-kontroller — selv sekundærtekst er ofte
`font-semibold` for lesbarhet på avstand/i sollys.
- **`tabular-nums` på ALL numerisk visning** (skår, HCP, datoer,
rangeringer) — hindrer tall fra å "hoppe" i bredde når de endres.
- **Golfvis fortegn:** til-par-tall formateres ALLTID som "E" (jevnt med
par), "+N" (over), "N" — ekte minustegn (U+2212), ALDRI en vanlig
bindestrek eller et bart negativt tall. Samme regel for HCP/
differensial-tall der fortegn er meningsbærende.
## Avstand, hjørner og lag
- **Hjørneradius** (fra `--radius: 0.625rem`, skalert opp via
`--radius-sm/md/lg/xl/2xl/3xl/4xl`): `rounded-xl` for felt/knapper i
tette rader, `rounded-2xl` for kort/paneler/knapper med normal vekt,
`rounded-3xl` for store side-nivå-containere (hull-panel, rundekort).
`rounded-full` reservert for sirkulære tilstander (se golfscore-språket)
og pille-formede brytere/merker.
- **Kort/panel-basismønster:** `rounded-2xl` eller `rounded-3xl`, `border
border-border`, `bg-card`, `p-4` (tett) til `p-5 sm:p-6` (romslig), ofte
`shadow-sm shadow-black/5` for lett løft fra bakgrunnen.
- **Sammenhengende lister** (leaderboard, spillerliste): ÉN container
(`rounded-2xl border border-border overflow-hidden`) med
`divide-y divide-border` mellom radene — IKKE separate kort med
mellomrom, det gir en "kort-følelse" som virker mer oppstykket enn
nødvendig når radene i praksis hører sammen (bevisst rettet opp
2026-07-26 på leaderboardet, se CLAUDE.md).
- **Stiplet kant** (`border border-dashed border-border`) markerer en
"legg til"/tom-tilstand-affordance (f.eks. "+ Medspiller"), ALDRI
brukt på innhold som allerede finnes.
## Trykkflater og avstand mellom elementer
- **44px er det ufravikelige gulvet** for enhver trykkbar flate
(`min-h-11` i Tailwind-tall siden 1 enhet = 4px). Primærhandlinger er
ofte enda større: `h-12``h-16` for tallvelgere/hovedknapper.
- Runde ikon-only-knapper (lukk, fjern) er minimum `size-11`.
- Gap mellom relaterte kontroller: `gap-2`/`gap-2.5`/`gap-3` normalt,
`gap-4`/`gap-6` mellom distinkte seksjoner.
## Layout-containere
- Innloggede/app-sider: `mx-auto w-full max-w-3xl` for innholdsrike
sider (runde-detalj), `max-w-xl` for enklere/lesevisninger
(leaderboard, scorekort, statistikk).
- Sticky header: `sticky top-0 z-10` (eller `z-20` når flere lag stables),
`border-b border-border`, `bg-background/90` til `/95` + `backdrop-blur`
— innhold forblir lesbart under skrolling uten å bli en solid, brå kant.
- Fullskjerm-overlegg (scoringsveiviser): `fixed inset-0 z-50 flex
flex-col bg-background`, med `header`/`main` (scroller,
`flex-1 overflow-y-auto`) /`footer` (handling-rad, `shrink-0`) — samme
tre-delt struktur uansett hvor i appen et slikt overlegg brukes.
- Fast-per-rad-høyde når kort skal se enhetlige ut i en liste
(`min-h-[Npx]` + `truncate` på tekstlinjer i stedet for fri wrapping) —
brukt bevisst på spillerkort 2026-07-26 for at rader med ulikt antall
merker/lengde på navn likevel blir like høye.
## Komponentmønstre (gjenbrukes, ikke gjenoppfinnes)
Disse er alle allerede bygget som lokale funksjoner i de respektive
`components/*.tsx`-filene (ikke delte imports på tvers av filer — hver
fil har sin egen lokale kopi tilpasset sin kontekst, et bevisst valg
gjentatt flere ganger denne økten fremfor tidlig abstraksjon).
- **`NumberPicker`** (`round-detail.tsx`) — 3-kolonners rutenett av store
(`h-16`) tallknapper, valgt tilstand = fylt `bg-primary`, med en
valgfri liten bildetekst under tallet (par-merke, eller — Slag-
varianten — kontekstuell golf-term: Albatross/Eagle/Birdie/Par/Bogey/
Dobbel bogey relativt til hullets par). Utvidbar "10+"-knapp for
sjeldne høye verdier, holder standardvisningen kort.
- **`Stepper`** (+/) — pille-formet rad med to `size-11`-knapper som
flankerer et sentrert tall. Brukt for løpende antall (putter, chip,
bunker, straffeslag) der en full talltavle ville vært overkill.
- **`ChoiceRow`** — horisontal rad av segmenterte knapper
(`flex-1`, valgt = `bg-primary`), for korte, faste alternativlister
(avstand første putt, kjønn, statistikknivå).
- **`DirectionCross`** — 3×3-rutenett med retningsikoner (`ArrowUp/Left/
Right/Down` + `Target` i sentrum) for utslags-/innspillretning. Tekst-
label på hver knapp (ALDRI kun ikon), matcher tilgjengelighetsregelen.
- **`ModeToggle`/segmentert bryter** — `inline-flex gap-1 rounded-full
bg-muted p-1`, hver fane `rounded-full`, valgt = `bg-primary text-
primary-foreground`. Brukt for Brutto/Netto/Poeng, Score/Spillere-faner
osv.
- **`Badge`** (delt shadcn-komponent, `components/ui/badge.tsx`) —
`variant="default"` (fylt grønn) for "Deg", `variant="outline"`
(bordered) for "Eier" — de to skal ALLTID være visuelt distinkte
varianter av hverandre, ikke samme stil i to farger.
## Golfscore-språket (form + farge, aldri farge alene)
Det viktigste, mest gjenbrukte visuelle mønsteret i appen — brukt i
`round-scorecard.tsx` (`ScoreMark`), `round-leaderboard.tsx`
(`ToParMark`/`PointsMark`/`HoleMark`), og flere steder til:
- **Sirkel (`rounded-full`)** = under par / positivt resultat.
- **Firkant med liten radius (`rounded-[4px]``rounded-[6px]`)** = over
par / negativt resultat.
- **Ren tekst, ingen ramme** = nøyaktig par ("E").
- **Fylt bakgrunn** (`bg-primary`/`bg-brand-orange` + kontrastfarget
tekst) = ekstra vekt (eagle+/dobbel bogey+, eller lederen i en
rangering) — fortsatt SAMME form som den vanlige varianten, kun
fyllingen endres. Vanlig variant = `border-2` + `/10`-tint bakgrunn +
farget tekst.
- **Poeng (stableford) har ingen retning** (høyere er alltid bedre) —
får derfor kun ÉN fast form (sirkel), samme fylt/bordered-emphasis-
logikk som resten.
- Rangeringstall bruker et eget, nøytralt merke (`bg-muted`/`bg-primary`
ved leder) — ikke samme form-språk som selve resultatet, for å unngå
at rangering og resultat blandes visuelt sammen.
## Ikonografi
- **`lucide-react`**, konsekvent gjennom hele appen — ingen annen
ikonpakke blandet inn.
- Ethvert ikon som er rent dekorativt får `aria-hidden="true"`.
- **Ikon-only er ALDRI lov for en viktig handling** — en tekstlabel følger
alltid med (unntak eksplisitt vurdert og akseptert: bjelleikonet for
varsler, fordi det er universelt gjenkjent OG har en beskrivende
`aria-label` med antall uleste).
## Tilbakemelding og tilstander
- **Lasting:** sentrert spinner, `size-8``size-10 animate-spin
rounded-full border-4 border-primary/20 border-t-primary`.
- **Feil:** `role="alert"`, `text-destructive`, alltid som ren tekst
(aldri kun et ikon eller en farget kant).
- **Tomme lister:** en kort forklarende tekst + evt. en stiplet
"legg til"-affordance, aldri en stille blank flate.
- **Sanntid (WebSocket):** ingen egen visuell "oppdatert!"-indikator —
data hentes på nytt og erstatter stille, siden radene allerede har
stabile nøkler/layout (unngår blunking).
## Navneformat (innhold, ikke et rent visuelt punkt — men styrer hva som vises hvor)
Direkte adressering av brukeren (dashbord-hilsen, e-post/varsel rettet
TIL mottakeren) bruker KUN fornavn. Alt annet (lister, roster, chat,
administrasjonsvisninger) bruker fullt navn/initialer — se CLAUDE.md for
den fulle, ufravikelige regelen. Nevnt her fordi det er en like fast
STANDING forventning til fremtidig UI som resten av dette dokumentet.
## Hva dette dokumentet bevisst IKKE er
- Ikke en erstatning for CLAUDE.md sin tilgjengelighetsregel (den er
autoritativ og gjengitt her kun i sammendrag).
- Ikke en frys av dagens design — nye mønstre legges til etter hvert som
de bygges (både via V0 og håndkodet), men skal alltid først sjekkes mot
det som ALLEREDE er etablert her fremfor å finne opp noe nytt.
- Ikke en komponent-API-referanse — se selve kildefilene
(`components/ui/*.tsx`, og de lokale komponentene i hver
`components/*.tsx`) for eksakte prop-signaturer.