{"id":46,"date":"2026-01-03T15:36:11","date_gmt":"2026-01-03T14:36:11","guid":{"rendered":"https:\/\/hornet-radar.com\/?page_id=46"},"modified":"2026-03-03T17:06:39","modified_gmt":"2026-03-03T16:06:39","slug":"details","status":"publish","type":"page","link":"https:\/\/hornet-radar.com\/en\/details","title":{"rendered":"Details view"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n\n<style>\nbody {\n  font-family: system-ui, sans-serif;\n}\n\nselect {\n  padding: 6px;\n  margin-bottom: 12px;\n}\n\ntable {\n  width: 100%;\n  border-collapse: collapse;\n}\n\nth, td {\n  padding: 8px;\n  border-bottom: 1px solid #ddd;\n  text-align: left;\n  vertical-align: middle;\n}\n\nth {\n  background: #f5f5f5;\n}\n\nimg.thumb {\n  width: 100px;\n  border-radius: 6px;\n  cursor: pointer;\n}\n\n.badge-ah {\n  color: #fff;\n  background: #d32f2f;\n  padding: 2px 6px;\n  border-radius: 4px;\n}\n\n.badge-eh {\n  color: #fff;\n  background: #388e3c;\n  padding: 2px 6px;\n  border-radius: 4px;\n}\n<\/style>\n<\/head>\n\n<body>\n<label>\n  Bait-Station:\n  <select id=\"pi-select\">\n    <option value=\"\">\u2013 please select \u2013<\/option>\n  <\/select>\n<\/label>\n\n<table>\n  <thead>\n    <tr>\n      <th>Time (UTC)<\/th>\n      <th>Species<\/th>\n      <th>Confidence<\/th>\n      <th>Approach Angle<\/th>\n      <th>Departure Angle<\/th>\n      <th>Source<\/th>\n      <th>Image<\/th>\n    <\/tr>\n  <\/thead>\n  <tbody id=\"sightings-table\">\n    <tr><td colspan=\"7\">Please select a Bait-Station<\/td><\/tr>\n  <\/tbody>\n<\/table>\n\n<script>\nconst SUPABASE_URL = \"https:\/\/lebtnjdpjntaqheahjoi.supabase.co\";\nconst SUPABASE_ANON_KEY = \"sb_publishable_yRnBJ6G8mN-44O_8iNKltw_J2_-899y\";\n\nconst piSelect = document.getElementById(\"pi-select\");\nconst tableBody = document.getElementById(\"sightings-table\");\n\n\/* ===== URL PARAM ===== *\/\nconst params = new URLSearchParams(window.location.search);\nconst preselectedPi = params.get(\"pi_id\");\n\n\/* ===== LOAD PI LIST ===== *\/\nfetch(`${SUPABASE_URL}\/rest\/v1\/sightings?select=pi_id`, {\n  headers: {\n    apikey: SUPABASE_ANON_KEY,\n    Authorization: `Bearer ${SUPABASE_ANON_KEY}`\n  }\n})\n.then(r => r.json())\n.then(data => {\n  const pis = [...new Set(data.map(d => d.pi_id))].sort();\n  const defaultPi = preselectedPi || pis[0];\n\n  pis.forEach(pi => {\n    const opt = document.createElement(\"option\");\n    opt.value = pi;\n    opt.textContent = pi;\n    if (pi === preselectedPi) opt.selected = true;\n    piSelect.appendChild(opt);\n  });\n  if (defaultPi) loadSightings(defaultPi);\n});\n\n\/* ===== Winkelberechnung ===== *\/\nfunction vectorToAngle(vec) {\n  if (!Array.isArray(vec) || vec.length !== 2) return null;\n\n  const [x, y] = vec;\n\n  let angle = Math.atan2(-y, x) * 180 \/ Math.PI;\n\n  \/\/ In 0\u2013360\u00b0 normalisieren und nach Norden drehen\n  angle = (90 - angle + 360) % 360;\n\n  return angle;\n}\n\n\nfunction formatAngle(angle) {\n  if (typeof angle !== \"number\") return \"-\";\n  return angle.toFixed(1) + \"\u00b0 \" + angleToCompass(angle);\n}\n\nfunction angleToCompass(angle) {\n  const dirs = [\"N\",\"NE\",\"E\",\"SE\",\"S\",\"SW\",\"W\",\"NW\"];\n  return dirs[Math.round(angle \/ 45) % 8];\n}\n\n\/* ===== LOAD SIGHTINGS ===== *\/\nfunction loadSightings(pi_id) {\n  tableBody.innerHTML = `<tr><td colspan=\"7\">Loading Data\u2026<\/td><\/tr>`;\n\n  fetch(`${SUPABASE_URL}\/rest\/v1\/sightings?select=*&pi_id=eq.${pi_id}&order=timestamp.desc`, {\n    headers: {\n      apikey: SUPABASE_ANON_KEY,\n      Authorization: `Bearer ${SUPABASE_ANON_KEY}`\n    }\n  })\n  .then(r => r.json())\n  .then(data => {\n    if (!data.length) {\n      tableBody.innerHTML = `<tr><td colspan=\"7\">No sightings<\/td><\/tr>`;\n      return;\n    }\n\n    tableBody.innerHTML = \"\";\n\n    data.forEach(d => {\n        \n        let approachVec = d.approach_vec;\n        let departureVec = d.departure_vec;\n\n        \/\/ Falls Supabase JSON als String liefert\n        if (typeof approachVec === \"string\") {\n          try { approachVec = JSON.parse(approachVec); } catch {}\n        }\n        if (typeof departureVec === \"string\") {\n          try { departureVec = JSON.parse(departureVec); } catch {}\n        }\n\n        const approachAngleRaw = vectorToAngle(approachVec);\n        const departureAngle = vectorToAngle(departureVec);\n        const approachAngle = approachAngleRaw !== null\n          ? (approachAngleRaw + 180) % 360\n          : null;\n\n        const speciesList = Array.isArray(d.species) ? d.species : [d.species];\n        const speciesHtml = speciesList.map(s =>\n        s.toLowerCase().includes(\"asian\")\n            ? `<span class=\"badge-ah\">AH<\/span>`\n            : `<span class=\"badge-eh\">EH<\/span>`\n        ).join(\" \");\n\n        \/\/ Detections sauber aufbereiten\n        let detectionsHtml = \"-\";\n        let detections = d.detections;\n\n        if (typeof detections === \"string\") {\n        try {\n            detections = JSON.parse(detections);\n        } catch (e) {\n            console.warn(\"Detections JSON parse failed\", e);\n            detections = [];\n        }\n        }\n\n        if (Array.isArray(detections) && detections.length > 0) {\n        detectionsHtml = detections.map(det => {\n            const label = det.class_id === 1 ? \"AH\" : \"EH\";\n            const conf =\n            typeof det.confidence === \"number\"\n                ? ((det.confidence * 100).toFixed(2))\n                : \"?\";\n            return `${label} (${conf}%)`;\n        }).join(\"<br>\");\n        }\n\n\n        const row = document.createElement(\"tr\");\n        row.innerHTML = `\n            <td>${new Date(d.timestamp).toLocaleString()}<\/td>\n            <td>${speciesHtml}<\/td>\n            <td>${detectionsHtml}<\/td>\n            <td>${formatAngle(approachAngle)}<\/td>\n            <td>${formatAngle(departureAngle)}<\/td>\n            <td>${d.source}<\/td>\n            <td>\n            <a href=\"${d.image_url}\" class=\"popup\">\n                <img decoding=\"async\" src=\"${d.thumb_url}\" class=\"thumb\">\n            <\/a>\n            <\/td>\n        `;\n        \n\n\n        tableBody.appendChild(row);\n    });\n});\n}\n\n\/* ===== EVENTS ===== *\/\npiSelect.addEventListener(\"change\", e => {\n  if (e.target.value) {\n    loadSightings(e.target.value);\n  }\n});\n\n<\/script>\n\n<script>\ndocument.addEventListener(\"click\", e => {\n    const a = e.target.closest(\"a.popup\");\n    if (!a) return;\n\n    e.preventDefault();\n\n    const overlay = document.createElement(\"div\");\n    overlay.style = `\n        position:fixed; inset:0; background:rgba(0,0,0,.8);\n        display:flex; justify-content:center; align-items:center;\n        cursor:pointer; z-index:9999;\n    `;\n\n    overlay.innerHTML = `<img decoding=\"async\" src=\"${a.href}\" style=\"max-width:90%;max-height:90%\">`;\n    overlay.onclick = () => overlay.remove();\n    document.body.appendChild(overlay);\n});\n<\/script>\n\n\n<\/body>\n<\/html>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Bait-Station: \u2013 please select \u2013 Time (UTC) Species Confidence Approach Angle Departure Angle Source Image Please select a Bait-Station<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":3,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-46","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/hornet-radar.com\/en\/wp-json\/wp\/v2\/pages\/46","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hornet-radar.com\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/hornet-radar.com\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/hornet-radar.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/hornet-radar.com\/en\/wp-json\/wp\/v2\/comments?post=46"}],"version-history":[{"count":52,"href":"https:\/\/hornet-radar.com\/en\/wp-json\/wp\/v2\/pages\/46\/revisions"}],"predecessor-version":[{"id":584,"href":"https:\/\/hornet-radar.com\/en\/wp-json\/wp\/v2\/pages\/46\/revisions\/584"}],"wp:attachment":[{"href":"https:\/\/hornet-radar.com\/en\/wp-json\/wp\/v2\/media?parent=46"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}