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.
This commit is contained in:
Erol Haagenrud 2026-07-27 09:03:15 +02:00
parent 9ec6f1c9ed
commit 6bfd986964
6 changed files with 704 additions and 98 deletions

View file

@ -2817,6 +2817,97 @@ ekte https, `teeoff.no` upåvirket.
boot-et rent, `/health`/`/dashboard`/`/my-rounds`/`/account` → 200 over boot-et rent, `/health`/`/dashboard`/`/my-rounds`/`/account` → 200 over
ekte https, `teeoff.no` upåvirket. ekte https, `teeoff.no` upåvirket.
### Oppdatering 2026-07-26: utslag/HCP per deltaker, gjeste-kontaktinfo, og scoringsflyten redesignet (v2)
Tre beslutninger denne dagen utvider ADR-033 uten å endre grunn-
arkitekturen (Beslutning A-G står uendret).
**1. Utslagssted er nå PER DELTAKER, ikke per runde.** Reelt hull
oppdaget ved bruk: alle deltakere delte tidligere `round.
tee_name_snapshot` uansett kjønn/faktisk valg — en medspiller kunne
aldri spille fra et annet utslag enn eieren. Migrasjon `028_round_
participant_tee.sql` gir `round_participant` sitt eget `tee_name_
snapshot`, backfylt fra rundens eksisterende utslag for eksisterende
rader. `ParticipantCreate`/`ParticipantUpdate` fikk et valgfritt
`tee_name`; et bane-bytte (eksisterende `RoundUpdate`-mekanisme)
nullstiller nå eksplisitt ALLE deltakeres per-deltaker-utslag til det
nye standardutslaget (et bane-bytte gjør individuelle valg fra den
gamle banen meningsløse). HCP kan nå også redigeres per deltaker for
KUN denne runden (samme reproduserbarhets-unntak som resten av
`round_participant` — endrer aldri spillerens faktiske profil),
avvist etter at runden er fullført (samme presedens som bane-bytte).
**2. Gjester (`user_id IS NULL`) fikk et valgfritt kontaktfelt +
redigerbart navn/kjønn.** Migrasjon `029_round_participant_guest_
email.sql` (`guest_email`, nullable). `ParticipantUpdate` fikk `guest_
name`/`gender`/`guest_email` — eksplisitt avvist (400) for en LENKET
deltaker (disse feltene gir ingen mening for noen med egen konto).
`gender`-endring inngår i samme rating-rekalkulering som utslag/HCP
(påvirker hvilken rating som er gyldig).
**3. Scoringsflyten redesignet til en skjermovertagende veiviser --
to iterasjoner, andre erstattet den første samme dag.** Brukeren delte
en skjermopptaksvideo av en konkurrentapp (Golf GameBook) sin
scoreregistrering og spurte om prinsippene kunne forbedre TeeCup.
Videoen ble analysert bilde for bilde (ffmpeg i en engangs Docker-
container) og avdekket et "samlebånd"-mønster: taltastatur med
kontekstuelle golf-termer (Eagle/Birdie/Par/Bogey ut fra hullets par),
og at fullført registrering for én spiller automatisk åpner NESTE
spillers registrering for samme hull.
**Første forsøk (v1), BEVISST avvist fra en full modal:** vurdert som
unødvendig risikofylt å bygge en skjermovertagende steg-for-steg-flyt
korrekt uten visuell testing (intet nettleserverktøy tilgjengelig i
denne økten). I stedet: golf-term-taltastatur lagt til der det allerede
var (`NumberPicker`s nye `showGolfTerms`-modus), pluss en "Neste:
{navn}"/"Neste hull"-knapp nederst på den EKSISTERENDE, lange
inline-siden.
**Brukeren testet v1 live og avviste den eksplisitt:** "ingen forbedring
i det hele tatt", "visuelt like overveldende og rotete" — den
forsiktige, risikoreduserte tilnærmingen var utilstrekkelig; en ekte
skjermovertagende veiviser var det som faktisk krevdes, sammen med et
nytt, eksplisitt krav: akkumulert score-så-langt for RUNDEN synlig for
HVER spiller samtidig (ikke bare én valgt), matchende konkurrentappens
vedvarende "E"/"+1"-visning ved siden av hvert navn.
**v2 bygget samme dag, ERSTATTER v1 helt:** ny `ScoringWizard`-komponent
(fullskjerm, egen DOM-stack utenfor `<main>`), tre steg maks drevet av
`stat_level` ("strokes_only" → kun Slag; "strokes_and_putts" → + Putter/
avstand første putt; "full" → + ett samlet detalj-steg). Bevisst FÆRRE,
grovere steg enn konkurrentens egne 5-6 skjermer — en risikoreduksjon
uten visuell testing, og TeeCups stat_level-modell gjør en finere
oppdeling mindre naturlig uansett. Alle spillerne vises som en fast,
ikke-trykkbar kontekst-rad øverst i veiviseren (speiler konkurrent-
appens "mist aldri oversikten"-prinsipp — bevisst IKKE trykkbar, siden
GameBooks egen video heller ikke viste spillerbytte midt i et steg).
Hovedsiden forenklet radikalt: det gamle Slag/Putter/"flere detaljer"-
skjemaet er fjernet, erstattet med én kompakt liste (navn, "HCP X ·
{til-par} så langt (N hull)", en stor rund knapp som åpner veiviseren).
Datalasting utvidet til å hente ALLE deltakeres hull med det samme
(ikke lenger lat lasting kun for aktiv spiller), nødvendig for at
akkumulert score kan vises for alle samtidig.
**Reell driftsfeil funnet OG rettet SAMME dag, rapportert av bruker som
testet live:** den gamle `ScoreSoFar`("Så langt i runden")-boksen ble
ved en feil IKKE fjernet/flyttet i selve v2-bygget — den lå fortsatt
øverst, uendret, og fikk brukeren til å rapportere "ingen synlig
endring i det hele tatt" (toppen av siden var reelt uendret). Bekreftet
presist ved å hente den FAKTISK kjørende JS-bunten i produksjon og
søke i den — begge de gamle OG de nye tekst-strengene lå i samme bunt,
som beviste dette var en ekte plasseringsfeil, ikke en cache-/
utrullingsfeil. Rettet ved å flytte `ScoreSoFar` til UNDER scorings-
seksjonen (fortsatt tilgjengelig, bare ikke lenger i veien for
hovedoppgaven) — samme klasse feil hele denne rundens redesign forsøkte
å løse.
**Bevisst utenfor omfang, IKKE bygget:** de mest granulære trekkene fra
konkurrentappen (separate skjermer per detalj-felt, en illustrert
"bommet green"-grafikk, spillerbytte midt i et steg) — vurdert som for
stor risiko å bygge presist uten visuell testing, og TeeCups egen
stat_level-gruppering gjør en så fin oppdeling mindre naturlig uansett.
**Ingen ekte nettleser-interaksjonstest utført av noen av iterasjonene**
(intet slikt verktøy tilgjengelig denne økten) — kun logikk (Node-
simulering av stegmaskinen, rangering, akkumulert-score-beregning) og
et ekte API-rundtur-script (nøyaktig samme feltkombinasjon som
veiviseren sender) er verifisert. Full detalj i CLAUDE.md sin
statuslogg (2026-07-26).
--- ---
## ADR-035: Dashbord-redesign — organisasjon blir implisitt ## ADR-035: Dashbord-redesign — organisasjon blir implisitt

View file

@ -3465,6 +3465,36 @@ Ferdig og verifisert:
ingen migrasjon, `docker compose up -d --build teecup_api ingen migrasjon, `docker compose up -d --build teecup_api
teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard` teecup_frontend`. Begge containere boot-et rent, `/health`/`/dashboard`
→ 200, `teeoff.no` upåvirket. → 200, `teeoff.no` upåvirket.
- **Scoringsflyt v3: administrasjon og scoring adskilt i to faner, BYGGET,
IKKE ENNÅ RULLET UT (2026-07-26) -- direkte svar på at bruker fortsatt
fant siden "bråkete og lite intuitiv" etter v2.** Brukeren viste et NYTT
sidestilt skjermbilde-par (samme mønster som første gang) og påpekte at
TeeCup fortsatt hadde mye synlig FØR selve scoringslisten (leaderboard-
forhåndsvisning, Rediger/Fullfør/Slett, hele spillerlisten med Rediger-
ikoner, "+Medspiller") -- nøyaktig det jeg selv identifiserte som GameBooks
kjerneprinsipp i den aller første analysen ("administrasjon og scoring er
ADSKILTE tabs"), men aldri fullt ut gjennomførte i v2 (kun `ScoreSoFar`
ble flyttet forrige runde, resten av det administrative innholdet ble
stående igjen øverst).
**Fikset denne gangen for ekte:** ny `pageTab`-state (`"score" | "manage"`,
default `"score"`), en enkel fanevelger rett under feilmeldingen. "Score"
(default) inneholder nå KUN: fullført-banner (hvis relevant), hull-
navigasjon, og selve hull-panelet (header + scoringslisten fra v2 +
Forrige/Neste hull) -- ingenting annet. "Spillere og runde" samler
leaderboard-forhåndsvisning, Rediger/Fullfør/Slett, hele spillerlisten
(`PlayerList`), OG `ScoreSoFar` (flyttet HIT fra forrige rundes
"under scoringslisten"-plassering, siden den er detaljert stats-innsyn,
ikke selve registreringsoppgaven).
**Verifisert:** ekte typesjekket produksjonsbuild kompilerte rent, samme
engangs `next dev`-container-sjekk ga 200, full regresjon (35-punkts
co-player-testsuite) fortsatt grønn, `test_isolation.sql` 12/12 (ren
frontend-omrokkering, ingen migrasjon).
**Samme ærlige begrensning som v1/v2:** ingen ekte nettleser-
interaksjonstest av selve fane-følelsen.
**Rullet ut mot ekte systemer 2026-07-26**, bruker bekreftet eksplisitt:
ingen migrasjon, `docker compose up -d --build teecup_frontend`
(gjenskapte også `teecup_api` som vanlig bivirkning). Begge containere
boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` upåvirket.
Neste steg: Neste steg:
0a. **Spillerliste-redesign — LIVE, men ikke nettleser-testet 0a. **Spillerliste-redesign — LIVE, men ikke nettleser-testet

233
DESIGN_SYSTEM.md Normal file
View file

@ -0,0 +1,233 @@
# 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.

View file

@ -738,8 +738,11 @@ tillatelse kreves:**
> universelt gjenkjent symbol, MEN skal ha en beskrivende > universelt gjenkjent symbol, MEN skal ha en beskrivende
> `aria-label` som inkluderer antall uleste). > `aria-label` som inkluderer antall uleste).
**Status: In-app varslingssenter BYGGET OG SCRATCH-VERIFISERT 2026-07-26, **Status: In-app varslingssenter BYGGET, SCRATCH-VERIFISERT OG RULLET UT
IKKE ENNÅ RULLET UT.** V0 kjørte prompten (zip 20), backend bygget for å LIVE 2026-07-26** (bruker bekreftet eksplisitt, se CLAUDE.md sin
statuslogg for utrullingsdetalj — denne linjen var utdatert helt til den
ble rettet i en senere gjennomgang samme dag). V0 kjørte prompten (zip
20), backend bygget for å
matche eksakt: migrasjon `026_notifications.sql` (tabell `notification`, matche eksakt: migrasjon `026_notifications.sql` (tabell `notification`,
`plain_connection()`-mønster, ingen RLS — samme som personlig profil/ `plain_connection()`-mønster, ingen RLS — samme som personlig profil/
runder/venner), ny `app/routers/notifications.py` (`create_notification()` runder/venner), ny `app/routers/notifications.py` (`create_notification()`
@ -931,7 +934,7 @@ hjem alene. To ideer, ikke gjensidig utelukkende:
**Status: ingen kode, ingen V0-prompt ennå** — venter på at retning (og **Status: ingen kode, ingen V0-prompt ennå** — venter på at retning (og
særlig turnering-spørsmålet over) avklares før noe designes ferdig. særlig turnering-spørsmålet over) avklares før noe designes ferdig.
### Oppdatering 2026-07-26: backend for RUNDE-leaderboardet BYGGET, V0-prompt sendt ### Oppdatering 2026-07-26: RUNDE-leaderboardet HELT FERDIG (håndkodet, ikke V0 -- se under)
Brukeren ba eksplisitt om å få leaderboardet for frittstående runder på Brukeren ba eksplisitt om å få leaderboardet for frittstående runder på
plass (kun runder, ikke turneringer — turnering-spørsmålet over fortsatt plass (kun runder, ikke turneringer — turnering-spørsmålet over fortsatt
@ -1036,20 +1039,38 @@ stemte eksakt).
> store trykkflater (min. 44px), aldri kun farge for å formidle > store trykkflater (min. 44px), aldri kun farge for å formidle
> informasjon (rangering/over-under par), lesbar uten briller. > informasjon (rangering/over-under par), lesbar uten briller.
**Ikke avgjort ennå, avklares når zip-en er klar til integrering:** **Rettet i selve prompten 2026-07-26, FØR den ble kjørt i v0.app:** den
nøyaktig plassering av lenke til full leaderboard-side (trolig opprinnelige "Deg"-merke-instruksen antok feilaktig at kun eieren ser sin
`round-detail.tsx`, som en lenke/knapp — ikke inline, samme "unngå for egen runde -- ADR-036 fase 3 (bygget samme dag) gjør nå at en lenket
mye stablet oppå hverandre"-lærdom som tidligere runder denne uken), medspiller også kan se leaderboardet. Byttet til to uavhengige merker
og ruten (foreslått `/my-rounds/[id]/leaderboard`, samme mønster som ("Eier" fra `is_owner`, "Deg" fra en egen `participant_id`-prop
`/my-rounds/[id]/stats`/`/scorecard`). komponenten mottar utenfra) -- se prompten over.
**Rettet i selve prompten 2026-07-26 (samme dag, fanget FØR den ble kjørt i **Oppdatering, samme dag: HÅNDKODET i stedet for kjørt i v0.app.**
v0.app):** den opprinnelige "Deg"-merke-instruksen antok feilaktig at kun Brukeren gikk tom for V0-credits (samme situasjon som spillerliste-
eieren ser sin egen runde -- ADR-036 fase 3 (bygget samme dag, se over) gjør redesignet rett over) og ba meg bygge direkte etter nøyaktig samme
nå at en lenket medspiller også kan se leaderboardet. Byttet til to designspesifikasjon som prompten over. Ny `components/round-leaderboard.tsx`
uavhengige merker ("Eier" fra `is_owner`, "Deg" fra en egen + rute `/my-rounds/[id]/leaderboard`, pluss en `RoundLeaderboardMini`
`participant_id`-prop komponenten mottar utenfra) -- se oppdatert prompt (topp 3 + "og N til", lenket fra selve rundens detaljside) og en ny
over. Fortsatt ikke kjørt i v0.app. "Se leaderboard"-lenke på hvert rundekort i `/my-rounds`-listen (krevde å
gjøre om `round-card.tsx` sin ytre `<Link>` til en `<div>` med to separate
lenker -- unngår en nestet `<a>`). Gjenbrukte bevisst etablerte mønstre
(`ScoreMark`s form+farge-språk fra `round-scorecard.tsx`, WS-sanntid-
mønsteret fra `round-stats.tsx`) fremfor å finne opp nye.
**To oppfølgingspunkter samme dag, begge bygget:** (1) leaderboardet
skulle vise hullscorer per spiller -- løst som en utvidbar rad (klikk for
å vise en horisontal strip med hull-for-hull brutto-merker), krevde en
liten backend-utvidelse (`LeaderboardHoleOut`/`holes`-felt, data var
allerede hentet, bare ikke eksponert). (2) et tredje "Poeng"
(stableford)-modus lagt til ved siden av Brutto/Netto (rangert synkende,
siden høyere poengsum er bedre) -- krevde `strokes_received` per hull +
`total_points` på hver leaderboard-rad (også backend, samme
utvidelsesmønster). Netto/poeng vises nå også som en rolig sekundærlinje
RETT under det fremhevede brutto-hullmerket (inspirert av et
referansebilde fra en konkurrentapp bruker delte, bevisst IKKE en kopi av
fargevalg/layout).
Full verifiserings- og utrullingsdetalj i CLAUDE.md sin statuslogg
(2026-07-26) -- ikke gjentatt her for å unngå duplisering.
--- ---
@ -1282,6 +1303,29 @@ feilside. **Samme ærlige begrensning som over:** ingen ekte nettleser-
interaksjonstest (brutto/netto-veksling, faktisk visuell høyde/form-språk) interaksjonstest (brutto/netto-veksling, faktisk visuell høyde/form-språk)
er utført. er utført.
**Oppfølging samme dag: scoringsflyten redesignet til en skjermovertagende
veiviser (v1 avvist av bruker, v2 erstattet den helt) — ✅ LIVE
2026-07-26.** Brukeren delte en skjermopptaksvideo av en konkurrentapp
(Golf GameBook) sin scoreregistrering. Første forsøk (v1) la kun til
golf-term-taltastatur + en "Neste spiller"-knapp på den EKSISTERENDE lange
inline-siden — brukeren testet den live og avviste den eksplisitt ("ingen
forbedring i det hele tatt", "visuelt like overveldende og rotete"). v2
bygget samme dag: en ekte skjermovertagende `ScoringWizard` (tre steg
maks, drevet av `stat_level`), hovedsiden erstattet med én kompakt
spillerliste (navn, akkumulert til-par-så-langt, en stor rund knapp som
åpner veiviseren). Krevde også å laste ALLE deltakeres hull med det
samme (ikke lenger lat lasting), slik at akkumulert score kan vises for
alle samtidig. **Reell driftsfeil funnet OG rettet samme dag:** den gamle
"Så langt i runden"-boksen (`ScoreSoFar`) ble ved en feil IKKE flyttet i
v2-bygget — lå fortsatt øverst, uendret, og fikk brukeren til å
rapportere "ingen synlig endring i det hele tatt" (bekreftet presist ved
å hente den faktisk kjørende JS-bunten i produksjon og søke i den — ikke
en cache-feil, en ekte plasseringsfeil). Rettet ved å flytte boksen til
under scoringsseksjonen. Full beslutnings-/begrunnelsesdetalj i
ARCHITECTURE_DECISIONS.md (ADR-033-oppdatering 2026-07-26), full
verifiserings-/utrullingsdetalj i CLAUDE.md sin statuslogg — ikke
duplisert her.
--- ---
## En tredje (informasjons-)farge til designet — 🧠 DRØFTET 2026-07-25, IKKE besluttet ## En tredje (informasjons-)farge til designet — 🧠 DRØFTET 2026-07-25, IKKE besluttet

View file

@ -294,6 +294,15 @@ export function RoundDetail({ roundId }: { roundId: string }) {
// hull 1 uansett faktisk starthull). // hull 1 uansett faktisk starthull).
const [currentHole, setCurrentHole] = useState<number | null>(null) const [currentHole, setCurrentHole] = useState<number | null>(null)
const [showAddGuest, setShowAddGuest] = useState(false) const [showAddGuest, setShowAddGuest] = useState(false)
// Administrasjon (leaderboard-forhåndsvisning/Rediger/Fullfør/Slett/
// spillerliste) og selve scoringen er nå to ADSKILTE faner (2026-07-26,
// etterspurt av bruker etter å ha sammenlignet direkte med en
// konkurrentapp -- forrige runde flyttet kun ScoreSoFar, men lot alt det
// ANDRE administrative innholdet stå igjen øverst på samme side som
// scoringen, som fortsatt gjorde siden "bråkete" før man i det hele tatt
// når frem til oppgaven). "Score" er default -- den ENESTE fanen de
// fleste trenger under selve spillingen.
const [pageTab, setPageTab] = useState<"score" | "manage">("score")
const [completing, setCompleting] = useState(false) const [completing, setCompleting] = useState(false)
const [deleting, setDeleting] = useState(false) const [deleting, setDeleting] = useState(false)
const [showEditRound, setShowEditRound] = useState(false) const [showEditRound, setShowEditRound] = useState(false)
@ -707,62 +716,122 @@ export function RoundDetail({ roundId }: { roundId: string }) {
</p> </p>
)} )}
{/* Kompakt leaderboard-forhåndsvisning (2026-07-26) -- kun for {/* To faner: "Score" (default, KUN hull-navigasjon + registrering --
runder med flere enn én deltaker (komponenten returnerer selv det eneste de fleste trenger midt i en runde) og "Spillere og
null ellers), lenke til hele leaderboardet. */} runde" (alt administrativt, samlet ett sted i stedet for å ligge
<RoundLeaderboardMini roundId={roundId} /> i veien øverst scoringssiden). */}
<div className="mb-5 inline-flex gap-1 rounded-full bg-muted p-1">
{/* Rediger/Fullfør/Slett samlet ØVERST, bevisst tonet ned (samme <button
nøytrale trigger-stil som hverandre) -- disse tidligere som type="button"
store, fremtredende knapper RETT under "Neste hull"-navigasjonen onClick={() => setPageTab("score")}
nederst i hull-panelet, som gjorde det altfor lett å trykke feil aria-pressed={pageTab === "score"}
ved et uhell mens man bare skulle bla mellom hull (rapportert av className={cn(
bruker 2026-07-25). Ved å flytte dem hit man aktivt scrolle OPP "min-h-10 rounded-full px-4 text-sm font-bold transition-colors",
forbi hull-registreringen for å dem i det hele tatt. */} pageTab === "score" ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:text-foreground",
<div className="mb-5 flex flex-col gap-3">
<div className="flex flex-wrap gap-2">
{isOwnerViewer && (
<button
type="button"
onClick={() => setShowEditRound((v) => !v)}
aria-expanded={showEditRound}
className="inline-flex min-h-11 items-center gap-1.5 rounded-xl border border-dashed border-border bg-card px-3 text-sm font-semibold text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground"
>
<Settings2 aria-hidden="true" className="size-4" />
Rediger runde
</button>
)} )}
>
{!completed && ( Score
<button </button>
type="button" <button
disabled={completing} type="button"
onClick={finishRound} onClick={() => setPageTab("manage")}
className="inline-flex min-h-11 items-center gap-1.5 rounded-xl border border-dashed border-border bg-card px-3 text-sm font-semibold text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground disabled:opacity-60" aria-pressed={pageTab === "manage"}
> className={cn(
<Check aria-hidden="true" className="size-4" /> "min-h-10 rounded-full px-4 text-sm font-bold transition-colors",
{completing ? "Fullfører…" : "Fullfør runde"} pageTab === "manage" ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:text-foreground",
</button>
)} )}
>
{isOwnerViewer && ( Spillere og runde
<button </button>
type="button"
disabled={deleting}
onClick={deleteRound}
className="inline-flex min-h-11 items-center gap-1.5 rounded-xl border border-dashed border-border bg-card px-3 text-sm font-semibold text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive disabled:opacity-60"
>
<Trash2 aria-hidden="true" className="size-4" />
{deleting ? "Sletter…" : "Slett runde"}
</button>
)}
</div>
{showEditRound && isOwnerViewer && (
<EditRoundPanel round={round} onPatch={patchRound} onClose={() => setShowEditRound(false)} />
)}
</div> </div>
{pageTab === "manage" && (
<>
{/* Kompakt leaderboard-forhåndsvisning (2026-07-26) -- kun for
runder med flere enn én deltaker (komponenten returnerer selv
null ellers), lenke til hele leaderboardet. */}
<RoundLeaderboardMini roundId={roundId} />
<div className="mb-5 flex flex-col gap-3">
<div className="flex flex-wrap gap-2">
{isOwnerViewer && (
<button
type="button"
onClick={() => setShowEditRound((v) => !v)}
aria-expanded={showEditRound}
className="inline-flex min-h-11 items-center gap-1.5 rounded-xl border border-dashed border-border bg-card px-3 text-sm font-semibold text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground"
>
<Settings2 aria-hidden="true" className="size-4" />
Rediger runde
</button>
)}
{!completed && (
<button
type="button"
disabled={completing}
onClick={finishRound}
className="inline-flex min-h-11 items-center gap-1.5 rounded-xl border border-dashed border-border bg-card px-3 text-sm font-semibold text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground disabled:opacity-60"
>
<Check aria-hidden="true" className="size-4" />
{completing ? "Fullfører…" : "Fullfør runde"}
</button>
)}
{isOwnerViewer && (
<button
type="button"
disabled={deleting}
onClick={deleteRound}
className="inline-flex min-h-11 items-center gap-1.5 rounded-xl border border-dashed border-border bg-card px-3 text-sm font-semibold text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive disabled:opacity-60"
>
<Trash2 aria-hidden="true" className="size-4" />
{deleting ? "Sletter…" : "Slett runde"}
</button>
)}
</div>
{showEditRound && isOwnerViewer && (
<EditRoundPanel round={round} onPatch={patchRound} onClose={() => setShowEditRound(false)} />
)}
</div>
{/* Spillerliste (2026-07-26, erstatter tidligere horisontal
fane-rad) -- vertikal, ett kort per spiller. Utslag/HCP/
statistikknivå + "Rediger" ligger INNI hvert kort (samme
kort er også selve velg-aktiv-spiller-trykkflaten), i stedet
for en egen rad under listen som kun gjaldt aktiv spiller. */}
<PlayerList
roundId={roundId}
players={players}
activePlayerId={activePlayer.id}
onSelect={setActivePlayerId}
onRemove={removeGuest}
onAdd={() => setShowAddGuest((v) => !v)}
addOpen={showAddGuest}
readOnly={readOnly}
canManage={isOwnerViewer}
editingPlayerId={editingPlayerId}
onToggleEdit={(id) => setEditingPlayerId((prev) => (prev === id ? null : id))}
onPatchParticipant={patchParticipant}
onAddGuest={addGuest}
onAddSearched={addSearchedParticipant}
/>
{/* Flyttet HIT (2026-07-26, samme redesign-runde) -- lå tidligere
selve scoringssiden (først øverst, deretter lenger ned),
men er detaljert stats-innsyn (fairway/innspill/kølle-
fordeling for VALGT spiller), ikke selve registrerings-
oppgaven. Hver spillers rad i scoringslisten viser allerede
løpende til-par, dette er rendyrket "se mer detaljer"
-- hører hjemme i administrasjons-fanen, ikke i veien for
score-registreringen. */}
{!completed && <ScoreSoFar holes={apiHoles} order={holeOrder} />}
</>
)}
{pageTab === "score" && (
<>
{/* Completed summary banner */} {/* Completed summary banner */}
{completed && ( {completed && (
<CompletedBanner <CompletedBanner
@ -772,35 +841,6 @@ export function RoundDetail({ roundId }: { roundId: string }) {
/> />
)} )}
{/* Spillerliste (2026-07-26, erstatter tidligere horisontal fane-rad)
-- vertikal, ett kort per spiller. Utslag/HCP/statistikknivå +
"Rediger" ligger INNI hvert kort (samme kort er også selve
velg-aktiv-spiller-trykkflaten), i stedet for en egen rad under
listen som kun gjaldt aktiv spiller. */}
<PlayerList
roundId={roundId}
players={players}
activePlayerId={activePlayer.id}
onSelect={setActivePlayerId}
onRemove={removeGuest}
onAdd={() => setShowAddGuest((v) => !v)}
addOpen={showAddGuest}
readOnly={readOnly}
canManage={isOwnerViewer}
editingPlayerId={editingPlayerId}
onToggleEdit={(id) => setEditingPlayerId((prev) => (prev === id ? null : id))}
onPatchParticipant={patchParticipant}
onAddGuest={addGuest}
onAddSearched={addSearchedParticipant}
/>
{/* Skjules når runden er fullført (2026-07-25, rapportert av bruker
som "veldig mye dobbel informasjon") -- CompletedBanner sin
"Se full rundestatistikk"-lenke dekker akkurat det samme,
langt grundigere. Fortsatt nyttig som live fremdriftsoversikt
mens runden pågår. */}
{!completed && <ScoreSoFar holes={apiHoles} order={holeOrder} />}
{/* Hole navigation */} {/* Hole navigation */}
{holes.length === 0 ? ( {holes.length === 0 ? (
<div className="mt-5 flex justify-center py-8"> <div className="mt-5 flex justify-center py-8">
@ -926,7 +966,8 @@ export function RoundDetail({ roundId }: { roundId: string }) {
)} )}
</> </>
)} )}
</>
)}
</main> </main>
{/* Skjermovertagende scoringsveiviser (2026-07-26) -- rendres HELT {/* Skjermovertagende scoringsveiviser (2026-07-26) -- rendres HELT

View file

@ -0,0 +1,167 @@
# TeeCup — skjerm-spec: Scorekort + Score-entry
> **Status: PRESKRIPTIV (mål, ikke beskrivelse).** Dette dokumentet er
> bevisst det motsatte av `DESIGN_SYSTEM.md`: der DS beskriver hva som ER
> i koden, beskriver dette hva skjermene SKAL være. Det gir repoet et tak
> å sikte mot, ikke bare et gulv å reprodusere.
>
> **Til Claude Code:** behandle token- og prop-navn her som *intensjon*.
> Der de avviker fra faktisk `globals.css` / `components/*.tsx`, vinner
> kildekoden — avstem, ikke gjett. Alt visuelt skal fortsatt gå gjennom
> tokenene og komponentene i `DESIGN_SYSTEM.md`; dette dokumentet endrer
> *komposisjon og tetthet*, ikke fargespråket.
>
> Grunnlaget er en gjennomgang av fem etablerte golf-apper (Golf GameBook,
> Golf Pad, Golfshot, Hole 19, 18Birdies). Reglene under er der de fem
> konvergerer — altså «slik løser kategorien det», ikke smak.
---
## 0. Prinsipper (gjelder begge skjermene)
Fire ting skiller de fem appene fra dagens TeeCup-skjerm. Alt under er
utledet av disse:
1. **Tetthet framfor høflighet.** Ingen av de fem lar en flate stå halvtom.
Entry- og visningskontrollene fyller skjermen. Et enkelt kort øverst med
død luft under er ikke et mønster i kategorien — det er tegnet på en
nettside, ikke et verktøy. **Regel:** ingen score-flate skal ha mer enn
~1 skjermhøyde tom plass under fold. Har runden få hull ført, vis alle
18 (kommende hull grået ut), ikke whitespace.
2. **Ett aksent-poeng på nøytralt underlag.** Alle fem bruker et nesten
monokromt underlag + ÉN aksent (blå/grønn/teal). TeeCup-score-skjermen
leser som grønt-på-grønt fordi sirkler, faner, hull-velger og knapper
deler samme grønn. **Regel:** `primary` (grønn) reserveres for aktiv
tilstand + under-par/positivt. `brand-orange` er kontrast-polen
(over par). Alt annet er nøytralt: `bg-card`, `bg-muted`,
`border-border`, `muted-foreground`. Hvis to naboelementer begge er
grønne uten at begge betyr «aktiv/positiv», er ett av dem feil.
3. **Tall vises alltid med term og/eller til-par — aldri farge alene.**
Alle fem viser Eagle/Birdie/Par/Bogey og/eller +N/N/E ved siden av
tallet. Dette er allerede din `§Golfscore-språket`; håndhev den. En bar
grønn sirkel med råtall (dagens skjerm) er det ene mønsteret ingen av de
fem bruker.
4. **Verktøy, ikke nettside.** Hard, tydelig header; klare skiller mellom
rader/kolonner; kraftig `font-extrabold tabular-nums` på resultat-tall.
Myke, like avrundede kort overalt = generisk.
---
## 1. Scorekort-skjerm (score-VISNING)
**Mål:** erstatt ett-hull-kortet med et tett, scrollbart scorekort-grid.
Dette er skjermens største enkeltløft.
### Layout
- Container: `mx-auto w-full max-w-xl` (per DS for scorekort-visninger).
- **Sticky header** (`sticky top-0 z-10 border-b border-border
bg-background/95 backdrop-blur`): banenavn, dato, evt. format
(Slag/Stableford). Ikke et stort kort — en tett topplinje.
- **Grid, ikke kort-stabel.** Én container
(`rounded-2xl border border-border overflow-hidden`) — DS sier eksplisitt
at sammenhengende lister skal være én container med `divide-y`, ikke
separate kort med mellomrom.
- **Spillere = rader.** Venstre kolonne er *sticky* (`sticky left-0`):
fullt navn (roster-kontekst → fullt navn, ikke fornavn) + HCP under, i
`text-sm muted-foreground`. Fast rad-høyde (`min-h-[Npx]` + `truncate`)
så rader med ulik navnelengde blir like høye (DS-mønsteret fra
2026-07-26).
- **Hull = kolonner**, horisontalt scrollbare. Kolonnetopp:
hull-nr (`font-bold`), Par og evt. HCP-indeks under i
`text-sm muted-foreground`. Aktivt/valgt hull markeres med `bg-primary`
på kolonnetoppen (én grønn, ikke grønn overalt).
- **Celle** = score for (spiller × hull): brutto som hovedtall
(`font-extrabold tabular-nums`) med net som liten senket subscript.
Score-merket følger `§Golfscore-språket` UBRYTELIG:
- under par → sirkel (`rounded-full`)
- over par → firkant (`rounded-[4px]`)
- E → ren tekst, ingen ramme
- eagle+/dobbel bogey+/leder → *fylt* (`bg-primary`/`bg-brand-orange`),
samme form, kun fyllingen endres. Vanlig = `border-2` + `/10`-tint.
- **Sum-kolonner:** Ut / Inn / Total i en høyre *sticky* kolonne
(`sticky right-0`), `tabular-nums font-extrabold`, til-par som E/+N/N.
- **Fyll flaten:** vis alle 18 hull-kolonner alltid. Uspilte hull =
tomme celler på nøytralt underlag, ikke utelatt.
### Farge
Underlaget er nøytralt (`bg-card`). Grønn (`primary`) opptrer kun i:
aktivt hulls kolonnetopp, under-par-merker, og lederens sum. Over-par-
merker bruker firkant-form + `brand-orange`-tint. Ingenting annet er grønt.
### Interaksjon
Tapp en celle (eller en rad-header) → åpne score-entry-overlegget (§2) for
det hullet. Overgang, ikke ny side.
---
## 2. Score-entry-skjerm (per hull)
**Mål:** tett, fokusert entry som fyller skjermen — komponentene finnes
allerede (`NumberPicker`, `Stepper`, `ChoiceRow`, `DirectionCross`); dette
er ren komposisjon.
### Struktur — DS' fullskjerm-overlegg
`fixed inset-0 z-50 flex flex-col bg-background`, tredelt:
**`header` (shrink-0)**
- Hull · Par · HCP-indeks, og «til par så langt» (`tabular-nums`, E/+N/N).
- Lukk-knapp, `size-11`, ikon + `aria-label`.
- Aktiv spillers navn + HCP (roster-kontekst → fullt navn).
**`main` (`flex-1 overflow-y-auto`)** — i denne rekkefølgen, alt synlig med
minst mulig scroll:
1. **Score**`NumberPicker` (eksisterende): 3-kol grid, `h-16`, valgt =
`bg-primary`, term-caption relativt til hullets par
(Albatross/Eagle/Birdie/Par/Bogey/Dobbel bogey). «10+»-utvidelse
beholdes.
2. **Putter**`Stepper` (+/, `size-11`).
3. **Fairway**`ChoiceRow` eller `DirectionCross`: Bom venstre / Treff /
Bom høyre. Alltid tekstlabel (DS: aldri ikon alene).
4. **Green (GIR)**`DirectionCross` «Bommet green? Hvor» (Lang/Kort/
Venstre/Høyre + treff i sentrum), eller `ChoiceRow` hvis enklere.
5. **Bunkerslag / Straffeslag / Chip**`Stepper` hver.
6. **Sand save / Up & down** — toggles (pille, `size-11`-treffflate).
**`footer` (shrink-0)** — handling-rad:
- Sekundær «Forrige» (`bg-muted`/nøytral) + primær «Neste»/«Ferdig»
(`bg-primary`), begge full bredde-andel, `min-h-12`.
### Multiplayer
Behold **per-spiller fullskjerm-fokus** (GameBook-mønsteret) framfor inline-
rader — det matcher størrelsen på `NumberPicker` og tilgjengelighetsgulvet
bedre enn tette inline-steppere. «Neste» går til neste spiller på samme
hull, deretter neste hull. (18Birdies' inline-alle-spillere er et alternativ
å vurdere senere, men krever mindre trykkflater — sekundært valg.)
### Tetthet
Score + Putter + Fairway skal være synlige uten scroll på en vanlig telefon.
Resten scroller i `main`. Ingen halvtom skjerm.
---
## 3. Compliance-pass (kjør FØR nybygg)
Dette er avvik mellom `DESIGN_SYSTEM.md` og faktisk kode som allerede er
synlige i skjermbildene. Fiks dem først — de gir størst effekt per innsats:
- [ ] **`ScoreMark` brukes på ALLE score-visninger.** Auditér
`round-scorecard.tsx` og score-visnings-skjermen: dagens grønne sirkler
med råtall bryter `§Golfscore-språket`. Bogey og dobbel bogey skal IKKE se
like ut, og over-par skal være firkant.
- [ ] **Badge-dublett «Deg Deg».** `Badge`: `default` (fylt) = «Deg»,
`outline` = «Eier». Fjern at navnet «Deg» får en «Deg»-badge oppå seg.
- [ ] **`tabular-nums`** på alle score-/HCP-/sum-tall.
- [ ] **44px trykkgulv** (`min-h-11`) på alt trykkbart; `size-11`
ikon-only.
- [ ] **Farge aldri alene** — hver farge-bærende tilstand har også form
eller tekst.
---
## 4. Referanser
Legg GameBook- + de fire andre skjermdumpene i `design/references/` og pek
Claude Code på dem: «scorekort-skjermen skal matche tetthet og hierarki i
disse». Visuell referanse forankrer bedre enn prosa alene.