diff --git a/ARCHITECTURE_DECISIONS.md b/ARCHITECTURE_DECISIONS.md index 22d6e6c..231e62e 100644 --- a/ARCHITECTURE_DECISIONS.md +++ b/ARCHITECTURE_DECISIONS.md @@ -8453,6 +8453,99 @@ til bruker (i dag kun `rock`/"Fjellknaus"). V0-prompt for polert visning skrives etter at bruker har godkjent den hånd-kodede versjonen. +### ADR-084 del 2: retting fra ekte produksjonsbruk, ny hazard_group-data for Tjøme (2026-08-18) + +Bruker viste et ekte skjermbilde av v2-diagrammet i produksjon (samme +dag som utrullingen over) og meldte fem konkrete avvik, pluss lastet +opp fem nye 32px-ikoner (fjellknaus/trær/dogleg/layup/landemerke) og et +regneark med Tjømes koordinater. Alle fem rettet: + +1. **Ett ikon per hindring, ikke to** -- viste seg IKKE å være en kode- + feil, men at ekte Tjøme-data manglet `hazard_group` (forventet, se + "gjenstår" over). Løst ved å faktisk sette `hazard_group` på ekte + data (se under). +2. **Ikon-overlapp med green** -- reell kode-feil: `layoutLane()` sin + kollisjons-kaskade for midtbanen tok ikke hensyn til at green-ikonet + selv opptar plass i samme sone. Fikset med `CENTER_TRACK_TOP` + (reservert sone under green-ikonet, kun for midtbanen). +3. **Skal ikonene i listen under diagrammet gjenbrukes, evt. i større + størrelse?** Bruker spurte oss om å vurdere -- svarte ja, 36px (opp + fra det gamle universelle 20px `TriangleAlert`-ikonet), forskjellig + ikon per type. +4. **Listen manglet forkant/senter/bakkant og venstre/senter/høyre-info** + -- lagt til som en undertekst-linje per rad. +5. **Listen viste hvert par to ganger** -- skrevet om til én rad per + hindring, med "Forkant X m / Bakkant Y m" på samme rad når parret. + +**Under samme runde, ny rettelse fra bruker etter at "bakre +greenbunker" (hull 4) skulle pares:** "(Den må jo for øvrig vises OVER +greenikonet?)" -- riktig observasjon: `alongPercent` (fra +`projectOntoAxis`, se hole-geometry.ts) klippes bevisst IKKE til +[0,100] i geometrimodulen, men visningen klippet den likevel ved selve +tegningen, så en hindring bak selve greenen (alongPercent > 100) endte +opp i SAMME sone som "helt inntil green"-hindringer -- fysisk feil +side av green-ikonet. Fikset i `hole-diagram-view.tsx`: når minst én +midtbane-hindring har alongPercent > 100, flyttes green-ikonet lenger +ned (`GREEN_TOP_PCT_PUSHED`), og en egen liten sone åpnes OVER det +flyttede ikonet (`BEHIND_GREEN_ZONE_TOP/BOTTOM`), med kollisjons- +kaskade i motsatt retning (`layoutBehindGreenLane`, kaskaderer OPPOVER +i stedet for nedover). Ingen tilsvarende sone bak TEE ennå -- ingen +kjent forekomst i data, men samme mønster kan gjenbrukes symmetrisk. + +**Ny `hazard_group`-data for Tjøme, fra ekte kildedata (ikke +heuristikk):** brukeren lastet opp klubbens eget regneark med +koordinater, som viste seg å ha en EKSPLISITT gruppenummer-kolonne som +parer forkant/bakkant til samme fysiske hindring -- akkurat den +identiteten `hazard_group` (del 1, migrasjon 080) var designet for å +representere. Kryssjekket regnearkets 38 gruppenumre mot databasens +180 koordinatpunkter via lat/long-matching (Python-script, engangs, +scratchpad): 76 av 76 gyldige punkter identifisert eksakt. Løste +DEFINITIVT de to tilfellene forrige runde bevisst utelot som +tvetydige (hull 4: tre høyre fairway-bunkere, hull 13: to) ved +eliminasjon -- 2 av regnearkets rader hadde feil koordinatformat (UTM/ +EPSG:25833 i stedet for WGS84, sannsynligvis kopi-lim-feil ved +regnearkets forfatning), men gruppenummeret var likevel nok til å +identifisere DB-raden ved eliminasjon (de gjenværende umatchede +punktene på riktig hull+side+type). Fant i tillegg at `rock` +("fjellknaus") også har forkant/bakkant-par på fire hull -- ikke +fanget opp i del 1 siden rock da bare hadde generisk ikon. Bruker +bekreftet i tillegg manuelt én ekstra paring regnearket selv ikke +hadde gruppenummer for (hull 4 sin "bakre greenbunker"). + +**Resultat: 78 koordinatrader satt til 39 distinkte `hazard_group`- +verdier** (format `{hull}-{regneark-gruppenummer}`, sporbart tilbake +til kilden), kjørt som en frittstående `UPDATE ... FROM (VALUES ...)` +mot ekte `teecup_db` (ikke en migrasjon -- datainnhold, ikke skjema). + +**Bevisst IKKE gjort:** bekk (creek) fikk et eget ikon fra bruker +(`hazard-creek.png`, kopiert til `frontend/public/hole-diagram/`, IKKE +koblet til noe ennå) -- domenet `course_poi_type` har ingen egen verdi +for bekk i dag (kun `water` dekker både dam og bekk), så å skille dem +visuelt krever en ny domeneverdi + reklassifisering av de spesifikke +bekk-punktene regnearket identifiserer. Bruker ba eksplisitt om at +dette gjøres som en EGEN runde/beslutning, ikke bakt inn i denne. + +**Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55 (to ganger, +etter hvert av de to kode-rundene). Ny scratch-runde (database+API+ +next dev, samme disiplin) med presist konstruert "bak green"-scenario +-- bekreftet visuelt at bakre greenbunker faktisk rendres OVER +green-ikonet, ikke i normal-sonen, med normal-sonens egne hindringer +uendret ved siden av. Lys+mørk bekreftet begge runder. Begge scratch- +stackene fullstendig revet ned. + +**Rullet ut 2026-08-18** -- bruker bekreftet begge deler eksplisitt +(kode + data). `docker compose build teecup_frontend && up -d` +(ingen migrasjon -- kun frontend-kode denne runden), deretter +`UPDATE`-en over kjørt mot ekte `teecup_db`. Rene containerlogger, +`https://teecup.golf/logg-inn` 200 OK, 78/78 rader bekreftet satt +(`count(*) ... WHERE hazard_group IS NOT NULL` = 78, 39 distinkte). + +**Gjenstår:** bekk (creek) som egen `poi_type` -- egen runde, bruker +har bedt om dette eksplisitt. Liste over `_HAZARD_CONFIG`-typer uten +eget ikon oppdatert: nå KUN `road` (ingen ikon lastet opp for denne). +V0-prompt for polert visning fortsatt ikke skrevet -- venter på at +bruker har sett/godkjent denne runden i produksjon også. + --- ## Utviklingsplan (rekkefølge) diff --git a/CHANGELOG.md b/CHANGELOG.md index f31695e..e96c7b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12110,3 +12110,73 @@ Neste steg: (i dag kun `rock`/"Fjellknaus") leveres til bruker. V0-prompt for polert visning skrives etter godkjenning av den hånd-kodede versjonen. + +102. **Hull-diagram v2, retting fra ekte produksjonsbruk (ADR-084 del + 2) — 2026-08-18.** Bruker viste et ekte skjermbilde av v2- + diagrammet i produksjon og meldte fem avvik: (1) hindringer med + forkant+bakkant viste to ikoner i stedet for ett -- skyldtes at + ekte Tjøme-data ennå ikke hadde `hazard_group` satt, ikke en + kode-feil. (2) Et ikon overlappet green-ikonet i midtbanen -- ekte + kode-feil, `layoutLane()` tok ikke hensyn til green-ikonets egen + plass; fikset med en reservert sone (`CENTER_TRACK_TOP`). (3-4) + Listen under diagrammet manglet forkant/senter/bakkant- og + venstre/senter/høyre-info, og viste hvert par to ganger -- skrevet + om til én rad per hindring med begge avstander på samme rad, pluss + undertekst for bane/posisjon. (5) Ikonene i listen byttet fra + universelt 20px `TriangleAlert` til type-spesifikke ikoner ved + 36px. + + Underveis, da hull 4 sin "bakre greenbunker" skulle pares: bruker + påpekte at den fysisk ligger BAK selve greenen og derfor må vises + OVER green-ikonet, ikke i vanlig-sonen. `alongPercent` fra + `projectOntoAxis` klippes bevisst ikke i geometrimodulen (kan bli + >100), men visningen klippet den likevel -- fikset ved å flytte + green-ikonet ned og åpne en egen sone over det + (`GREEN_TOP_PCT_PUSHED`/`BEHIND_GREEN_ZONE_*`, + `layoutBehindGreenLane` kaskaderer oppover) når minst én + midtbane-hindring faktisk ligger bak green. + + Fem nye ikoner (fjellknaus/trær/dogleg/layup/landemerke, alle + 32px) integrert -- disse typene var i domenet fra før men manglet + egen visning i diagrammet (fjellknaus hadde generisk ikon, resten + var usynlige der). + + **Ny ekte `hazard_group`-data for Tjøme, fra kildedata, ikke + heuristikk:** bruker lastet opp klubbens eget koordinat-regneark, + som viste seg å ha en EKSPLISITT gruppenummer-kolonne som allerede + parer forkant/bakkant -- akkurat identiteten `hazard_group` (del 1) + var designet for. Kryssjekket regnearkets 38 grupper mot databasens + 180 punkter via lat/long (engangs Python-script) -- løste + DEFINITIVT de to sakene forrige runde bevisst lot stå + (hull 4: tre høyre fairway-bunkere, hull 13: to) ved eliminasjon, + til tross for at 2 rader i regnearket hadde feil koordinatformat + (UTM/EPSG:25833 -- trolig kopi-lim-feil, uskadelig siden kun + gruppenummeret var nødvendig). Fant i tillegg at `rock` også har + forkant/bakkant-par på fire hull (ikke fanget opp i del 1). Bruker + bekreftet manuelt én ekstra paring uten gruppenummer i regnearket + (hull 4 sin bakre greenbunker). Resultat: 78 rader satt til 39 + distinkte `hazard_group`-verdier (`{hull}-{regneark-gruppe}`, + sporbart til kilden) via en frittstående `UPDATE ... FROM + (VALUES ...)` -- datainnhold, ikke skjema, ingen ny migrasjon. + + **Bevisst utsatt:** bruker lastet opp et bekk (creek)-ikon, men + domenet `course_poi_type` skiller ikke bekk fra dam (begge + `water`) -- å vise dem ulikt krever en ny domeneverdi + + reklassifisering av spesifikke punkter. Bruker ba eksplisitt om at + dette blir en EGEN runde, ikke bakt inn her. + + **Verifisert:** `tsc --noEmit` rent, `vitest run` 55/55 (to ganger). + Ny scratch-runde med presist konstruert "bak green"-scenario -- + bekreftet visuelt at bakre greenbunker rendres over green-ikonet, + normal-sonens hindringer uendret ved siden av. Lys+mørk bekreftet + begge runder. Begge scratch-stackene revet ned. + + **Rullet ut 2026-08-18** -- bruker bekreftet kode og data hver for + seg. `docker compose build teecup_frontend && up -d` (kun + frontend-kode, ingen migrasjon), deretter UPDATE-en mot ekte + `teecup_db`. Rene containerlogger, `https://teecup.golf/logg-inn` + 200 OK, 78/78 rader bekreftet satt (39 distinkte grupper). + + **Gjenstår:** bekk som egen `poi_type` (egen runde, bedt om + eksplisitt av bruker). Ikon-mangel-lista er nå kun `road`. V0- + prompt for polert visning fortsatt ikke skrevet. diff --git a/frontend/components/hole-diagram-view.tsx b/frontend/components/hole-diagram-view.tsx index d6bcbba..26f268a 100644 --- a/frontend/components/hole-diagram-view.tsx +++ b/frontend/components/hole-diagram-view.tsx @@ -12,7 +12,7 @@ import { MapPin, TriangleAlert } from "lucide-react" import { cn } from "@/lib/utils" -export type HazardKind = "sand" | "water" | "generic" +export type HazardKind = "sand" | "water" | "rock" | "tree" | "dogleg" | "layup" | "landmark" | "generic" export type DiagramHazard = { key: string @@ -27,6 +27,21 @@ export type DiagramHazard = { * hindringen er et ekte forkant+bakkant-par (se hazard_group i * datalaget). `null` = enkeltpunkt, ingen tekst over ikonet. */ above: number | null + /** Kun meningsfullt når `above` er `null` -- brukes i listen under + * diagrammet til å si "forkant"/"senter"/"bakkant" om et + * enkeltstående punkt. */ + location: "front" | "middle" | "back" | null +} + +const _LANE_LABEL: Record = { + left: "Venstre", + center: "Senter", + right: "Høyre", +} +const _LOCATION_LABEL: Record<"front" | "middle" | "back", string> = { + front: "forkant", + middle: "midtpunkt", + back: "bakkant", } type Props = { @@ -39,40 +54,78 @@ type Props = { 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) +// Midtbanen har i tillegg faste green-/tee-ankre tegnet OVENPÅ selve +// sporet (green-ikon+etikett øverst, tee-prikk+etikett nederst) -- en +// hindring plassert etter samme skala som venstre/høyre-banen kunne +// dermed havne visuelt oppå green-ikonet. Reservert sone er banens +// egen skala, kun for midtbanen. +const CENTER_TRACK_TOP = 29 +const CENTER_TRACK_BOTTOM = 80 const LABEL_MIN_GAP_PCT = 16 // hindringer i samme bane trenger mer luft nå (ikon + to tall) +// En hindring KAN ligge bak selve greenen (alongPercent > 100 -- f.eks. +// en bakre greenbunker) -- projectOntoAxis() klipper bevisst IKKE dette +// bort (se hole-geometry.ts). Uten særbehandling ville en slik hindring +// blitt klippet til samme posisjon som "helt inntil green" og dermed +// visuelt smeltet sammen med front-of-green-hindringer, selv om den +// fysisk ligger på MOTSATT side av green-ikonet. Løsning: når minst én +// senterbane-hindring er bak green, flyttes green-ankeret lenger ned +// (fra faste 3% til GREEN_TOP_PCT_PUSHED), og en egen, liten sone +// ovenfor det flyttede ankeret reserveres til disse hindringene -- +// nærmest green kaskaderer OPPOVER (motsatt retning av layoutLane) ved +// kollisjon. Ingen tilsvarende sone finnes (ennå) for hindringer bak +// TEE -- ingen kjent forekomst i dagens data, men samme mønster kan +// gjenbrukes symmetrisk om det trengs. +const GREEN_TOP_PCT_DEFAULT = 3 +const GREEN_TOP_PCT_PUSHED = 22 +const CENTER_TRACK_TOP_PUSHED = 40 +const BEHIND_GREEN_ZONE_TOP = 3 +const BEHIND_GREEN_ZONE_BOTTOM = 19 +const BEHIND_GREEN_MAX_OVERSHOOT = 40 // % av hull-lengden bak green før visuelt klippet til toppen + function clamp(n: number, lo: number, hi: number) { return Math.min(hi, Math.max(lo, n)) } -function alongToTopPct(alongPercent: number) { +function alongToTopPct(alongPercent: number, top = TRACK_TOP, bottom = TRACK_BOTTOM) { const a = clamp(alongPercent, 0, 100) - return TRACK_TOP + ((100 - a) / 100) * (TRACK_BOTTOM - TRACK_TOP) + return top + ((100 - a) / 100) * (bottom - top) } function formatMeters(m: number) { return `${Math.round(m)} m` } -function HazardIcon({ kind }: { kind: HazardKind }) { - if (kind === "sand") { - return - } - if (kind === "water") { - return +const _ICON_SRC: Partial> = { + sand: "/hole-diagram/hazard-sand.png", + water: "/hole-diagram/hazard-water.png", + rock: "/hole-diagram/hazard-rock.png", + tree: "/hole-diagram/hazard-tree.png", + dogleg: "/hole-diagram/hazard-dogleg.png", + layup: "/hole-diagram/hazard-layup.png", + landmark: "/hole-diagram/hazard-landmark.png", +} + +function HazardIcon({ kind, size = 32 }: { kind: HazardKind; size?: number }) { + const src = _ICON_SRC[kind] + if (src) { + 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)) + * bane siden hindringer nå er spredt over tre baner. `top`/`bottom` + * lar midtbanen reservere plass til de faste green-/tee-ankrene. */ +function layoutLane(hazards: DiagramHazard[], top = TRACK_TOP, bottom = TRACK_BOTTOM) { + const sorted = [...hazards].sort( + (a, b) => alongToTopPct(a.alongPercent, top, bottom) - alongToTopPct(b.alongPercent, top, bottom), + ) const placed: (DiagramHazard & { topPct: number })[] = [] let lastTop = Number.NEGATIVE_INFINITY for (const h of sorted) { - const desired = alongToTopPct(h.alongPercent) + const desired = alongToTopPct(h.alongPercent, top, bottom) const topPct = Math.max(desired, lastTop + LABEL_MIN_GAP_PCT) placed.push({ ...h, topPct }) lastTop = topPct @@ -80,6 +133,34 @@ function layoutLane(hazards: DiagramHazard[]) { return placed } +function behindGreenDesiredTopPct(alongPercent: number) { + const overshoot = clamp(alongPercent - 100, 0, BEHIND_GREEN_MAX_OVERSHOOT) + // 0 overshoot (rett bak green) -> bunnen av sonen (nærmest green-ikonet). + // Maks overshoot -> toppen av sonen (lengst unna, øverst i diagrammet). + return ( + BEHIND_GREEN_ZONE_BOTTOM - + (overshoot / BEHIND_GREEN_MAX_OVERSHOOT) * (BEHIND_GREEN_ZONE_BOTTOM - BEHIND_GREEN_ZONE_TOP) + ) +} + +/** Speilvendt av layoutLane() -- kaskaderer OPPOVER (mot diagrammets + * toppkant) ved kollisjon, siden disse hindringene ligger bak green, + * ikke foran den. */ +function layoutBehindGreenLane(hazards: DiagramHazard[]) { + const sorted = [...hazards].sort( + (a, b) => behindGreenDesiredTopPct(b.alongPercent) - behindGreenDesiredTopPct(a.alongPercent), + ) + const placed: (DiagramHazard & { topPct: number })[] = [] + let lastTop = Number.POSITIVE_INFINITY + for (const h of sorted) { + const desired = behindGreenDesiredTopPct(h.alongPercent) + const topPct = Math.min(desired, lastTop - LABEL_MIN_GAP_PCT) + placed.push({ ...h, topPct }) + lastTop = topPct + } + return placed +} + function HazardMarker({ hazard, topPct }: { hazard: DiagramHazard; topPct: number }) { return (
h.lane === "left")) - const centerHazards = layoutLane(hazards.filter((h) => h.lane === "center")) + const centerAll = hazards.filter((h) => h.lane === "center") + const centerBehindGreen = centerAll.filter((h) => h.alongPercent > 100) + const centerNormal = centerAll.filter((h) => h.alongPercent <= 100) + const hasBehindGreen = centerBehindGreen.length > 0 + const greenTopPct = hasBehindGreen ? GREEN_TOP_PCT_PUSHED : GREEN_TOP_PCT_DEFAULT + const centerTrackTop = hasBehindGreen ? CENTER_TRACK_TOP_PUSHED : CENTER_TRACK_TOP + const centerHazards = layoutLane(centerNormal, centerTrackTop, CENTER_TRACK_BOTTOM) + const behindGreenHazards = layoutBehindGreenLane(centerBehindGreen) const rightHazards = layoutLane(hazards.filter((h) => h.lane === "right")) const playerTop = alongToTopPct(playerPosition.alongPercent) @@ -160,7 +248,8 @@ export function HoleDiagram({ distances, playerPosition, hazards, size = "full",
+ {behindGreenHazards.map((h) => ( + + ))} + )} @@ -223,12 +311,30 @@ export function HoleDiagram({ distances, playerPosition, hazards, size = "full", ) } -function HazardRow({ label, value }: { label: string; value: number }) { +function HazardRow({ hazard }: { hazard: DiagramHazard }) { + const meta = [ + _LANE_LABEL[hazard.lane], + hazard.above === null && hazard.location ? _LOCATION_LABEL[hazard.location] : null, + ] + .filter(Boolean) + .join(" · ") return (
  • -
  • ) } @@ -281,11 +387,14 @@ function buildDiagramAria( 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(", ") + .map((h) => { + const lane = _LANE_LABEL[h.lane].toLowerCase() + if (h.above !== null) { + return `${h.label}, ${lane}, forkant ${Math.round(h.below)} meter, bakkant ${Math.round(h.above)} meter` + } + const location = h.location ? `, ${_LOCATION_LABEL[h.location]}` : "" + return `${h.label}, ${lane}${location}, ${Math.round(h.below)} meter` + }) + .join("; ") return `${base} Hindringer: ${list}.` } diff --git a/frontend/components/hole-target-distance.tsx b/frontend/components/hole-target-distance.tsx index a68f077..0af53b4 100644 --- a/frontend/components/hole-target-distance.tsx +++ b/frontend/components/hole-target-distance.tsx @@ -54,17 +54,26 @@ const _HAZARD_LABELS: Record = { fairway_bunker: "Bunker (fairway)", water: "Vannhinder", rock: "Fjellknaus", + trees: "Trær", + dogleg: "Dogleg", + layup: "Layup-punkt", + landmark: "Landemerke", } // ADR-084: hvilket ikon en hindringstype får i diagrammet. "generic" -// (typer uten dedikert ikon, i dag kun rock) faller tilbake til -// lucide sin TriangleAlert -- se hole-diagram-view.tsx. -type HazardKind = "sand" | "water" | "generic" +// (typer uten dedikert ikon -- i dag ingen, `road` ville falt hit hvis +// den noen gang klassifiseres som hindring) faller tilbake til lucide +// sin TriangleAlert -- se hole-diagram-view.tsx. +type HazardKind = "sand" | "water" | "rock" | "tree" | "dogleg" | "layup" | "landmark" | "generic" const _HAZARD_KIND: Record = { green_bunker: "sand", fairway_bunker: "sand", water: "water", - rock: "generic", + rock: "rock", + trees: "tree", + dogleg: "dogleg", + layup: "layup", + landmark: "landmark", } // Grupperer rå hindringspunkter til hindrings-ENTITETER for diagrammet. @@ -99,6 +108,10 @@ function groupDiagramHazards( alongPercent: number below: number above: number | null + // Kun meningsfullt for enkeltstående punkt (above === null) -- + // et parret forkant+bakkant-hinder (above !== null) trenger ikke + // dette, UI-en viser da alltid eksplisitt "forkant"/"bakkant". + location: "front" | "middle" | "back" | null } const entities: Entity[] = [] @@ -124,6 +137,7 @@ function groupDiagramHazards( alongPercent: alongValues.reduce((a, b) => a + b, 0) / alongValues.length, below: front ? distanceTo(front) : distanceTo(primary), above: front && back ? distanceTo(back) : null, + location: front && back ? null : primary.location, }) } for (const p of standalone) { @@ -137,6 +151,7 @@ function groupDiagramHazards( alongPercent: along(p), below: distanceTo(p), above: null, + location: p.location, }) } return entities diff --git a/frontend/public/hole-diagram/hazard-creek.png b/frontend/public/hole-diagram/hazard-creek.png new file mode 100644 index 0000000..d45baf3 Binary files /dev/null and b/frontend/public/hole-diagram/hazard-creek.png differ diff --git a/frontend/public/hole-diagram/hazard-dogleg.png b/frontend/public/hole-diagram/hazard-dogleg.png new file mode 100644 index 0000000..023da74 Binary files /dev/null and b/frontend/public/hole-diagram/hazard-dogleg.png differ diff --git a/frontend/public/hole-diagram/hazard-landmark.png b/frontend/public/hole-diagram/hazard-landmark.png new file mode 100644 index 0000000..8ae5440 Binary files /dev/null and b/frontend/public/hole-diagram/hazard-landmark.png differ diff --git a/frontend/public/hole-diagram/hazard-layup.png b/frontend/public/hole-diagram/hazard-layup.png new file mode 100644 index 0000000..de6d78f Binary files /dev/null and b/frontend/public/hole-diagram/hazard-layup.png differ diff --git a/frontend/public/hole-diagram/hazard-rock.png b/frontend/public/hole-diagram/hazard-rock.png new file mode 100644 index 0000000..84ade95 Binary files /dev/null and b/frontend/public/hole-diagram/hazard-rock.png differ diff --git a/frontend/public/hole-diagram/hazard-tree.png b/frontend/public/hole-diagram/hazard-tree.png new file mode 100644 index 0000000..bfca5e6 Binary files /dev/null and b/frontend/public/hole-diagram/hazard-tree.png differ