// assets/js/weather_map_hook.js function isMobile() { return window.innerWidth < 768 } const COLOR_SCALES = { refractivity_gradient: { breakpoints: [ { value: -500, r: 0, g: 255, b: 163 }, { value: -300, r: 125, g: 255, b: 212 }, { value: -200, r: 255, g: 229, b: 102 }, { value: -100, r: 255, g: 144, b: 68 }, { value: 0, r: 255, g: 79, b: 79 } ], format: v => v != null ? `${v.toFixed(0)} N/km` : "N/A", label: "Refractivity Gradient", unit: "N/km" }, bl_height: { breakpoints: [ { value: 0, r: 0, g: 255, b: 163 }, { value: 500, r: 125, g: 255, b: 212 }, { value: 1000, r: 255, g: 229, b: 102 }, { value: 2000, r: 255, g: 144, b: 68 }, { value: 3000, r: 255, g: 79, b: 79 } ], format: v => v != null ? `${v.toFixed(0)} m` : "N/A", label: "Boundary Layer Height", unit: "m" }, dewpoint_depression: { breakpoints: [ { value: 0, r: 0, g: 255, b: 163 }, { value: 5, r: 125, g: 255, b: 212 }, { value: 10, r: 255, g: 229, b: 102 }, { value: 20, r: 255, g: 144, b: 68 }, { value: 30, r: 255, g: 79, b: 79 } ], format: v => v != null ? `${v.toFixed(1)} \u00b0C` : "N/A", label: "Dewpoint Depression (T - Td)", unit: "\u00b0C" }, pwat: { breakpoints: [ { value: 0, r: 139, g: 90, b: 43 }, { value: 15, r: 194, g: 165, b: 116 }, { value: 30, r: 173, g: 216, b: 230 }, { value: 45, r: 65, g: 105, b: 225 }, { value: 60, r: 0, g: 0, b: 180 } ], format: v => v != null ? `${v.toFixed(1)} mm` : "N/A", label: "Precipitable Water", unit: "mm" }, temperature: { breakpoints: [ { value: -20, r: 0, g: 0, b: 200 }, { value: 0, r: 100, g: 149, b: 237 }, { value: 15, r: 255, g: 255, b: 150 }, { value: 30, r: 255, g: 140, b: 0 }, { value: 45, r: 200, g: 0, b: 0 } ], format: v => v != null ? `${v.toFixed(1)} \u00b0C (${(v * 9/5 + 32).toFixed(0)} \u00b0F)` : "N/A", label: "Surface Temperature", unit: "\u00b0C" }, ducting: { breakpoints: null, trueColor: { r: 0, g: 255, b: 163 }, format: v => v ? "Yes" : "No", label: "Ducting Detected", unit: "" }, surface_rh: { breakpoints: [ { value: 0, r: 255, g: 144, b: 68 }, { value: 30, r: 255, g: 229, b: 102 }, { value: 60, r: 125, g: 255, b: 212 }, { value: 80, r: 0, g: 255, b: 163 }, { value: 100, r: 0, g: 200, b: 130 } ], format: v => v != null ? `${v.toFixed(0)}%` : "N/A", label: "Relative Humidity", unit: "%" }, surface_refractivity: { breakpoints: [ { value: 250, r: 255, g: 79, b: 79 }, { value: 300, r: 255, g: 144, b: 68 }, { value: 330, r: 255, g: 229, b: 102 }, { value: 360, r: 125, g: 255, b: 212 }, { value: 400, r: 0, g: 255, b: 163 } ], format: v => v != null ? `${v.toFixed(1)} N` : "N/A", label: "Surface Refractivity", unit: "N" }, temp_850mb: { breakpoints: [ { value: -20, r: 0, g: 0, b: 200 }, { value: -5, r: 100, g: 149, b: 237 }, { value: 5, r: 255, g: 255, b: 150 }, { value: 15, r: 255, g: 140, b: 0 }, { value: 30, r: 200, g: 0, b: 0 } ], format: v => v != null ? `${v.toFixed(1)} °C` : "N/A", label: "Temperature at 850mb", unit: "°C" }, dewpoint_850mb: { breakpoints: [ { value: -20, r: 139, g: 90, b: 43 }, { value: -5, r: 194, g: 165, b: 116 }, { value: 5, r: 173, g: 216, b: 230 }, { value: 15, r: 65, g: 105, b: 225 }, { value: 25, r: 0, g: 0, b: 180 } ], format: v => v != null ? `${v.toFixed(1)} °C` : "N/A", label: "Dewpoint at 850mb", unit: "°C" }, lapse_rate: { breakpoints: [ { value: 0, r: 0, g: 255, b: 163 }, { value: 4, r: 125, g: 255, b: 212 }, { value: 6.5, r: 255, g: 229, b: 102 }, { value: 8, r: 255, g: 144, b: 68 }, { value: 10, r: 255, g: 79, b: 79 } ], format: v => v != null ? `${v.toFixed(1)} °C/km` : "N/A", label: "Lapse Rate", unit: "°C/km" }, inversion_strength: { breakpoints: [ { value: 0, r: 80, g: 80, b: 80 }, { value: 1, r: 255, g: 229, b: 102 }, { value: 3, r: 125, g: 255, b: 212 }, { value: 5, r: 0, g: 255, b: 163 }, { value: 10, r: 0, g: 200, b: 130 } ], format: v => v != null ? (v > 0 ? `+${v.toFixed(1)} °C` : "None") : "N/A", label: "Inversion Strength", unit: "°C" }, inversion_base_m: { breakpoints: [ { value: 0, r: 0, g: 255, b: 163 }, { value: 500, r: 125, g: 255, b: 212 }, { value: 1000, r: 255, g: 229, b: 102 }, { value: 2000, r: 255, g: 144, b: 68 }, { value: 3000, r: 255, g: 79, b: 79 } ], format: v => v != null ? `${v.toFixed(0)} m AGL` : "None", label: "Inversion Base Height", unit: "m" }, duct_base_m: { breakpoints: [ { value: 0, r: 0, g: 255, b: 163 }, { value: 200, r: 125, g: 255, b: 212 }, { value: 500, r: 255, g: 229, b: 102 }, { value: 1000, r: 255, g: 144, b: 68 }, { value: 2000, r: 255, g: 79, b: 79 } ], format: v => v != null ? `${v.toFixed(0)} m AGL` : "None", label: "Duct Base Height", unit: "m" }, duct_strength: { breakpoints: [ { value: 0, r: 80, g: 80, b: 80 }, { value: 5, r: 255, g: 229, b: 102 }, { value: 10, r: 125, g: 255, b: 212 }, { value: 20, r: 0, g: 255, b: 163 }, { value: 40, r: 0, g: 200, b: 130 } ], format: v => v != null ? (v > 0 ? `${v.toFixed(1)} M-units` : "None") : "N/A", label: "Duct Strength", unit: "M" } } function interpolateColor(value, breakpoints) { if (value == null) return null if (value <= breakpoints[0].value) return breakpoints[0] if (value >= breakpoints[breakpoints.length - 1].value) return breakpoints[breakpoints.length - 1] for (let i = 0; i < breakpoints.length - 1; i++) { const lo = breakpoints[i] const hi = breakpoints[i + 1] if (value >= lo.value && value <= hi.value) { const t = (value - lo.value) / (hi.value - lo.value) return { r: Math.round(lo.r + (hi.r - lo.r) * t), g: Math.round(lo.g + (hi.g - lo.g) * t), b: Math.round(lo.b + (hi.b - lo.b) * t) } } } return breakpoints[breakpoints.length - 1] } function buildDetailHTML(point) { if (!point) return "" const rows = [ { label: "Temperature", value: COLOR_SCALES.temperature.format(point.temperature), color: "#ff9044" }, { label: "Td Depression", value: COLOR_SCALES.dewpoint_depression.format(point.dewpoint_depression), color: "#7dffd4" }, { label: "Rel. Humidity", value: COLOR_SCALES.surface_rh.format(point.surface_rh), color: "#00ffa3" }, { label: "Sfc Pressure", value: point.surface_pressure_mb != null ? `${point.surface_pressure_mb.toFixed(1)} mb` : "N/A", color: "#aaa" }, { label: "Refractivity", value: COLOR_SCALES.surface_refractivity.format(point.surface_refractivity), color: "#00ffa3" }, { label: "N-Gradient", value: COLOR_SCALES.refractivity_gradient.format(point.refractivity_gradient), color: "#00ffa3" }, { label: "BL Height", value: COLOR_SCALES.bl_height.format(point.bl_height), color: "#ffe566" }, { label: "PWAT", value: COLOR_SCALES.pwat.format(point.pwat), color: "#4169e1" }, { separator: true }, { label: "T @ 850mb", value: COLOR_SCALES.temp_850mb.format(point.temp_850mb), color: "#ff9044" }, { label: "Td @ 850mb", value: COLOR_SCALES.dewpoint_850mb.format(point.dewpoint_850mb), color: "#4169e1" }, { label: "Lapse Rate", value: COLOR_SCALES.lapse_rate.format(point.lapse_rate), color: "#ffe566" }, { label: "Inversion", value: COLOR_SCALES.inversion_strength.format(point.inversion_strength), color: point.inversion_strength > 0 ? "#00ffa3" : "#666" }, { label: "Inv. Base", value: COLOR_SCALES.inversion_base_m.format(point.inversion_base_m), color: point.inversion_base_m != null ? "#7dffd4" : "#666" }, { separator: true }, { label: "Ducting", value: COLOR_SCALES.ducting.format(point.ducting), color: point.ducting ? "#00ffa3" : "#ff4f4f" }, { label: "Duct Base", value: COLOR_SCALES.duct_base_m.format(point.duct_base_m), color: point.duct_base_m != null ? "#00ffa3" : "#666" }, { label: "Duct Strength", value: COLOR_SCALES.duct_strength.format(point.duct_strength), color: point.duct_strength != null ? "#00ffa3" : "#666" } ] const mobile = isMobile() const closeBtn = mobile ? `` : "" const handle = mobile ? `