"use client" // Hull-diagram v2 (2026-08-17, ADR-084) -- kategoriske venstre/senter/ // høyre-akser i stedet for kontinuerlig sideveis-forskyvning (ADR-083). // Se ADR-084: kontinuerlig `crossMeters` ga for liten synlig forskjell // på ekte banedata (Tjøme hull 18 -- bunker+vann havnet begge nesten på // senterlinjen selv om de faktisk ligger på ulik side). Banevalget // kommer nå fra `hazards[].lane`, satt av datalaget i // hole-target-distance.tsx basert på `side_fairway` -- IKKE utledet her. // Ren kontrollert komponent, ingen egen GPS-/geometrilogikk. import { MapPin, TriangleAlert } from "lucide-react" import { cn } from "@/lib/utils" export type HazardKind = "sand" | "water" | "generic" export type DiagramHazard = { key: string kind: HazardKind label: string lane: "left" | "center" | "right" /** 0 = ved tee, 100 = ved green. */ alongPercent: number /** Avstand FRA SPILLEREN, vises rett under ikonet. Alltid satt. */ below: number /** Avstand til bakkant, vises rett over ikonet -- KUN satt når * hindringen er et ekte forkant+bakkant-par (se hazard_group i * datalaget). `null` = enkeltpunkt, ingen tekst over ikonet. */ above: number | null } type Props = { distances: { front: number; middle: number; back: number } playerPosition: { alongPercent: number } hazards: DiagramHazard[] size?: "compact" | "full" className?: string } const TRACK_TOP = 12 // where alongPercent === 100 lands (leaves room for the flag) const TRACK_BOTTOM = 88 // where alongPercent === 0 lands (leaves room for the tee dot) const LABEL_MIN_GAP_PCT = 16 // hindringer i samme bane trenger mer luft nå (ikon + to tall) function clamp(n: number, lo: number, hi: number) { return Math.min(hi, Math.max(lo, n)) } function alongToTopPct(alongPercent: number) { const a = clamp(alongPercent, 0, 100) return TRACK_TOP + ((100 - a) / 100) * (TRACK_BOTTOM - TRACK_TOP) } function formatMeters(m: number) { return `${Math.round(m)} m` } function HazardIcon({ kind }: { kind: HazardKind }) { if (kind === "sand") { return } if (kind === "water") { return } return } /** Nudger ikoner i SAMME bane fra øverst til nederst hvis de er for * nære vertikalt -- samme kollisjons-mønster som før, men kjørt per * bane siden hindringer nå er spredt over tre baner. */ function layoutLane(hazards: DiagramHazard[]) { const sorted = [...hazards].sort((a, b) => alongToTopPct(a.alongPercent) - alongToTopPct(b.alongPercent)) const placed: (DiagramHazard & { topPct: number })[] = [] let lastTop = Number.NEGATIVE_INFINITY for (const h of sorted) { const desired = alongToTopPct(h.alongPercent) const topPct = Math.max(desired, lastTop + LABEL_MIN_GAP_PCT) placed.push({ ...h, topPct }) lastTop = topPct } return placed } function HazardMarker({ hazard, topPct }: { hazard: DiagramHazard; topPct: number }) { return ( ) } export function HoleDiagram({ distances, playerPosition, hazards, size = "full", className }: Props) { if (size === "compact") { return (
{hazards.length > 0 && ( )}
) } const leftHazards = layoutLane(hazards.filter((h) => h.lane === "left")) const centerHazards = layoutLane(hazards.filter((h) => h.lane === "center")) const rightHazards = layoutLane(hazards.filter((h) => h.lane === "right")) const playerTop = alongToTopPct(playerPosition.alongPercent) return (
{/* Venstre bane */}
{leftHazards.map((h) => ( ))}
{/* Midtbane -- eneste bane med green/tee/spiller, alltid faste ankre her */}
{centerHazards.map((h) => ( ))}
{/* Høyre bane */}
{rightHazards.map((h) => ( ))}
{hazards.length > 0 && (

Hindringer

    {hazards.flatMap((h) => h.above !== null ? [ , , ] : [], )}
)}
) } function HazardRow({ label, value }: { label: string; value: number }) { return (
  • ) } function BigNumber({ label, value, emphasized }: { label: string; value: number; emphasized?: boolean }) { return (
    {Math.round(value)} {label}
    ) } function CompactNumber({ label, value, emphasized }: { label: string; value: number; emphasized?: boolean }) { return (
    {Math.round(value)} {label}
    ) } function buildDiagramAria( distances: Props["distances"], hazards: DiagramHazard[], player: Props["playerPosition"], ) { void player const base = `Baneskisse. Senter ${Math.round(distances.middle)} meter til green. Din posisjon markert.` if (hazards.length === 0) return `${base} Ingen hindringer.` const list = hazards .map((h) => h.above !== null ? `${h.label} forkant ${Math.round(h.below)} meter, bakkant ${Math.round(h.above)} meter` : `${h.label} ${Math.round(h.below)} meter`, ) .join(", ") return `${base} Hindringer: ${list}.` }