{"id":1116,"date":"2026-03-13T17:09:26","date_gmt":"2026-03-13T16:09:26","guid":{"rendered":"https:\/\/www.datura.website\/?page_id=1116"},"modified":"2026-03-13T17:09:26","modified_gmt":"2026-03-13T16:09:26","slug":"spaceweather","status":"publish","type":"page","link":"https:\/\/www.datura.website\/?page_id=1116","title":{"rendered":"SpaceWeather"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\" \/>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \/>\n  <title>Space Weather API Data Tracker<\/title>\n  <script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/Chart.js\/3.9.1\/chart.min.js\"><\/script>\n  <style>\n    *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n\n    body {\n      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n      background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);\n      min-height: 100vh;\n      color: #333;\n      padding: 20px;\n    }\n\n    .container {\n      max-width: 1600px;\n      margin: 0 auto;\n      background: rgba(255,255,255,0.95);\n      border-radius: 15px;\n      padding: 30px;\n      box-shadow: 0 15px 35px rgba(0,0,0,0.2);\n      backdrop-filter: blur(10px);\n    }\n\n    h1 {\n      background: linear-gradient(135deg, #1565c0, #42a5f5);\n      -webkit-background-clip: text;\n      -webkit-text-fill-color: transparent;\n      background-clip: text;\n      text-align: center;\n      font-size: 2.8em;\n      margin-bottom: 25px;\n    }\n\n    h2 {\n      color: #1565c0;\n      border-bottom: 3px solid #e3f2fd;\n      padding-bottom: 12px;\n      margin: 30px 0 0 0;\n      font-size: 1.4em;\n    }\n\n    h3 {\n      margin: 0 0 12px 0;\n      color: #666;\n      font-size: 13px;\n      text-transform: uppercase;\n      letter-spacing: 1.2px;\n    }\n\n    .info-panel {\n      background: linear-gradient(135deg, #e8f5e8, #c8e6c9);\n      border-radius: 10px;\n      padding: 18px 22px;\n      margin: 20px 0;\n      border-left: 5px solid #4caf50;\n      font-size: 14px;\n      line-height: 1.8;\n    }\n\n    \/* Refresh bar *\/\n    .refresh-bar {\n      display: flex;\n      align-items: center;\n      justify-content: space-between;\n      background: #e3f2fd;\n      border-radius: 10px;\n      padding: 10px 18px;\n      margin-bottom: 20px;\n      font-size: 13px;\n      color: #1565c0;\n    }\n    .refresh-bar button {\n      background: linear-gradient(135deg, #1565c0, #42a5f5);\n      color: white;\n      border: none;\n      padding: 7px 18px;\n      border-radius: 20px;\n      cursor: pointer;\n      font-size: 13px;\n      transition: opacity .2s;\n    }\n    .refresh-bar button:hover { opacity: .85; }\n    #countdown { font-weight: bold; }\n\n    \/* Spinner *\/\n    .spinner-wrap {\n      display: none;\n      text-align: center;\n      padding: 40px;\n    }\n    .spinner {\n      width: 48px; height: 48px;\n      border: 5px solid #e3f2fd;\n      border-top-color: #1565c0;\n      border-radius: 50%;\n      animation: spin .8s linear infinite;\n      display: inline-block;\n    }\n    @keyframes spin { to { transform: rotate(360deg); } }\n\n    \/* Data grid *\/\n    .data-grid {\n      display: grid;\n      grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));\n      gap: 22px;\n      margin-top: 22px;\n    }\n\n    .data-item {\n      background: linear-gradient(135deg, #f8f9ff, #e3f2fd);\n      border-radius: 15px;\n      padding: 22px;\n      text-align: center;\n      box-shadow: 0 8px 25px rgba(0,0,0,0.08);\n      transition: transform .3s, box-shadow .3s;\n      border: 1px solid rgba(63,81,181,0.1);\n    }\n    .data-item:hover { transform: translateY(-6px); box-shadow: 0 15px 40px rgba(0,0,0,0.14); }\n    .data-item.solar { background: linear-gradient(135deg, #fff8e1, #ffecb3); }\n    .data-item.alerts { background: linear-gradient(135deg, #fff3e0, #ffe0b2); }\n    .data-item.error { background: linear-gradient(135deg, #ffebee, #ffcdd2); }\n\n    .data-value {\n      font-size: 24px;\n      color: #1565c0;\n      font-weight: bold;\n      margin: 14px 0;\n      word-break: break-word;\n    }\n    .data-item.solar .data-value { color: #f57c00; }\n    .data-item.alerts .data-value { color: #ef6c00; }\n    .data-item.error .data-value { color: #c62828; font-size: 18px; }\n\n    .metric { font-size: 12px; color: #666; margin-top: 8px; }\n\n    \/* Status dot *\/\n    .status-indicator {\n      display: inline-block;\n      width: 11px; height: 11px;\n      border-radius: 50%;\n      margin-right: 8px;\n      vertical-align: middle;\n    }\n    .status-ok { background: #4caf50; }\n    .status-error { background: #f44336; }\n    .status-warning { background: #ff9800; }\n\n    \/* Alerts list *\/\n    .alert-item {\n      background: #fff3e0;\n      border-left: 4px solid #ff9800;\n      padding: 14px;\n      margin: 10px 0;\n      border-radius: 6px;\n      font-size: 13px;\n      line-height: 1.6;\n      white-space: pre-wrap;\n      word-break: break-word;\n    }\n    .alert-item small { color: #999; display: block; margin-top: 6px; }\n    #alertsContainer { margin-top: 16px; }\n\n    \/* Charts *\/\n    .chart-grid {\n      display: grid;\n      grid-template-columns: repeat(auto-fit, minmax(480px, 1fr));\n      gap: 28px;\n      margin-top: 28px;\n    }\n    .chart-container {\n      background: white;\n      border-radius: 15px;\n      padding: 22px;\n      box-shadow: 0 8px 25px rgba(0,0,0,0.08);\n      height: 380px;\n      position: relative;\n    }\n    .chart-title {\n      color: #1565c0;\n      font-size: 16px;\n      font-weight: bold;\n      margin-bottom: 12px;\n      text-align: center;\n    }\n\n    \/* Table *\/\n    .history-table {\n      width: 100%;\n      border-collapse: collapse;\n      margin-top: 20px;\n      background: white;\n      border-radius: 10px;\n      overflow: hidden;\n      box-shadow: 0 5px 15px rgba(0,0,0,0.08);\n      font-size: 13px;\n    }\n    .history-table th, .history-table td {\n      padding: 11px 14px;\n      text-align: left;\n      border-bottom: 1px solid #eee;\n    }\n    .history-table th {\n      background: linear-gradient(135deg, #1565c0, #42a5f5);\n      color: white;\n      font-weight: 600;\n      white-space: nowrap;\n    }\n    .history-table tr:last-child td { border-bottom: none; }\n    .history-table tr:hover { background: #f5f9ff; }\n\n    \/* Timestamp *\/\n    .timestamp {\n      color: #7986cb;\n      font-style: italic;\n      text-align: center;\n      margin-top: 24px;\n      font-size: 15px;\n      font-weight: 500;\n    }\n\n    \/* Sources *\/\n    .sources {\n      background: #f5f5f5;\n      padding: 14px 18px;\n      border-radius: 8px;\n      margin: 20px 0;\n      font-size: 13px;\n      line-height: 1.8;\n    }\n\n    \/* KP badge color *\/\n    .kp-0, .kp-1 { color: #4caf50 !important; }\n    .kp-2, .kp-3 { color: #8bc34a !important; }\n    .kp-4 { color: #ffc107 !important; }\n    .kp-5 { color: #ff9800 !important; }\n    .kp-6, .kp-7 { color: #f44336 !important; }\n    .kp-8, .kp-9 { color: #9c27b0 !important; }\n\n    @media (max-width: 600px) {\n      h1 { font-size: 1.8em; }\n      .chart-grid { grid-template-columns: 1fr; }\n      .chart-container { height: 280px; }\n    }\n  <\/style>\n<\/head>\n<body>\n<div class=\"container\">\n\n  <h1>\ud83d\udef0\ufe0f Space Weather API Data Tracker<\/h1>\n\n  <div class=\"refresh-bar\">\n    <span>Auto-refresh in <span id=\"countdown\">300<\/span>s<\/span>\n    <button onclick=\"fetchAll()\">\u27f3 Refresh Now<\/button>\n  <\/div>\n\n  <div class=\"info-panel\">\n    <strong>\ud83d\udce1 API Data Sources:<\/strong><br>\n    \u2022 NOAA Space Weather Prediction Center (services.swpc.noaa.gov)<br>\n    \u2022 GOES Satellite Real-time Plasma &#038; X-ray Data<br>\n    \u2022 NOAA Planetary K-index (1-minute)<br>\n    \u2022 Solar Energetic Particle &amp; Alert Feed\n  <\/div>\n\n  <div id=\"spinner\" class=\"spinner-wrap\"><div class=\"spinner\"><\/div><p style=\"margin-top:14px;color:#1565c0\">Loading live data\u2026<\/p><\/div>\n\n  <!-- GEOMAGNETIC -->\n  <h2>\ud83c\udf0d Geomagnetic Activity<\/h2>\n  <div class=\"data-grid\">\n    <div class=\"data-item\" id=\"kpCard\">\n      <h3><span class=\"status-indicator status-ok\" id=\"kpDot\"><\/span>KP Index (Geomagnetic Activity)<\/h3>\n      <div class=\"data-value\" id=\"kpValue\">\u2014<\/div>\n      <div class=\"metric\" id=\"kpMeta\"><\/div>\n    <\/div>\n    <div class=\"data-item\" id=\"kpForecastCard\">\n      <h3><span class=\"status-indicator status-ok\"><\/span>3-Day KP Forecast<\/h3>\n      <div class=\"data-value\" id=\"kpForecastValue\" style=\"font-size:18px;\">\u2014<\/div>\n    <\/div>\n  <\/div>\n\n  <!-- SOLAR WIND -->\n  <h2>\ud83d\udca8 Solar Wind Conditions<\/h2>\n  <div class=\"data-grid\">\n    <div class=\"data-item\" id=\"windCard\">\n      <h3><span class=\"status-indicator status-ok\" id=\"windDot\"><\/span>Solar Wind Speed<\/h3>\n      <div class=\"data-value\" id=\"windSpeed\">\u2014<\/div>\n      <div class=\"metric\" id=\"windDensity\"><\/div>\n      <div class=\"metric\" id=\"windMeta\"><\/div>\n    <\/div>\n    <div class=\"data-item\" id=\"magCard\">\n      <h3><span class=\"status-indicator status-ok\"><\/span>Interplanetary Magnetic Field<\/h3>\n      <div class=\"data-value\" id=\"magBt\">\u2014<\/div>\n      <div class=\"metric\" id=\"magBz\"><\/div>\n      <div class=\"metric\" id=\"magMeta\"><\/div>\n    <\/div>\n  <\/div>\n\n  <!-- SOLAR FLARE -->\n  <h2>\u2600\ufe0f Solar Flare Activity<\/h2>\n  <div class=\"data-grid\">\n    <div class=\"data-item solar\" id=\"fluxCard\">\n      <h3><span class=\"status-indicator status-ok\" id=\"fluxDot\"><\/span>Current X-ray Flux (1-8 \u00c5)<\/h3>\n      <div class=\"data-value\" id=\"fluxValue\">\u2014<\/div>\n      <div class=\"metric\" id=\"fluxClass\"><\/div>\n      <div class=\"metric\" id=\"fluxMeta\"><\/div>\n    <\/div>\n    <div class=\"data-item solar\" id=\"flareCard\">\n      <h3><span class=\"status-indicator status-ok\"><\/span>Recent Solar Flare Events<\/h3>\n      <div class=\"data-value\" id=\"flareValue\" style=\"font-size:18px;\">\u2014<\/div>\n    <\/div>\n  <\/div>\n\n  <!-- ALERTS -->\n  <h2>\ud83d\udea8 Active Space Weather Alerts<\/h2>\n  <div class=\"data-item alerts\" style=\"margin-top:20px;\">\n    <h3><span class=\"status-indicator status-warning\"><\/span>Alert Status<\/h3>\n    <div class=\"data-value\" id=\"alertCount\">\u2014<\/div>\n  <\/div>\n  <div id=\"alertsContainer\"><\/div>\n\n  <!-- CHARTS -->\n  <h2>\ud83d\udcca Historical Data Charts<\/h2>\n  <div class=\"chart-grid\">\n    <div class=\"chart-container\">\n      <div class=\"chart-title\">KP Index Evolution<\/div>\n      <canvas id=\"kpChart\"><\/canvas>\n    <\/div>\n    <div class=\"chart-container\">\n      <div class=\"chart-title\">Solar Wind Speed (km\/s)<\/div>\n      <canvas id=\"windChart\"><\/canvas>\n    <\/div>\n    <div class=\"chart-container\">\n      <div class=\"chart-title\">X-ray Flux (W\/m\u00b2)<\/div>\n      <canvas id=\"fluxChart\"><\/canvas>\n    <\/div>\n    <div class=\"chart-container\">\n      <div class=\"chart-title\">Space Weather Alerts Count<\/div>\n      <canvas id=\"alertChart\"><\/canvas>\n    <\/div>\n  <\/div>\n\n  <!-- HISTORY TABLE -->\n  <h2>\ud83d\udccb Historical Data Table<\/h2>\n  <div style=\"overflow-x:auto;\">\n    <table class=\"history-table\">\n      <thead>\n        <tr>\n          <th>Date &amp; Time (UTC)<\/th>\n          <th>KP Index<\/th>\n          <th>Solar Wind Speed<\/th>\n          <th>X-ray Flux<\/th>\n          <th>Flare Class<\/th>\n          <th>Alerts<\/th>\n        <\/tr>\n      <\/thead>\n      <tbody id=\"historyBody\">\n        <tr><td colspan=\"6\" style=\"text-align:center;padding:20px;color:#999;\">Loading\u2026<\/td><\/tr>\n      <\/tbody>\n    <\/table>\n  <\/div>\n\n  <div class=\"sources\">\n    <strong>Data Sources:<\/strong><br>\n    \u2022 NOAA SWPC Planetary K-index API \u2014 <code>services.swpc.noaa.gov\/products\/noaa-planetary-k-index.json<\/code><br>\n    \u2022 NOAA SWPC Solar Wind Plasma \u2014 <code>services.swpc.noaa.gov\/products\/solar-wind\/plasma.json<\/code><br>\n    \u2022 NOAA SWPC IMF \u2014 <code>services.swpc.noaa.gov\/products\/solar-wind\/mag.json<\/code><br>\n    \u2022 NOAA SWPC GOES X-ray \u2014 <code>services.swpc.noaa.gov\/json\/goes\/primary\/xrays-6-hour.json<\/code><br>\n    \u2022 NOAA SWPC Alerts \u2014 <code>services.swpc.noaa.gov\/products\/alerts.json<\/code>\n  <\/div>\n\n  <p class=\"timestamp\" id=\"collectTime\">\ud83d\udd52 Data collected: \u2014<\/p>\n<\/div>\n\n<script>\n\/\/ \u2500\u2500\u2500 NOAA API endpoints \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst API = {\n  kp:      'https:\/\/services.swpc.noaa.gov\/products\/noaa-planetary-k-index.json',\n  plasma:  'https:\/\/services.swpc.noaa.gov\/products\/solar-wind\/plasma.json',\n  mag:     'https:\/\/services.swpc.noaa.gov\/products\/solar-wind\/mag.json',\n  xray:    'https:\/\/services.swpc.noaa.gov\/json\/goes\/primary\/xrays-6-hour.json',\n  flares:  'https:\/\/services.swpc.noaa.gov\/json\/solar_regions.json',\n  alerts:  'https:\/\/services.swpc.noaa.gov\/products\/alerts.json',\n  forecast:'https:\/\/services.swpc.noaa.gov\/products\/noaa-scales-overview.json',\n};\n\n\/\/ \u2500\u2500\u2500 Chart instances \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet charts = {};\n\nconst baseChartOpts = {\n  responsive: true,\n  maintainAspectRatio: false,\n  animation: { duration: 600 },\n  plugins: { legend: { display: false } },\n  scales: { x: { display: false }, y: { beginAtZero: false } },\n};\n\nfunction initCharts() {\n  const mk = (id, type, color, opts) => {\n    const ctx = document.getElementById(id).getContext('2d');\n    return new Chart(ctx, {\n      type,\n      data: { labels: [], datasets: [{ data: [], borderColor: color, backgroundColor: color.replace(')', ',0.15)').replace('rgb','rgba'), fill: true, tension: 0.4 }] },\n      options: opts || { ...baseChartOpts },\n    });\n  };\n\n  charts.kp    = mk('kpChart',   'line', 'rgb(21,101,192)');\n  charts.wind  = mk('windChart', 'line', 'rgb(76,175,80)');\n  charts.flux  = mk('fluxChart', 'line', 'rgb(245,124,0)', {\n    ...baseChartOpts,\n    scales: { x: { display: false }, y: { type: 'logarithmic', beginAtZero: false } },\n  });\n  charts.alert = mk('alertChart','bar',  'rgb(255,152,0)', {\n    ...baseChartOpts,\n    scales: { x: { display: false }, y: { beginAtZero: true } },\n  });\n  \/\/ bar dataset styling\n  charts.alert.data.datasets[0].fill = false;\n  charts.alert.data.datasets[0].backgroundColor = 'rgba(255,152,0,0.7)';\n}\n\nfunction updateChart(chart, labels, data) {\n  chart.data.labels = labels;\n  chart.data.datasets[0].data = data;\n  chart.update('none');\n}\n\n\/\/ \u2500\u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function fetchJSON(url) {\n  const r = await fetch(url, { cache: 'no-store' });\n  if (!r.ok) throw new Error(`HTTP ${r.status} for ${url}`);\n  return r.json();\n}\n\nfunction fmtTime(str) {\n  \/\/ Normalise \"2026-03-13 13:10:00.000\" \u2192 \"2026-03-13 13:10 UTC\"\n  return str ? str.replace('T', ' ').replace(\/\\.\\d+$\/, '').replace(\/ UTC$\/, '') + ' UTC' : 'N\/A';\n}\n\nfunction kpColor(val) {\n  const v = Math.floor(val);\n  if (v <= 1) return '#4caf50';\n  if (v <= 3) return '#8bc34a';\n  if (v === 4) return '#ffc107';\n  if (v === 5) return '#ff9800';\n  if (v <= 7) return '#f44336';\n  return '#9c27b0';\n}\n\nfunction classifyKp(v) {\n  if (v < 4) return 'Quiet';\n  if (v < 5) return 'Unsettled\/Active';\n  if (v < 6) return 'Minor Storm (G1)';\n  if (v < 7) return 'Moderate Storm (G2)';\n  if (v < 8) return 'Strong Storm (G3)';\n  if (v < 9) return 'Severe Storm (G4)';\n  return 'Extreme Storm (G5)';\n}\n\nfunction flareClass(flux) {\n  if (!flux || flux <= 0) return 'N\/A';\n  if (flux < 1e-7) return 'A';\n  if (flux < 1e-6) return 'B';\n  if (flux < 1e-5) return 'C';\n  if (flux < 1e-4) return 'M';\n  return 'X';\n}\n\n\/\/ \u2500\u2500\u2500 Fetch all data \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet historyLog = []; \/\/ rolling log entries\n\nasync function fetchAll() {\n  document.getElementById('spinner').style.display = 'block';\n\n  try {\n    const [kpRaw, plasmaRaw, magRaw, xrayRaw, alertsRaw] = await Promise.allSettled([\n      fetchJSON(API.kp),\n      fetchJSON(API.plasma),\n      fetchJSON(API.mag),\n      fetchJSON(API.xray),\n      fetchJSON(API.alerts),\n    ]);\n\n    \/\/ \u2500\u2500 KP \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n    let currentKp = null, kpTime = null;\n    let kpLabels = [], kpVals = [];\n\n    if (kpRaw.status === 'fulfilled') {\n      const kpData = kpRaw.value;\n      \/\/ First row is header: [\"time_tag\",\"kp\",\"kp_fraction\",\"kp_index\"]\n      const rows = kpData.slice(1).filter(r => r[1] !== null && r[1] !== '');\n      \/\/ Use last 72 points for chart\n      const recent = rows.slice(-72);\n      kpLabels = recent.map(r => r[0]);\n      kpVals   = recent.map(r => parseFloat(r[1]) || null);\n\n      if (rows.length) {\n        const last = rows[rows.length - 1];\n        currentKp = parseFloat(last[1]);\n        kpTime    = last[0];\n      }\n    }\n\n    if (currentKp !== null) {\n      const col = kpColor(currentKp);\n      document.getElementById('kpValue').textContent = `KP ${currentKp.toFixed(2)}  (${classifyKp(currentKp)})`;\n      document.getElementById('kpValue').style.color = col;\n      document.getElementById('kpMeta').textContent  = `Last updated: ${fmtTime(kpTime)}`;\n      document.getElementById('kpDot').className = 'status-indicator status-ok';\n    } else {\n      document.getElementById('kpValue').textContent = 'KP index unavailable';\n      document.getElementById('kpDot').className = 'status-indicator status-error';\n    }\n\n    \/\/ KP forecast card \u2014 simple text\n    document.getElementById('kpForecastValue').textContent =\n      currentKp !== null ? `Storm level: ${classifyKp(currentKp)}` : 'Unavailable';\n\n    updateChart(charts.kp, kpLabels, kpVals);\n\n    \/\/ \u2500\u2500 Solar Wind Plasma \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n    let windSpeed = null, windDens = null, windTime = null;\n    let windLabels = [], windVals = [];\n\n    if (plasmaRaw.status === 'fulfilled') {\n      const pd = plasmaRaw.value;\n      const rows = pd.slice(1).filter(r => r[1] !== null);\n      const recent = rows.slice(-72);\n      windLabels = recent.map(r => r[0]);\n      windVals   = recent.map(r => parseFloat(r[2]) || null); \/\/ speed column\n\n      if (rows.length) {\n        const last = rows[rows.length - 1];\n        windDens  = parseFloat(last[1]);\n        windSpeed = parseFloat(last[2]);\n        windTime  = last[0];\n      }\n    }\n\n    if (windSpeed !== null) {\n      document.getElementById('windSpeed').textContent   = `${windSpeed.toFixed(1)} km\/s`;\n      document.getElementById('windDensity').textContent = `Density: ${windDens.toFixed(2)} p\/cm\u00b3`;\n      document.getElementById('windMeta').textContent    = `Last updated: ${fmtTime(windTime)}`;\n      document.getElementById('windDot').className = 'status-indicator status-ok';\n    } else {\n      document.getElementById('windSpeed').textContent = 'Solar wind data unavailable';\n      document.getElementById('windDot').className = 'status-indicator status-error';\n    }\n\n    updateChart(charts.wind, windLabels, windVals);\n\n    \/\/ \u2500\u2500 IMF Magnetic Field \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n    let magBt = null, magBz = null, magTime = null;\n\n    if (magRaw.status === 'fulfilled') {\n      const md = magRaw.value;\n      const rows = md.slice(1).filter(r => r[1] !== null);\n      if (rows.length) {\n        const last = rows[rows.length - 1];\n        magBt   = parseFloat(last[6]); \/\/ bt\n        magBz   = parseFloat(last[3]); \/\/ bz_gsm\n        magTime = last[0];\n      }\n    }\n\n    if (magBt !== null) {\n      document.getElementById('magBt').textContent  = `Bt ${magBt.toFixed(1)} nT`;\n      document.getElementById('magBz').textContent  = `Bz: ${magBz.toFixed(1)} nT ${magBz < 0 ? '(southward \u2b07)' : '(northward \u2b06)'}`;\n      document.getElementById('magMeta').textContent = `Last updated: ${fmtTime(magTime)}`;\n    } else {\n      document.getElementById('magBt').textContent = 'IMF data unavailable';\n    }\n\n    \/\/ \u2500\u2500 X-ray Flux \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n    let fluxVal = null, fluxTime = null;\n    let fluxLabels = [], fluxVals = [];\n\n    if (xrayRaw.status === 'fulfilled') {\n      const xd = xrayRaw.value;\n      \/\/ Filter for 1-8 Angstrom band (energy=\"0.1-0.8nm\" or similar)\n      const longBand = xd.filter(r => r.energy === '0.1-0.8nm' || r.energy === '1-8A');\n      const src = longBand.length ? longBand : xd;\n      const recent = src.slice(-72);\n      fluxLabels = recent.map(r => r.time_tag);\n      fluxVals   = recent.map(r => parseFloat(r.flux) || null);\n\n      if (src.length) {\n        const last = src[src.length - 1];\n        fluxVal  = parseFloat(last.flux);\n        fluxTime = last.time_tag;\n      }\n    }\n\n    if (fluxVal !== null && fluxVal > 0) {\n      const cls = flareClass(fluxVal);\n      document.getElementById('fluxValue').textContent = fluxVal.toExponential(2) + ' W\/m\u00b2';\n      document.getElementById('fluxClass').textContent = `Classification: ${cls}-class`;\n      document.getElementById('fluxMeta').textContent  = `Last updated: ${fmtTime(fluxTime)}`;\n      document.getElementById('fluxDot').className = 'status-indicator status-ok';\n    } else {\n      document.getElementById('fluxValue').textContent = 'N\/A';\n      document.getElementById('fluxClass').textContent = 'Classification: Quiet';\n      document.getElementById('fluxMeta').textContent  = 'Last updated: N\/A';\n    }\n\n    \/\/ Flare card \u2014 show recent high-class events from flux data\n    const highFlux = fluxVals.filter(v => v && v >= 1e-5);\n    document.getElementById('flareValue').textContent =\n      highFlux.length ? `${highFlux.length} C\/M\/X event(s) in last 6h` : 'No significant events in last 6h';\n\n    updateChart(charts.flux, fluxLabels, fluxVals);\n\n    \/\/ \u2500\u2500 Alerts \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n    let activeAlerts = [];\n\n    if (alertsRaw.status === 'fulfilled') {\n      activeAlerts = alertsRaw.value;\n    }\n\n    document.getElementById('alertCount').textContent = `${activeAlerts.length} Active Alert(s)`;\n\n    const ac = document.getElementById('alertsContainer');\n    ac.innerHTML = '';\n    const show = activeAlerts.slice(0, 8);\n    show.forEach(a => {\n      const div = document.createElement('div');\n      div.className = 'alert-item';\n      const msg  = (a.message || '').slice(0, 300) + (a.message && a.message.length > 300 ? '\u2026' : '');\n      const time = a.issue_datetime || '';\n      div.innerHTML = `<strong>Alert:<\/strong> ${msg}<small>Time: ${fmtTime(time)}<\/small>`;\n      ac.appendChild(div);\n    });\n    if (!show.length) ac.innerHTML = '<p style=\"color:#999;margin-top:10px;\">No active alerts.<\/p>';\n\n    \/\/ Alert count chart \u2014 stub with a single bar for now (history not available from single call)\n    updateChart(charts.alert, ['Now'], [activeAlerts.length]);\n\n    \/\/ \u2500\u2500 History table \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n    const now = new Date();\n    const nowStr = now.toISOString().replace('T', ' ').replace(\/\\.\\d+Z$\/, '') + ' UTC';\n    const entry = {\n      time:  nowStr,\n      kp:    currentKp !== null ? `KP ${currentKp.toFixed(2)}` : 'KP index unavailable',\n      wind:  windSpeed !== null ? `${windSpeed.toFixed(1)} km\/s` : 'Solar wind data unavailable',\n      flux:  fluxVal   !== null && fluxVal > 0 ? fluxVal.toExponential(2) + ' W\/m\u00b2' : 'N\/A',\n      cls:   fluxVal   !== null && fluxVal > 0 ? flareClass(fluxVal) : 'Quiet',\n      alerts: activeAlerts.length,\n    };\n\n    \/\/ Keep last 20 entries\n    historyLog.unshift(entry);\n    if (historyLog.length > 20) historyLog = historyLog.slice(0, 20);\n    renderTable();\n\n    \/\/ Timestamp\n    document.getElementById('collectTime').textContent = `\ud83d\udd52 Data collected: ${nowStr}`;\n\n  } catch (err) {\n    console.error('fetchAll error', err);\n  }\n\n  document.getElementById('spinner').style.display = 'none';\n}\n\nfunction renderTable() {\n  const tbody = document.getElementById('historyBody');\n  if (!historyLog.length) {\n    tbody.innerHTML = '<tr><td colspan=\"6\" style=\"text-align:center;color:#999\">No data yet.<\/td><\/tr>';\n    return;\n  }\n  tbody.innerHTML = historyLog.map(e => `\n    <tr>\n      <td>${e.time}<\/td>\n      <td>${e.kp}<\/td>\n      <td>${e.wind}<\/td>\n      <td>${e.flux}<\/td>\n      <td>${e.cls}<\/td>\n      <td>${e.alerts}<\/td>\n    <\/tr>`).join('');\n}\n\n\/\/ \u2500\u2500\u2500 Countdown & auto-refresh \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst INTERVAL = 300; \/\/ seconds\nlet remaining = INTERVAL;\n\nsetInterval(() => {\n  remaining--;\n  if (remaining <= 0) {\n    remaining = INTERVAL;\n    fetchAll();\n  }\n  document.getElementById('countdown').textContent = remaining;\n}, 1000);\n\n\/\/ \u2500\u2500\u2500 Init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ninitCharts();\nfetchAll();\n<\/script>\n<\/body>\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>Space Weather API Data Tracker \ud83d\udef0\ufe0f Space Weather API Data Tracker Auto-refresh in 300s \u27f3 Refresh Now \ud83d\udce1 API Data Sources: \u2022 NOAA Space Weather Prediction Center (services.swpc.noaa.gov) \u2022 GOES Satellite Real-time Plasma &#038; X-ray Data \u2022 NOAA Planetary K-index (1-minute) \u2022 Solar Energetic Particle &amp; Alert Feed Loading live data\u2026 \ud83c\udf0d Geomagnetic Activity KP [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-1116","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/www.datura.website\/index.php?rest_route=\/wp\/v2\/pages\/1116","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.datura.website\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.datura.website\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.datura.website\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.datura.website\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1116"}],"version-history":[{"count":1,"href":"https:\/\/www.datura.website\/index.php?rest_route=\/wp\/v2\/pages\/1116\/revisions"}],"predecessor-version":[{"id":1117,"href":"https:\/\/www.datura.website\/index.php?rest_route=\/wp\/v2\/pages\/1116\/revisions\/1117"}],"wp:attachment":[{"href":"https:\/\/www.datura.website\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1116"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}