Fiks scroll-til-spillerliste for godt: mål header-høyden live, ikke hardkodet
Presis scratch-måling avdekket at den hardkodede 112px-antagelsen (arvet fra scroll-mt-28) var feil -- ekte headerhøyde var 141.5px for en runde med bane+utslag+dato+klokkeslett samtidig, siden headeren pakkes til flere linjer avhengig av innhold. Ny getHeaderHeight() måler live i stedet for å gjette. scrollToPlayerList() erstatter scrollIntoView med manuell beregning; playerListReached bruker samme terskel (delt SCROLL_GAP_PX-konstant) -- måtte rettes en gang til underveis da de to brukte ulike terskler. Se CHANGELOG.md #142. Ikke rullet ut ennå. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
76b306cb29
commit
116600cd3c
2 changed files with 89 additions and 12 deletions
39
CHANGELOG.md
39
CHANGELOG.md
|
|
@ -13584,3 +13584,42 @@ Neste steg:
|
|||
spillerlisten -- knappen dukket korrekt opp igjen. `tsc --noEmit` rent.
|
||||
|
||||
**Ikke rullet ut ennå** -- venter på bekreftelse fra bruker.
|
||||
|
||||
142. **Nei, ikke helt -- ekte målebasert oppfølging av #141,
|
||||
2026-08-20.** Bruker spurte direkte: scroller det nå faktisk langt
|
||||
nok ned til å vise HELE spillerkort 1? Presis scratch-måling (ikke
|
||||
skjermbilde-eyeballing) avdekket at svaret fortsatt var nei: det
|
||||
hardkodede `112px` (lånt fra `scroll-mt-28`, brukt både i CSS-en for
|
||||
scroll-hoppet og i #141 sin `playerListReached`-terskel) stemte ikke
|
||||
-- headerens EKTE høyde målte 141,5px for denne runden (bane + utslag
|
||||
+ dato + klokkeslett samtidig), ~30px mer enn antatt. Toppen av
|
||||
spillerkort 1 lå dermed fortsatt delvis bak headeren.
|
||||
|
||||
Rot-årsak: `RoundHeader` sin høyde er reelt DYNAMISK (rundenavn/
|
||||
utslag/dato/klokkeslett kan pakkes til flere linjer avhengig av
|
||||
runden) -- en NY hardkodet konstant (f.eks. 160px) ville bare flyttet
|
||||
samme sårbarhet til en annen runde med enda lenger innhold, ikke løst
|
||||
den. Derfor målt LIVE i stedet:
|
||||
|
||||
- Ny `getHeaderHeight()`-hjelper: `document.querySelector("header")
|
||||
?.getBoundingClientRect().height` (112 kun som aller siste
|
||||
nødfallback).
|
||||
- `scrollToPlayerList()` erstatter `scrollIntoView` + statisk
|
||||
`scroll-mt-28` -- regner selv ut nøyaktig mål-scrollposisjon med
|
||||
`getHeaderHeight()` + en delt `SCROLL_GAP_PX = 16`-konstant.
|
||||
- `playerListReached`-sjekken (#141) oppdatert til å bruke SAMME
|
||||
`getHeaderHeight() + SCROLL_GAP_PX`-terskel -- måtte rettes en gang
|
||||
til underveis i denne rundens scratch-verifisering: brukte først
|
||||
kun `getHeaderHeight()` uten gap-konstanten, som fikk knappen til å
|
||||
henge synlig selv etter et korrekt landet hopp (16px avvik mellom
|
||||
hvor scrollet faktisk landet og hvor "reached" ble regnet ut).
|
||||
|
||||
**Verifisert i scratch, presist (ikke bare skjermbilde):** målte
|
||||
`getBoundingClientRect()` for spillerkort 1 og headeren direkte via
|
||||
`evaluate_script` rett etter scroll-hoppet. Bekreftet numerisk:
|
||||
`cardTop: 157.5`, `headerBottom: 141.5`, `gapBelowHeader: 16`,
|
||||
`fullyVisible: true` (både over header OG innenfor viewport-bunnen).
|
||||
Bekreftet at den flytende knappen samtidig er `aria-hidden="true"`
|
||||
(korrekt skjult) i samme øyeblikk. `tsc --noEmit` rent.
|
||||
|
||||
**Ikke rullet ut ennå** -- venter på bekreftelse fra bruker.
|
||||
|
|
|
|||
|
|
@ -6634,6 +6634,26 @@ function sumForPlayer(list: ApiHole[] | undefined, numbers: number[]): { strokes
|
|||
// Trykk åpner SAMME eksisterende fullskjerm-veiviser som før (onOpenEntry)
|
||||
// -- selve registreringsvinduet er bevisst IKKE rørt av denne rundens
|
||||
// V0-prompt.
|
||||
// Målt LIVE, ikke en hardkodet konstant -- den sticky RoundHeader-en sin
|
||||
// høyde er reelt dynamisk (rundenavn/dato/klokkeslett kan pakkes til flere
|
||||
// linjer avhengig av runden), så en fast pixel-gjetning viste seg feil for
|
||||
// en runde med lengre innhold enn den opprinnelige gjetningen (112px) var
|
||||
// kalibrert mot (scratch-verifisering 2026-08-20: ekte høyde 141,5px for
|
||||
// en runde med bane+utslag+dato+klokkeslett samtidig). Brukt av BÅDE
|
||||
// scroll-hoppet til spillerlisten og `playerListReached`-sjekken under.
|
||||
function getHeaderHeight(): number {
|
||||
return document.querySelector("header")?.getBoundingClientRect().height ?? 112
|
||||
}
|
||||
|
||||
// Pusterom under headeren når spillerlisten scrolles til -- brukt for
|
||||
// BÅDE selve scroll-målposisjonen og "reached"-terskelen (se begge
|
||||
// under). Delt konstant med vilje: scroll-hoppet lander bevisst
|
||||
// `SCROLL_GAP_PX` under headeren (ikke flush), så "reached"-sjekken må
|
||||
// bruke SAMME avstand, ellers forblir den flytende knappen synlig etter
|
||||
// at hoppet har landet (funnet i scratch-verifisering 2026-08-20 -- de to
|
||||
// brukte ulike terskler).
|
||||
const SCROLL_GAP_PX = 16
|
||||
|
||||
// "Registrer score" -- samme knapp, to plasseringer (se PlayerHoleCards):
|
||||
// i normal side-flyt når banekartet er kollapset, flytende festet nederst
|
||||
// på skjermen når det er utvidet (V0-eksport, 2026-08-20, brukerønske --
|
||||
|
|
@ -6725,28 +6745,35 @@ function PlayerHoleCards({
|
|||
// 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.
|
||||
// faktiske høyde -- 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 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.
|
||||
// "ikke nådd" gjennom hele resten av scrollingen forbi grensen. Vanlig
|
||||
// scroll-lytter med direkte `getBoundingClientRect()`-sjekk gir korrekt,
|
||||
// kontinuerlig oppdatering.
|
||||
//
|
||||
// ANNET funn i scratch, samme runde: en HARDKODET grense (112px, lånt
|
||||
// fra `scroll-mt-28` andre steder i filen) var selv FEIL -- headeren
|
||||
// sin faktiske høyde målte 141.5px for denne runden, ikke 112. Headeren
|
||||
// sitt innhold (rundenavn/dato/klokkeslett) kan pakkes til flere linjer
|
||||
// avhengig av runden, så høyden er reelt DYNAMISK, ikke en konstant --
|
||||
// en ny hardkodet gjetning (f.eks. 160px) ville bare flyttet samme
|
||||
// sårbarhet til en annen runde med enda lenger innhold. Målt LIVE i
|
||||
// stedet, se `getHeaderHeight()` under.
|
||||
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)
|
||||
setPlayerListReached(el.getBoundingClientRect().top <= getHeaderHeight() + SCROLL_GAP_PX)
|
||||
}
|
||||
check()
|
||||
window.addEventListener("scroll", check, { passive: true })
|
||||
|
|
@ -6756,6 +6783,17 @@ function PlayerHoleCards({
|
|||
window.removeEventListener("resize", check)
|
||||
}
|
||||
}, [mapExpanded])
|
||||
// Manuell scroll (ikke `scrollIntoView` + CSS `scroll-margin-top`) --
|
||||
// samme begrunnelse som `getHeaderHeight()` over: en CSS-klasse som
|
||||
// `scroll-mt-28` kan ikke være dynamisk, og headerens høyde varierer
|
||||
// reelt med runde-innholdet. Regner ut nøyaktig mål-posisjon selv, med
|
||||
// et lite pusterom (16px) under headeren.
|
||||
function scrollToPlayerList() {
|
||||
const el = playerListRef.current
|
||||
if (!el) return
|
||||
const top = el.getBoundingClientRect().top + window.scrollY - getHeaderHeight() - SCROLL_GAP_PX
|
||||
window.scrollTo({ top, behavior: "smooth" })
|
||||
}
|
||||
const holeMeta: Record<number, { par: number; index: number }> = {}
|
||||
for (const list of Object.values(holesByParticipant)) {
|
||||
for (const h of list) {
|
||||
|
|
@ -6896,7 +6934,7 @@ function PlayerHoleCards({
|
|||
kunne dytte knappen helt utenfor synlig skjermbilde). */}
|
||||
{!mapExpanded && (
|
||||
<RegisterScoreButton
|
||||
onClick={() => playerListRef.current?.scrollIntoView({ behavior: "smooth", block: "start" })}
|
||||
onClick={scrollToPlayerList}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
@ -7169,7 +7207,7 @@ function PlayerHoleCards({
|
|||
>
|
||||
<div className="rounded-2xl shadow-xl shadow-black/25">
|
||||
<RegisterScoreButton
|
||||
onClick={() => playerListRef.current?.scrollIntoView({ behavior: "smooth", block: "start" })}
|
||||
onClick={scrollToPlayerList}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue