teecup/DESIGN_SYSTEM.md
Erol Haagenrud d395f86753 Update Todos
Skrive migrasjon 052 (guest_first_name/guest_last_name + backfill)

Bygge backend: splitt navn i ParticipantCreate/Update, auto-synk guest_name

Bygge backend: GET known-guest-oppslag (e-post-basert, scoped til egen bruker)

Bygge backend: retroaktiv e-post-kobling ved innlogging (auth.py)

Bygge backend: HTML-e-post-støtte i email.py (multipart/alternative)

Bygge backend: scorekort/statistikk-e-post ved complete_round (individuell + delt-ball)

Frontend: e-post-felt + navnesplitt + autofyll fra søk i AddGuestForm

Frontend: "vi kjenner igjen denne e-posten"-forslag i AddGuestForm

Scratch-verifisere hele kjeden (migrasjon+backend+frontend+e-post) end-to-end

Kjøre migrasjon mot ekte teecup_db og rulle ut (etter bekreftelse)
Alt er bygget og scratch-verifisert. Før jeg ruller ut mot ekte teecup_db, her er planen — trenger din bekreftelse per sikkerhetsregelen:

Migrasjon 052_guest_name_split.sql (rent additiv, ingen sletting):

ALTER TABLE round_participant ADD COLUMN guest_first_name text; + guest_last_name text
Backfiller eksisterende guest_name-rader ved samme "første ord/resten"-heuristikk som frontend bruker til autofyll
Deretter: docker compose up -d --build teecup_api teecup_frontend

Hva som er bygget:

E-post som valgfritt felt i "Legg til uten konto (gjest)" (fantes i backend fra før, manglet i selve skjemaet)
Navn splittet i for-/etternavn (etternavn valgfritt)
Retroaktiv kobling: spiller runder som gjest før du har konto → runden dukker opp i "Egne runder" når du logger inn med samme e-post, men teller ikke automatisk mot faktisk HCP (må bekreftes selv — unngår at en fremmed kan påvirke HCP-en din uten samtykke)
E-post med scorekort + statistikk + invitasjon sendes automatisk når runden fullføres, til enhver gjest med registrert e-post — første HTML-e-post i appen
Autofyll fra søkefeltet inn i gjesteskjemaet
"Vi kjenner igjen denne e-posten"-forslag, scoped til dine egne tidligere gjester (ikke globalt — unngår en personvernlekkasje)
2026-08-03 06:24:57 +02:00

17 KiB
Raw Blame History

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.

2026-07-29: dette dokumentet ble revidert og formalisert basert på "alternativ designinstruks.md" (brukerens eget forslag, forsøkt først på dashbordet, deretter godkjent som ny fasit). Innholdet under ER derfor nå den gjeldende regelen — men er ikke nødvendigvis rullet ut på ALLE skjermer ennå (se CHANGELOG.md for hvor langt konsistens-sjekken har kommet). Rett opportunistisk opp eldre skjermer mot dette dokumentet når de likevel røres, samme mønster som tilgjengelighetsregelen over.

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, og ALDRI hardkodede Tailwind-fargeklasser som slate-500/gray-100 i ny kode. Dette er ikke bare en stilregel: hardkodede farger bryter den automatiske mørk/lys-tema-logikken (se "Lyst og mørkt tema" under), som ellers virker helt av seg selv så lenge token-klassene brukes.

Token Lys modus Rolle
background / foreground ren hvit (identisk med card) / mørk grå sideflate / brødtekst
card / card-foreground hvit / mørk 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å sekundær vekt, sjeldent brukt alene
muted / muted-foreground lys grå / dempet bakgrunnstoner, sekundær tekst/bildetekst
accent / accent-foreground lys grå hover-tilstand på nøytrale flater
destructive rød-oransje slett/fare-handlinger
border / input lys grå 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-1chart-6 grønn→blå→gul→oransje→rød-skala statistikk-/fordelingsvisualiseringer (ADR-033), ALDRI brukt for kategoriske UI-tilstander

2026-08-02: alle nøytrale tokens (background/muted/accent/ border/secondary m.fl.) hadde tidligere en svak grønn hue (130-145) iblandet selv når de skulle være "nøytral grå" — dette ga et vedvarende, utilsiktet grønt skjær på nesten hver bakgrunn, hover-tilstand og kant i hele appen (brukeren opplevde appen som "stuck i det lysegrønne utseendet"). Rettet til ekte nøytral grå (chroma 0) på tvers av lys/ mørk/media-query-blokkene. primary er UENDRET og fortsatt den eneste tokenen som skal lese som grønn — reservert til valgt/aktiv tilstand og primærhandling, se semantikk-listen under.

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 (revidert 2026-07-29): ved text-base (16px) og god kontrast (text-foreground) er font-normal helt akseptabelt og FORETRUKKET for brødtekst — unngår visuell støy av at alt roper likt. Hierarki skapes primært med STØRRELSE og FARGE (kontrast), ikke bare vekt. text-sm eller mindre (bildetekst/metadata) bør derimot ofte gis font-medium for å kompensere for den mindre størrelsen — aldri font-normal under text-base. Overskrifter/tall/primærhandlinger beholder font-bold/font-extrabold som før.
  • 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

  • 8-punkts rutenett (2026-07-29): marger, padding og gap følger 8-gangen (p-2=8px, p-4=16px, p-6=24px, p-8=32px) for forutsigbar rytme. Halv-steg (gap-1.5, py-2.5 osv.) unngås i nytt arbeid — brukes kun der et etablert mønster (f.eks. shadcn sine egne primitiver) allerede definerer det.
  • Kort får tydeligere løft (2026-07-29): shadow-md shadow-black/8 (opp fra shadow-sm shadow-black/5) på kort/paneler i lyst tema — mer synlig separasjon fra bakgrunnen. Se round-card.tsx/ tournament-card.tsx/install-prompt.tsx/dashboard.tsx for det gjeldende mønsteret; eldre skjermer som fortsatt har shadow-sm shadow-black/5 bør rettes opp neste gang de røres.
  • 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-12h-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.

Interaksjon (2026-07-29)

  • Alle interaktive elementer skal ha en definert :hover- OG :active-tilstand — :active (f.eks. active:scale-[0.98] på kort/ knapper, active:opacity-70 på lenker/ikon-knapper) gir umiddelbar, taktil bekreftelse på at trykket faktisk ble registrert. shadcn sin Button-primitiv har allerede active:translate-y-px innebygd; egne håndbygde trykkflater (kort, <Link>-baserte rader) må legge det til eksplisitt siden de ikke arver primitivens klasser.
  • disabled bruker disabled:opacity-50 disabled:pointer-events-none (allerede standard i Input/Button).
  • Bevegelse: transition-all duration-200 ease-in-out for state- endringer på knapper/kort — kjapt og "snappy", ikke en treg nettside- følelse. Ingen animasjon/fade lenger enn 300ms.

Skjemaer og inndata (mobil-først)

  • Tekst i <input>/<select>/<textarea> er ALLTID minst text-base (16px) — mindre utløser tvungen auto-zoom på iOS Safari. shadcn sin Input-primitiv løser dette allerede (text-base md:text-sm — større på mobil, kan trappes ned på desktop der zoom ikke er et problem).
  • Riktig inputMode for numeriske felt ("numeric"/"decimal" for HCP/ slag/score) slik at riktig mobiltastatur vises med det samme — se tallvelgerne i scoring-flyten for eksisterende bruk.
  • Fokus: focus-visible:ring-2 focus-visible:ring-ring (aldri nettleser-standard outline).

Safe areas og skjermkanter (2026-07-29)

Elementer festet til topp (sticky header) eller bunn (footer-handlinger) skal ikke havne bak en fysisk notch/Dynamic Island eller hjem- indikatoren. Praktisk løsning: pt-[env(safe-area-inset-top)] på selve sticky-headeren, pb-[max(4rem,env(safe-area-inset-bottom))] på siste innholdscontainer — env() resolver til 0 på enheter uten notch, så dette er trygt å legge til overalt, ikke bare på skjermer der det faktisk trengs.

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 bryterinline-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-8size-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.