Skjul flytende "Registrer score" når spillerlisten er nådd/passert

Ny playerListReached-tilstand skjuler den flytende knappen (og dens
spacer) fra #140 så snart spillerlisten når headerens høyde -- forblir
skjult resten av veien nedover, dukker opp igjen kun ved scroll tilbake
opp forbi listen. Første forsøk med IntersectionObserver+rootMargin var
feil (fyrer ikke på interne terskler mens allerede overlappende) -- byttet
til en scroll/resize-lytter med direkte getBoundingClientRect-sjekk. Se
CHANGELOG.md #141. Ikke rullet ut ennå.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Erol Haagenrud 2026-08-20 08:45:54 +02:00
parent 200d00cf5f
commit 76b306cb29
2 changed files with 101 additions and 13 deletions

View file

@ -13547,3 +13547,40 @@ Neste steg:
**Rullet ut 2026-08-20** -- bruker bekreftet, samlet med #139. Ren **Rullet ut 2026-08-20** -- bruker bekreftet, samlet med #139. Ren
frontend. `docker compose build teecup_frontend && up -d`, rene frontend. `docker compose build teecup_frontend && up -d`, rene
logger, 200 OK. logger, 200 OK.
141. **Flytende "Registrer score" hang igjen synlig etter at spillerlisten
var nådd, 2026-08-20.** Direkte oppfølging av #140 -- bruker viste
skjermbilde: selv etter å ha scrollet forbi spillerkortene (helt ned
til kommentarfeltet), sto den flytende "Registrer score ↓"-knappen
fortsatt og fløt over bunnen av skjermen, uten mening siden
spillerlisten allerede var passert (pilen peker "ned til spillerne",
men spillerne var nå OVER, ikke under). Bruker spurte om dette burde
V0-promptes -- Claude vurderte det til å være ren VIS/SKJUL-LOGIKK
rundt et allerede ferdig designet element (fra #140), ikke en ny
visuell flate, og løste det direkte.
`round-detail.tsx`: ny `playerListReached`-tilstand, sant når
spillerlistens toppkant har nådd/passert headerens høyde (112px,
samme referanse som `scroll-mt-28`). Knappen (og spaceren under den,
fra #140) vises nå kun når `mapExpanded && !playerListReached`.
**Feilslått første forsøk, funnet i scratch:** brukte først
`IntersectionObserver` med en krympet `rootMargin` for å fange
112px-krysningen. Viste seg feil -- med standard `threshold: [0]`
fyrer observatøren KUN ved 0%->0%-kryssinger (elementet helt inn/ut av
roten), ikke ved at `boundingClientRect.top` passerer en vilkårlig
linje MENS elementet allerede overlapper roten. Spillerlisten ble
dermed hengende fast på "ikke nådd" gjennom resten av scrollingen,
identisk med bugen selv om koden så riktig ut. Erstattet med en vanlig
`scroll`/`resize`-lytter (passive) som leser
`getBoundingClientRect().top` direkte -- korrekt, kontinuerlig
oppdatering.
**Verifisert i scratch** (samme mock-oppsett som #139/#140): utvidet
banekartet, trykket flytende "Registrer score" -- knappen forsvant nå
korrekt idet spillerkort 1 landet synlig, ingen tomt mellomrom stod
igjen. Scrollet helt ned til kommentarfeltet -- knappen forble skjult
(ikke lenger den opprinnelige bugen). Scrollet tilbake opp forbi
spillerlisten -- knappen dukket korrekt opp igjen. `tsc --noEmit` rent.
**Ikke rullet ut ennå** -- venter på bekreftelse fra bruker.

View file

@ -6719,6 +6719,43 @@ function PlayerHoleCards({
const [mapExpanded, setMapExpanded] = useState(false) const [mapExpanded, setMapExpanded] = useState(false)
const [distanceAvailable, setDistanceAvailable] = useState(false) const [distanceAvailable, setDistanceAvailable] = useState(false)
const playerListRef = useRef<HTMLUListElement>(null) const playerListRef = useRef<HTMLUListElement>(null)
// Flytende "Registrer score" (2026-08-20) skal kun vises mens
// spillerlisten fortsatt er UTENFOR synlig område -- brukerfunn: den
// hang igjen synlig (og dekket delvis over spillerkort 1) selv etter at
// brukeren allerede hadde scrollet forbi/til spillerlisten, der den ikke
// lenger gir mening ("Registrer score ↓" når du allerede ser
// spillerne). "Reached" = listens toppkant har nådd/passert headerens
// faktiske høyde (samme 112px som scroll-mt-28 andre steder i denne
// filen) -- forblir sant BÅDE mens listen vises OG etter at brukeren
// har scrollet helt forbi den (listen ligger da over synlig område,
// `top` er negativ). Kun scroll TILBAKE OPP forbi listen (top blir
// igjen større enn 112) gjør at knappen skal dukke opp igjen.
//
// Første forsøk brukte IntersectionObserver med en krympet rootMargin
// -- viste seg FEIL i scratch-verifisering: med standard threshold=[0]
// fyrer den kun ved 0%->>0%-kryssinger (element helt inn/ut av roten),
// ikke ved at `boundingClientRect.top` passerer en INTERN linje midt i
// et allerede-overlappende element. Listen ble dermed hengende på
// "ikke nådd" gjennom hele resten av scrollingen forbi 112px-grensen.
// Vanlig scroll-lytter med direkte `getBoundingClientRect()`-sjekk gir
// korrekt, kontinuerlig oppdatering.
const [playerListReached, setPlayerListReached] = useState(false)
useEffect(() => {
if (!mapExpanded) return
const HEADER_HEIGHT_PX = 112
function check() {
const el = playerListRef.current
if (!el) return
setPlayerListReached(el.getBoundingClientRect().top <= HEADER_HEIGHT_PX)
}
check()
window.addEventListener("scroll", check, { passive: true })
window.addEventListener("resize", check)
return () => {
window.removeEventListener("scroll", check)
window.removeEventListener("resize", check)
}
}, [mapExpanded])
const holeMeta: Record<number, { par: number; index: number }> = {} const holeMeta: Record<number, { par: number; index: number }> = {}
for (const list of Object.values(holesByParticipant)) { for (const list of Object.values(holesByParticipant)) {
for (const h of list) { for (const h of list) {
@ -7102,23 +7139,34 @@ function PlayerHoleCards({
/> />
)} )}
{/* Flytende "Registrer score" (V0-eksport, 2026-08-20) -- kun mens {/* Flytende "Registrer score" (V0-eksport, 2026-08-20) -- mens
banekartet er utvidet. `fixed` posisjonerer mot selve viewporten banekartet er utvidet OG spillerlisten fortsatt er utenfor synlig
(ingen ancestor her setter transform/filter, det er trygt), område (se `playerListReached` over -- uten denne betingelsen hang
gradient-scrim + skygge løfter den visuelt over innholdet under, knappen igjen synlig, og dekket delvis over spillerkort 1, selv
`env(safe-area-inset-bottom)` unngår at den klemmes helt inntil etter at brukeren allerede hadde scrollet dit). `fixed`
kanten telefoner med gest-navigasjon. `pointer-events-none` posisjonerer mot selve viewporten (ingen ancestor her setter
selve scrim-sonen når skjult/i overgang, slik at den aldri transform/filter, det er trygt), gradient-scrim + skygge
blokkerer klikk innhold bak. */} løfter den visuelt over innholdet under, `env(safe-area-inset-
bottom)` unngår at den klemmes helt inntil kanten på telefoner
med gest-navigasjon. `pointer-events-none` selve scrim-sonen
når skjult/i overgang, slik at den aldri blokkerer klikk
innhold bak. */}
<div <div
aria-hidden={!mapExpanded} aria-hidden={!mapExpanded || playerListReached}
className={cn( className={cn(
"fixed inset-x-0 bottom-0 z-30 px-5 pb-[calc(0.875rem+env(safe-area-inset-bottom))] pt-8 transition-all duration-300 ease-out", "fixed inset-x-0 bottom-0 z-30 px-5 pb-[calc(0.875rem+env(safe-area-inset-bottom))] pt-8 transition-all duration-300 ease-out",
"bg-gradient-to-t from-background via-background/90 to-transparent", "bg-gradient-to-t from-background via-background/90 to-transparent",
mapExpanded ? "translate-y-0 opacity-100" : "pointer-events-none translate-y-full opacity-0", mapExpanded && !playerListReached
? "translate-y-0 opacity-100"
: "pointer-events-none translate-y-full opacity-0",
)} )}
> >
<div className={cn("mx-auto w-full max-w-3xl", mapExpanded ? "pointer-events-auto" : "pointer-events-none")}> <div
className={cn(
"mx-auto w-full max-w-3xl",
mapExpanded && !playerListReached ? "pointer-events-auto" : "pointer-events-none",
)}
>
<div className="rounded-2xl shadow-xl shadow-black/25"> <div className="rounded-2xl shadow-xl shadow-black/25">
<RegisterScoreButton <RegisterScoreButton
onClick={() => playerListRef.current?.scrollIntoView({ behavior: "smooth", block: "start" })} onClick={() => playerListRef.current?.scrollIntoView({ behavior: "smooth", block: "start" })}
@ -7127,8 +7175,11 @@ function PlayerHoleCards({
</div> </div>
</div> </div>
{/* Gir siste innhold på siden rom til å scrolle klar av den flytende {/* Gir siste innhold på siden rom til å scrolle klar av den flytende
knappen i stedet for å bli permanent skjult bak den. */} knappen i stedet for å bli permanent skjult bak den -- samme
{mapExpanded && <div aria-hidden="true" className="h-20" />} betingelse som selve knappen (2026-08-20-rettelsen), ellers ble
det stående et tomt, uforklart mellomrom selv etter at knappen
alt var skjult. */}
{mapExpanded && !playerListReached && <div aria-hidden="true" className="h-20" />}
</div> </div>
) )
} }