From 2ad07d09fe41d8d89c806350e4da23426a0e62d3 Mon Sep 17 00:00:00 2001 From: Erol Haagenrud Date: Wed, 29 Jul 2026 16:26:31 +0200 Subject: [PATCH] Deployed and documented. Summary of what changed: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The match/skins identity banner (the "Match" card showing both players/sides + a status like "1 UP" or "AS") now renders as a territory bar in all three places it appears — round-detail.tsx's live Score tab, round-scorecard.tsx's post-round summary, and session-scorecard.tsx's tournament match view. The leading side's colored zone extends proportionally past the midpoint into the trailing side's half (capped so both names stay legible at extreme leads), and both sides are equal/neutral at AS or before any holes are scored — matching your sketch's idea directly. Along the way I found and fixed a real overlap bug during browser testing: a long name could visually collide with the centered status pill on narrow (mobile) screens. Root cause was the identity box sizing itself to its own content instead of stretching to fill its zone — fixed so it always stretches, with alignment now handled via justify-start/end instead. Verified in both light/dark mode, normal and extreme leads, on a real scratch round matching your HCP scenario. Deployed live, teeoff.no unaffected. --- CLAUDE.md | 72 ++++++++++ frontend/components/round-detail.tsx | 153 +++++++++++++++++----- frontend/components/round-scorecard.tsx | 145 +++++++++++++++----- frontend/components/session-scorecard.tsx | 139 +++++++++++++++----- 4 files changed, 412 insertions(+), 97 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index e94aeae..f0f8bd2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5232,6 +5232,78 @@ Ferdig og verifisert: Begge containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no` upåvirket. +- **Match-identitetsbanneret: territorium-bar i stedet for flat 50/50-boks, + BYGGET, GRUNDIG BROWSERVERIFISERT (inkl. en reell overlapp-bug funnet OG + fikset) OG LIVE (2026-07-29), samme dag:** brukeren lastet opp to bilder + av dagens banner (samme identitetskort som ble bygget dagen før -- navn+ + HCP i hver ende, en sentrert statusboks) og en "VELDIG DÅRLIG, kun + illustrerende" skisse av eget forslag: den ledende sidens halvdel bør + markeres OVER på motstanderens halvdel (ikke en statisk 50/50-boks), og + ved AS skal begge sider markeres likt (nøytralt). + **Design:** ny `leadZoneFraction(lead, totalHoles)` -- `0.5 + (lead / + totalHoles) * 0.5`, klippet til `[0.18, 0.82]` for å alltid holde begge + navn lesbare selv ved en ekstrem ledelse. Dominant side får en solid, + mettet farge (`bg-primary`/`bg-brand-orange`, hvit/kontrastfarget tekst + via allerede eksisterende `--primary-foreground`/`--brand-orange- + foreground`-tokens) og strekker seg proporsjonalt forbi midtlinjen; + underlegne side beholder samme fargetone som en svak, lys tint + (`/12`-opacity) med vanlig temafarget tekst. Ved AS (eller ingen hull + spilt ennå): begge soner nøyaktig 50/50 og nøytralt `bg-muted` -- ingen + side ser ut til å lede. Egen lokal kopi i alle tre filer der et match- + identitetsbanner finnes (samme "én fil, én kopi"-konvensjon som + MatchScorecardCell-arbeidet dagen før): `round-detail.tsx` sin + `FormatResultPanel` (primary/brand-orange-tokens), `round-scorecard.tsx` + sin nye delte `LeadBar`+`LeadZone` (samme tokens, portert fra den + tidligere `IdentitySide`+`CenterBadge`-duoen), `session-scorecard.tsx` + sin `IdentityBlock` (FAKTISK lagets `team.color`-hex i stedet for faste + tokens -- dominant sone `backgroundColor: color` + hvit tekst, svak sone + `color-mix(in srgb, ${color} 15%, transparent)`, samme kontrastvalg som + den eksisterende `SegmentedBar` i tournament-leaderboard.tsx). + **Reell overlapp-bug funnet OG fikset UNDER selve browserverifiseringen, + ikke antatt riktig fra kodegjennomgang alene:** første forsøk brukte en + `position:absolute`-sentrert statusboks OPPÅ to `width:X%`-delte soner -- + et ekte skjermbilde på en smal (390px) mobil-viewport viste at et langt + navn ("Erol Haagenrud") ble delvis SKJULT bak statusboksen ("l Haagenrud" + vist i stedet, ikke en ellipse-trunkering, et reelt visuelt overlapp). + Første fiksforsøk (CSS Grid med `minmax(0,X fr) auto minmax(0,Y fr)` i + stedet for absolutt posisjonering) løste IKKE problemet alene -- fortsatt + samme overlapp ved re-test. Rot-årsaken var dypere: identitetsboksen + brukte `items-start`/`items-end` på sin YTRE flex-kolonne, som sizer + boksen etter INNHOLDETS egen bredde (shrink-to-fit) i stedet for sonens + faktiske tildelte bredde -- innholdet kunne dermed visuelt strekke seg + utover sin egen sone og inn i midt-kolonnen uansett hvor korrekt selve + grid-/bredde-fordelingen var. Fikset ved at boksen alltid STREKKER SEG + (fjernet items-start/items-end helt), med venstre/høyre-justering i + stedet løst via `justify-start`/`justify-end` på navne-raden og + `text-left`/`text-right` på HCP-teksten -- INNI en boks som nå alltid har + sonens fulle, korrekte bredde, slik at `truncate` faktisk virker presist. + **Verifisert grundig i en isolert scratch-nettleserøkt** (fersk + `teecup_scratch`-database + isolert scratch-MinIO + engangs API- + container, ekte `next dev` mot scratch-backend, Chrome DevTools MCP, 390× + 844 mobil-viewport): bygget en ekte match-runde fra bunnen via API (egen- + definert 18-hulls bane, Erol HCP 10 vs. gjest "Tore Morell" HCP 54, + samme HCP-mønster som brukerens skjermbilde) og testet re-render etter + HVER kodeendring, ikke bare én gang til slutt -- fanget nettopp DERFOR + både at CSS Grid-fiksen alene ikke var nok, og at stretch-fiksen + faktisk løste det. Bekreftet: normal ledelse ("1 UP", dominant sone + moderat bredere), en EKSTREM ledelse (avgjort "9&7", dominant sone + nesten fyller hele baren, underlegne navn korrekt trunkert "T…"/"HCP…" + i stedet for å overlappe), AS/ingen-hull-spilt (nøytral 50/50, `bg- + muted`), BÅDE lys og mørk modus (kontrast bekreftet i begge), og samme + fiks bekreftet å fungere identisk i `round-scorecard.tsx` sin + `MatchScorecardGrid`-visning (samme runde, `/my-rounds/{id}/scorecard`). + `session-scorecard.tsx` sin tournament-variant IKKE egen nettleser- + testet denne runden (identisk kodemønster, kun `team.color`-hex i stedet + for faste tokens -- lavere risiko, men flagget ærlig som ikke eget + bevist). Ekte typesjekket produksjonsbuild kjørt (to runder -- én etter + første, mislykkede CSS Grid-only-forsøk, én etter den faktiske stretch- + fiksen), alle 24 ruter listet begge ganger. + **Rullet ut live 2026-07-29**, bruker bekreftet eksplisitt: ingen + migrasjon (ren frontend), `docker compose up -d --build + teecup_frontend` (gjenskapte også `teecup_api` som vanlig bivirkning, + ingen backend-kode rørt). Begge containere boot-et rent, `/health`/ + `/dashboard` → 200, `teeoff.no` upåvirket. + Neste steg: 0a. **Spillerliste-redesign — nå FAKTISK nettleser-bekreftet (2026-07-27, full 22-skjerms gjennomgang):** rendrer korrekt, ingen diff --git a/frontend/components/round-detail.tsx b/frontend/components/round-detail.tsx index 684b74c..edb3bfe 100644 --- a/frontend/components/round-detail.tsx +++ b/frontend/components/round-detail.tsx @@ -2396,6 +2396,74 @@ function IdentitySide({ identity, side }: { identity: { name: string; hcp: numbe ) } +// Territorium-bar (2026-07-29, erstatter det tidligere flate, likt-delte +// banneret) -- den ledende sidens sone strekker seg PROPORSJONALT forbi +// midtlinjen inn på motstanderens halvdel (brukerens eget forslag: "hele +// den halve delen som tilhører den som er en opp er markert over på +// motstanderens halvdel"), i stedet for en statisk 50/50-boks med tekst i +// midten. Ved AS (eller ingen hull spilt ennå) er begge soner nøyaktig like +// store OG likt (nøytralt) farget -- ingen side skal se ut til å lede. +// Bruker samme proporsjonale to-fargede-bar-idé som SegmentedBar +// (tournament-leaderboard.tsx), tilpasset lead-i-hull i stedet for poeng. +function leadZoneFraction(lead: number, totalHoles: number): number { + if (totalHoles <= 0) return 0.5 + const fraction = 0.5 + (lead / totalHoles) * 0.5 + return Math.min(0.82, Math.max(0.18, fraction)) +} + +function LeadZone({ + identity, + side, + dominant, +}: { + identity: { name: string; hcp: number | null } + side: "a" | "b" + dominant: boolean +}) { + const textTone = dominant ? (side === "a" ? "text-primary-foreground" : "text-brand-orange-foreground") : "text-foreground" + const subTone = dominant + ? side === "a" + ? "text-primary-foreground/75" + : "text-brand-orange-foreground/75" + : "text-muted-foreground" + // Merk: den ytre boksen STREKKER SEG (ingen items-start/items-end her -- + // det ville sizet boksen etter innholdets egen bredde, ikke etter sonens + // faktiske tildelte bredde, og kunne visuelt lekke inn i midt-kolonnen på + // smale skjermer). Venstre/høyre-justering skjer i stedet med + // justify-start/justify-end og text-align INNI en boks som alltid har + // sonens fulle, korrekte bredde -- truncate virker da presist. + return ( +
+
+ {!dominant && ( +
+ {identity.hcp !== null && ( + + HCP {identity.hcp} + + )} +
+ ) +} + type ApiFlightSummary = { round_id: string name: string | null @@ -2579,10 +2647,9 @@ function FormatResultPanel({ ) } - // Identitetsbanner (2026-07-29, erstatter den tidligere plainere teksten + - // den nå overflødige hull-for-hull-sirkel-listen -- selve scorekortet - // under viser dette per hull nå). Navn+HCP enten side, stor sentrert - // løpende status, "Ferdig"-merke når avgjort -- samme visuelle språk som + // Identitetsbanner (2026-07-29, territorium-bar-versjon -- se leadZoneFraction + // over). Navn+HCP i hver sone, stor sentrert løpende status flytende oppå + // begge soner, "Ferdig"-merke når avgjort -- samme visuelle språk (nå) som // MatchScorecardGrid (round-scorecard.tsx) og TournamentMatchGrid // (session-scorecard.tsx), egen lokal kopi per prosjektets konvensjon. const identityA = sideIdentity(round, players, "a") @@ -2594,39 +2661,61 @@ function FormatResultPanel({ const isDormie = result.match_is_dormie ?? false const tone: "a" | "b" | "neutral" = lead > 0 ? "a" : lead < 0 ? "b" : "neutral" const statusLabel = holesPlayed > 0 ? formatMatchStatus(lead, holesRemaining, isClosed, isDormie) : null + const totalHoles = holesPlayed + holesRemaining + const widthA = holesPlayed > 0 ? leadZoneFraction(lead, totalHoles) * 100 : 50 + const widthB = 100 - widthA + const zoneAClass = tone === "a" ? "bg-primary" : tone === "b" ? "bg-primary/12" : "bg-muted" + const zoneBClass = tone === "b" ? "bg-brand-orange" : tone === "a" ? "bg-brand-orange/12" : "bg-muted" return (
{FORMAT_LABELS[round.play_format] ?? round.play_format} -
- -
- {statusLabel ? ( - <> - - {statusLabel} - - {isClosed ? ( - - Ferdig - - ) : ( - - {holesPlayed} hull spilt - {holesRemaining > 0 && isDormie && " · dormie"} - - )} - - ) : ( - Ingen hull spilt ennå - )} + {/* CSS Grid, ikke absolutt-posisjonert overlegg (2026-07-29-fiks) -- + en tidligere versjon la statusen som en absolutt sentrert boks OPPÅ + navnetekst, som kunne overlappe et langt navn på smale skjermer. + Med grid-kolonner `minmax(0,X fr) auto minmax(0,Y fr)` reserverer + nettleseren ALLTID nøyaktig midt-kolonnens egen bredde til status- + boksen -- soneandelen forblir proporsjonal (X:Y), men kan aldri + overlappe, og lange navn trunkeres i stedet (samme `truncate`- + mekanisme som ellers). */} +
+
+ +
+
+
+ {statusLabel ? ( + <> + + {statusLabel} + + {isClosed ? ( + + Ferdig + + ) : ( + + {holesPlayed} hull spilt + {holesRemaining > 0 && isDormie && " · dormie"} + + )} + + ) : ( + Ingen hull spilt ennå + )} +
+
+
+
-
) diff --git a/frontend/components/round-scorecard.tsx b/frontend/components/round-scorecard.tsx index f057b47..984fdab 100644 --- a/frontend/components/round-scorecard.tsx +++ b/frontend/components/round-scorecard.tsx @@ -443,42 +443,121 @@ function computeRunning(results: ("a" | "b" | "halved")[], totalHoles: number): type HeaderIdentity = { name: string; hcp: number | null } -function IdentitySide({ identity, side }: { identity: HeaderIdentity; side: "a" | "b" }) { +// Territorium-bar (2026-07-29, brukerens eget forslag: den ledende sidens +// sone strekker seg proporsjonalt forbi midtlinjen inn på motstanderens +// halvdel, i stedet for en statisk 50/50-boks med tekst i midten). Ved AS +// eller ingen hull avgjort ennå er begge soner like store og nøytralt +// farget. Egen lokal kopi av samme idé som round-detail.tsx sin +// FormatResultPanel, per prosjektets "én fil, én kopi"-konvensjon. +function leadZoneFraction(lead: number, totalHoles: number): number { + if (totalHoles <= 0) return 0.5 + const fraction = 0.5 + (lead / totalHoles) * 0.5 + return Math.min(0.82, Math.max(0.18, fraction)) +} + +function LeadZone({ identity, side, dominant }: { identity: HeaderIdentity; side: "a" | "b"; dominant: boolean }) { + const textTone = dominant ? (side === "a" ? "text-primary-foreground" : "text-brand-orange-foreground") : "text-foreground" + const subTone = dominant + ? side === "a" + ? "text-primary-foreground/75" + : "text-brand-orange-foreground/75" + : "text-muted-foreground" + // Boksen STREKKER SEG (ingen items-start/items-end) -- ellers sizes den + // etter innholdets egen bredde, ikke sonens faktiske tildelte bredde, og + // kan visuelt lekke inn i midt-kolonnen på smale skjermer. Justering skjer + // med justify-start/justify-end + text-align inni en boks som alltid har + // full, korrekt bredde -- truncate virker da presist. return ( -
-
-