"use client" // V0-eksportert visning (2026-08-17, ADR-083) -- ren kontrollert // komponent, ingen egen GPS-/geometrilogikk. All geometri-utregning // (projectOntoAxis) bor i hole-target-distance.tsx, allerede bevist // riktig med enhetstester + scratch-verifisering FØR denne visningen // ble bestilt. Erstattet en midlertidig hånd-kodet stand-in med samme // props-kontrakt -- samme mønster som FlagPlantSheet/PlayerImportPanel. import { Flag, MapPin, TriangleAlert } from "lucide-react" import { cn } from "@/lib/utils" export type DiagramHazard = { label: string /** Live distance FROM THE PLAYER, always accurate. */ distanceMeters: number /** 0 = at tee, 100 = at green. May be <0 or >100. */ alongPercent: number /** Positive = right of the tee->green axis, negative = left. */ crossMeters: number } type Props = { distances: { front: number; middle: number; back: number } playerPosition: { alongPercent: number; crossMeters: number } /** Already sorted nearest-to-player first. */ hazards: DiagramHazard[] size?: "compact" | "full" className?: string } // --- Diagram geometry (percentages of the field box) --------------------- // Fixed endpoints: green stays at the top, tee stays at the bottom, always. 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) // Sideways scale: this many meters of cross offset maps to the max visual swing. const MAX_CROSS_METERS = 30 const MAX_OFFSET_PCT = 40 // ±40% of the field width, per spec // Label collision handling: minimum vertical gap between stacked labels. const LABEL_MIN_GAP_PCT = 11 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 crossToLeftPct(crossMeters: number) { const offset = clamp( (crossMeters / MAX_CROSS_METERS) * MAX_OFFSET_PCT, -MAX_OFFSET_PCT, MAX_OFFSET_PCT, ) return 50 + offset } function formatMeters(m: number) { return `${Math.round(m)} m` } export function HoleDiagram({ distances, playerPosition, hazards, size = "full", className, }: Props) { if (size === "compact") { return (
{hazards.length > 0 && ( )}
) } // --- Full variant ------------------------------------------------------- // Precompute placements and a light label-collision nudge (top -> bottom). const placed = hazards.map((h, i) => ({ ...h, key: `${h.label}-${i}`, topPct: alongToTopPct(h.alongPercent), leftPct: crossToLeftPct(h.crossMeters), })) const byTop = [...placed].sort((a, b) => a.topPct - b.topPct) const labelTopByKey = new Map() let lastLabelTop = Number.NEGATIVE_INFINITY for (const p of byTop) { const desired = p.topPct const labelTop = Math.max(desired, lastLabelTop + LABEL_MIN_GAP_PCT) labelTopByKey.set(p.key, labelTop) lastLabelTop = labelTop } const playerTop = alongToTopPct(playerPosition.alongPercent) const playerLeft = crossToLeftPct(playerPosition.crossMeters) return (
{/* Distance readout: center emphasized + largest, in the tee-green color. */}
{/* The hole diagram. Green fixed top, tee fixed bottom. */}
{/* Discreet tee->green centerline */} {/* Accessible, screen-reader-friendly text list — the same info as the diagram. */} {hazards.length > 0 && (

Hindringer

    {hazards.map((h, i) => (
  • ))}
)}
) } 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"], ) { 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.label} ${Math.round(h.distanceMeters)} meter`) .join(", ") return `${base} Hindringer: ${list}.` }