<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ .Title }}</title>
    <link rel="stylesheet" href="/static/css/tailwind.min.css">
    <script src="/static/js/plotly-2.27.0.min.js"></script>
    <style>
        body { font-family: Arial, sans-serif; margin: 0; }
        .content-narrow { width: 86%; max-width: 1200px; margin: 0 auto; }
        .card { background: #fff; border: 1px solid #e5e7eb; border-radius: 6px; padding: 20px; }
        .plot-box { width: clamp(320px, 80vw, 720px); margin: 0 auto; aspect-ratio: 1 / 1; overflow: hidden; }
        @supports not (aspect-ratio: 1 / 1) { .plot-box { height: 520px; } }
    </style>
</head>
<body>
{{ template "header" . }}

<div class="content-narrow p-4">
  <div class="card mb-4">
    <div class="text-lg font-semibold mb-2">历史时次查询</div>
    <div class="w-full flex flex-col items-start gap-2">
      <div class="flex flex-wrap items-center gap-2">
        <input id="tsStart" type="datetime-local" class="border rounded px-2 py-1"/>
        <span>至</span>
        <input id="tsEnd" type="datetime-local" class="border rounded px-2 py-1"/>
        <button id="tsQuery" class="px-3 py-1 bg-blue-600 text-white rounded">查询</button>
      </div>
      <div>
        <select id="timeSelect" class="border rounded px-2 py-1 min-w-[260px]">
          <option value="">最新</option>
        </select>
      </div>
    </div>
  </div>
  <div class="card mb-4">
    <div class="text-lg font-semibold mb-2">7/40/104 瓦片信息</div>
    <div class="text-sm space-y-1">
      <div>时间：<span id="dt" class="font-mono"></span></div>
      <div>索引：z=<span id="z"></span> / y=<span id="y"></span> / x=<span id="x"></span></div>
      <div>尺寸：<span id="size"></span></div>
      <div>边界：W=<span id="west"></span>，S=<span id="south"></span>，E=<span id="east"></span>，N=<span id="north"></span></div>
      <div>分辨率（度/像素）：<span id="res"></span></div>
    </div>
  </div>

  <div class="card mb-4">
    <div class="text-lg font-semibold mb-2">雷达站气象（番禺雷达站）</div>
    <div id="rtInfo" class="text-sm grid grid-cols-2 gap-y-1 gap-x-6">
      <div>站点：<span id="rt_alias"></span></div>
      <div>位置：<span id="rt_lat"></span>，<span id="rt_lon"></span></div>
      <div>时间：<span id="rt_dt" class="font-mono"></span></div>
      <div>温度：<span id="rt_t"></span> ℃</div>
      <div>湿度：<span id="rt_h"></span></div>
      <div>云量：<span id="rt_c"></span></div>
      <div>能见度：<span id="rt_vis"></span> km</div>
      <div>下行短波：<span id="rt_dswrf"></span> W/m²</div>
      <div>风速：<span id="rt_ws"></span> m/s</div>
      <div>风向：<span id="rt_wd"></span> °</div>
      <div>气压：<span id="rt_p"></span> Pa</div>
    </div>
  </div>

  <div class="card mb-4">
    <div class="text-lg font-semibold mb-2">未来 3H 预报</div>
    <div id="sectorInfo" class="text-sm">
      <div id="sectorStatus">计算中…</div>
      <div id="sectorDetail" class="mt-1 hidden">
        最近距离：<span id="sectorDist"></span> km；预计到达时间：<span id="sectorETA"></span><br>
        位置：lat=<span id="sectorLat"></span>，lon=<span id="sectorLon"></span> 组合反射率：<span id="sectorDBZ"></span> dBZ
      </div>
    </div>
  </div>

  <div class="card" style="width:100%;">
    <div class="text-lg font-semibold mb-2 flex items-center justify-between">
      <div>
        雷达组合反射率 <span id="titleDt" class="text-gray-500 text-sm"></span>
      </div>
      <div class="flex items-center gap-2 text-sm">
        <button id="btnPrev" class="px-2 py-1 border rounded">上一时次</button>
        <span id="countInfo" class="text-gray-600">共0条，第0条</span>
        <button id="btnNext" class="px-2 py-1 border rounded">下一时次</button>
      </div>
    </div>
    <div class="w-full flex justify-center mb-2">
      <input id="timeSlider" type="range" min="0" max="0" value="0" step="1" class="slider slider-horizontal w-64" />
    </div>
    <div id="radarPlot" class="plot-box"></div>
  </div>

  <div class="card mt-4" style="width:100%;">
    <div class="text-lg font-semibold mb-2">正方形裁减区域</div>
    <div id="squarePlot" class="plot-box"></div>
  </div>
</div>

<script>
  const ST_ALIAS = '番禺雷达站';
  const ST_LAT = 23.0225, ST_LON = 113.3313;
  const TILE_Z = 7, TILE_Y = 40, TILE_X = 104; // 使用广州瓦片

  let gTileValues = null, gXs = null, gYs = null;
  let gWindFromDeg = null, gWindSpeedMS = null;
  let gTimes = [];
  let gCurrentIdx = -1;
  let gTileDT = null; // 当前瓦片时间（用于 ETA）

  function toRad(d){ return d * Math.PI / 180; }
  function toDeg(r){ return r * 180 / Math.PI; }
  function haversine(lat1, lon1, lat2, lon2){
    const R = 6371000; const dLat = toRad(lat2-lat1); const dLon = toRad(lon2-lon1);
    const a = Math.sin(dLat/2)**2 + Math.cos(toRad(lat1))*Math.cos(toRad(lat2))*Math.sin(dLon/2)**2;
    return 2*R*Math.asin(Math.sqrt(a));
  }
  function bearingDeg(lat1, lon1, lat2, lon2){
    const φ1=toRad(lat1), φ2=toRad(lat2), λ1=toRad(lon1), λ2=toRad(lon2);
    const y=Math.sin(λ2-λ1)*Math.cos(φ2);
    const x=Math.cos(φ1)*Math.sin(φ2)-Math.sin(φ1)*Math.cos(φ2)*Math.cos(λ2-λ1);
    const θ=Math.atan2(y,x); return (toDeg(θ)+360)%360;
  }
  function angDiff(a,b){ return Math.abs(((a-b+540)%360)-180); }
  function destPoint(lat,lon,brg,dist){
    const R=6371000; const δ=dist/R; const θ=toRad(brg);
    const φ1=toRad(lat), λ1=toRad(lon);
    const φ2=Math.asin(Math.sin(φ1)*Math.cos(δ)+Math.cos(φ1)*Math.sin(δ)*Math.cos(θ));
    const λ2=λ1+Math.atan2(Math.sin(θ)*Math.sin(δ)*Math.cos(φ1),Math.cos(δ)-Math.sin(φ1)*Math.sin(φ2));
    return {lat:toDeg(φ2), lon:((toDeg(λ2)+540)%360)-180};
  }

  async function loadLatestTile(){
    const res = await fetch(`/api/radar/latest?z=${TILE_Z}&y=${TILE_Y}&x=${TILE_X}`);
    if(!res.ok) throw new Error('加载最新瓦片失败');
    const t = await res.json(); await renderTile(t);
  }
  async function loadTileAt(dtStr){
    const res = await fetch(`/api/radar/at?z=${TILE_Z}&y=${TILE_Y}&x=${TILE_X}&dt=${encodeURIComponent(dtStr)}`);
    if(!res.ok) throw new Error('加载指定时间瓦片失败');
    const t = await res.json(); await renderTile(t, dtStr);
  }

  function fmtDTLocal(dt){ const pad=(n)=>String(n).padStart(2,'0'); return `${dt.getFullYear()}-${pad(dt.getMonth()+1)}-${pad(dt.getDate())}T${pad(dt.getHours())}:${pad(dt.getMinutes())}`; }
  function fromDTLocalInput(s){ if(!s) return null; const t=new Date(s.replace('T','-').replace(/-/g,'/')); const pad=(n)=>String(n).padStart(2,'0'); return `${t.getFullYear()}-${pad(t.getMonth()+1)}-${pad(t.getDate())} ${pad(t.getHours())}:${pad(t.getMinutes())}:00`; }

  async function populateTimes(fromStr, toStr){
    try{
      let url = `/api/radar/times?z=${TILE_Z}&y=${TILE_Y}&x=${TILE_X}`;
      if(fromStr && toStr){ url += `&from=${encodeURIComponent(fromStr)}&to=${encodeURIComponent(toStr)}`; } else { url += `&limit=60`; }
      const res = await fetch(url); if(!res.ok) return; const j = await res.json();
      const sel = document.getElementById('timeSelect'); while (sel.options.length > 1) sel.remove(1);
      gTimes = j.times || [];
      gTimes.forEach(dt=>{ const opt=document.createElement('option'); opt.value=dt; opt.textContent=dt; sel.appendChild(opt); });
      if (gTimes.length>0 && gCurrentIdx<0){ sel.value=gTimes[0]; gCurrentIdx=0; }
      updateCountAndButtons(); updateSlider();
    }catch{}
  }

  async function renderTile(t, forcedDt){
    const fmt5=(n)=>Number(n).toFixed(5);
    document.getElementById('dt').textContent=t.dt;
    document.getElementById('z').textContent=t.z;
    document.getElementById('y').textContent=t.y;
    document.getElementById('x').textContent=t.x;
    document.getElementById('size').textContent=`${t.width} × ${t.height}`;
    document.getElementById('west').textContent=fmt5(t.west);
    document.getElementById('south').textContent=fmt5(t.south);
    document.getElementById('east').textContent=fmt5(t.east);
    document.getElementById('north').textContent=fmt5(t.north);
    document.getElementById('res').textContent=fmt5(t.res_deg);
    document.getElementById('titleDt').textContent=`（${t.dt}）`;
    const selBox=document.getElementById('timeSelect'); for(let i=0;i<selBox.options.length;i++){ if(selBox.options[i].value===t.dt){ selBox.selectedIndex=i; break; } }
    gCurrentIdx=gTimes.indexOf(t.dt); updateCountAndButtons(); updateSlider();

    const w=t.width, h=t.height; gTileValues=t.values;
    const xs=new Array(w), ys=new Array(h);
    const stepX=(t.east-t.west)/w, stepY=(t.north-t.south)/h;
    for(let i=0;i<w;i++){ xs[i]=t.west+(i+0.5)*stepX; }
    for(let j=0;j<h;j++){ ys[j]=t.south+(j+0.5)*stepY; }
    gXs=xs; gYs=ys;

    const colors=["#0000FF","#00BFFF","#00FFFF","#7FFFD4","#7CFC00","#ADFF2F","#FFFF00","#FFD700","#FFA500","#FF8C00","#FF4500","#FF0000","#DC143C","#C71585","#8B008B"];
    const scale=[]; for(let i=0;i<colors.length;i++){ const tpos=colors.length===1?0:i/(colors.length-1); scale.push([tpos,colors[i]]); }
    const zBins=[], custom=[];
    for(let r=0;r<h;r++){
      const row=gTileValues[r]; const rowBins=[], rowCustom=[];
      for(let c=0;c<w;c++){
        const val=row[c]; if(val==null){ rowBins.push(null); rowCustom.push([r,c,null]); continue; }
        let dbz=Number(val); if(dbz<0)dbz=0; if(dbz>75)dbz=75; let bin=Math.floor(dbz/5); if(bin>=15)bin=14;
        rowBins.push(bin); rowCustom.push([r,c,dbz]);
      }
      zBins.push(rowBins); custom.push(rowCustom);
    }
    const heatTrace={ type:'heatmap', x:xs, y:ys, z:zBins, customdata:custom, colorscale:scale, zmin:0, zmax:14,
      colorbar:{orientation:'h',x:0.5,y:-0.12,xanchor:'center',yanchor:'top',len:0.8,thickness:16,title:{text:'dBZ',side:'bottom'},
        tickmode:'array',tickvals:Array.from({length:15},(_,i)=>i),ticktext:Array.from({length:15},(_,i)=>String(i*5))},
      hovertemplate:'lon=%{x:.3f}, lat=%{y:.3f}<br>dBZ=%{customdata[2]:.1f}<extra></extra>' };
    const layout={ autosize:true, margin:{l:40,r:8,t:8,b:90}, xaxis:{title:'经度',tickformat:'.2f',zeroline:false,constrain:'domain',automargin:true},
      yaxis:{title:{text:'纬度',standoff:12},tickformat:'.2f',zeroline:false,scaleanchor:'x',scaleratio:1,constrain:'domain',automargin:true} };
    const data=[heatTrace];
    if(gWindFromDeg!==null && gWindSpeedMS>0){
      const half=30, samples=64, start=gWindFromDeg-half, end=gWindFromDeg+half, rangeM=gWindSpeedMS*3*3600;
      const xsFan=[ST_LON], ysFan=[ST_LAT];
      for(let i=0;i<=samples;i++){ const θ=start+(end-start)*(i/samples); const p=destPoint(ST_LAT,ST_LON,((θ%360)+360)%360,rangeM); xsFan.push(p.lon); ysFan.push(p.lat); }
      xsFan.push(ST_LON); ysFan.push(ST_LAT);
      data.push({ type:'scatter', mode:'lines', x:xsFan, y:ysFan, line:{color:'#FFFFFF',width:2,dash:'dash'}, fill:'toself', fillcolor:'rgba(255,255,255,0.18)', hoverinfo:'skip', showlegend:false });
    }
    const plotEl=document.getElementById('radarPlot');
    Plotly.newPlot(plotEl, data, layout, {responsive:true, displayModeBar:false}).then(()=>{ const s=plotEl.clientWidth; Plotly.relayout(plotEl,{height:s}); });
    window.addEventListener('resize',()=>{ const s=plotEl.clientWidth; Plotly.relayout(plotEl,{height:s}); Plotly.Plots.resize(plotEl); });

    try{
      const base = forcedDt ? new Date(forcedDt.replace(/-/g,'/')) : new Date(t.dt.replace(/-/g,'/'));
      gTileDT = base; // 记录瓦片时间
      await fetchRealtimeWithFallback(base);
      maybeCalcSector();
      maybePlotSquare();
    }catch(e){ await loadRealtimeLatest(); maybeCalcSector(); maybePlotSquare(); }
  }

  async function fetchRealtimeWithFallback(base){
    const pad=(n)=>String(n).padStart(2,'0');
    const ceil10=new Date(base); const m=base.getMinutes(); const up=(Math.floor(m/10)*10+10)%60; ceil10.setMinutes(up,0,0); if(up===0){ ceil10.setHours(base.getHours()+1); }
    const dtStr=`${ceil10.getFullYear()}-${pad(ceil10.getMonth()+1)}-${pad(ceil10.getDate())} ${pad(ceil10.getHours())}:${pad(ceil10.getMinutes())}:00`;
    let res=await fetch(`/api/radar/weather_at?alias=${encodeURIComponent(ST_ALIAS)}&dt=${encodeURIComponent(dtStr)}`);
    if(res.ok){ const r=await res.json(); fillRealtime(r); return; }
    // 番禺此10分钟缺测，则用广州相同10分钟时次
    res=await fetch(`/api/radar/weather_at?alias=${encodeURIComponent('广州雷达站')}&dt=${encodeURIComponent(dtStr)}`);
    if(res.ok){ const r=await res.json(); fillRealtime(r); return; }
    // 最后退回番禺最新
    await loadRealtimeLatest();
  }

  async function loadRealtimeLatest(){
    const res=await fetch(`/api/radar/weather_latest?alias=${encodeURIComponent(ST_ALIAS)}`);
    if(!res.ok) throw new Error('加载实时气象失败');
    const r=await res.json(); fillRealtime(r);
  }
  function fillRealtime(r){
    const f2=(n)=>Number(n).toFixed(2), f4=(n)=>Number(n).toFixed(4);
    document.getElementById('rt_alias').textContent=r.alias;
    document.getElementById('rt_lat').textContent=f4(r.lat);
    document.getElementById('rt_lon').textContent=f4(r.lon);
    document.getElementById('rt_dt').textContent=r.dt;
    document.getElementById('rt_t').textContent=f2(r.temperature);
    document.getElementById('rt_h').textContent=f2(r.humidity);
    document.getElementById('rt_c').textContent=f2(r.cloudrate);
    document.getElementById('rt_vis').textContent=f2(r.visibility);
    document.getElementById('rt_dswrf').textContent=f2(r.dswrf);
    document.getElementById('rt_ws').textContent=f2(r.wind_speed);
    document.getElementById('rt_wd').textContent=f2(r.wind_direction);
    document.getElementById('rt_p').textContent=f2(r.pressure);
    gWindFromDeg=Number(r.wind_direction); gWindSpeedMS=Number(r.wind_speed);
  }

  function maybeCalcSector(){
    try{
      if(!gTileValues || !gXs || !gYs || gWindFromDeg===null || gWindSpeedMS===null) return;
      const halfAngle=30; const rangeM=gWindSpeedMS*3*3600; let best=null;
      const h=gTileValues.length, w=gTileValues[0].length;
      for(let r=0;r<h;r++){
        const lat=gYs[r]; for(let c=0;c<w;c++){
          const val=gTileValues[r][c]; if(val==null) continue; const dbz=Number(val); if(!(dbz>=40)) continue;
          const lon=gXs[c]; const dist=haversine(ST_LAT,ST_LON,lat,lon); if(dist>rangeM) continue;
          const brg=bearingDeg(ST_LAT,ST_LON,lat,lon); if(angDiff(brg,gWindFromDeg)>halfAngle) continue;
          if(!best || dist<best.dist){ best={dist,lat,lon,dbz}; }
        }
      }
      const statusEl=document.getElementById('sectorStatus'); const detailEl=document.getElementById('sectorDetail');
      if(!best){ statusEl.textContent='无（≥40 dBZ）'; detailEl.classList.add('hidden'); }
      else {
        const etaSec=best.dist/gWindSpeedMS;
        const base = gTileDT instanceof Date ? gTileDT : new Date();
        const eta=new Date(base.getTime()+etaSec*1000);
        const pad=(n)=>String(n).padStart(2,'0'); const etaStr=`${eta.getFullYear()}-${pad(eta.getMonth()+1)}-${pad(eta.getDate())} ${pad(eta.getHours())}:${pad(eta.getMinutes())}`;
        document.getElementById('sectorDist').textContent=(best.dist/1000).toFixed(1);
        document.getElementById('sectorETA').textContent=etaStr;
        document.getElementById('sectorLat').textContent=Number(best.lat).toFixed(4);
        document.getElementById('sectorLon').textContent=Number(best.lon).toFixed(4);
        document.getElementById('sectorDBZ').textContent=Number(best.dbz).toFixed(1);
        statusEl.textContent='三小时内可能有降雨（≥40 dBZ ）'; detailEl.classList.remove('hidden');
      }
    }catch(e){ document.getElementById('sectorStatus').textContent='风险评估计算失败：'+e.message; }
  }

  function maybePlotSquare(){
    try{
      if(!gTileValues || !gXs || !gYs || gWindSpeedMS===null) return;
      const R=6371000; const rangeM=gWindSpeedMS*3*3600;
      const dLat=(rangeM/R)*(180/Math.PI);
      const dLon=(rangeM/(R*Math.cos(toRad(ST_LAT))))*(180/Math.PI);
      const latMin=ST_LAT-dLat, latMax=ST_LAT+dLat, lonMin=ST_LON-dLon, lonMax=ST_LON+dLon;
      const h=gYs.length, w=gXs.length;
      let rStart=0; while(rStart<h && gYs[rStart]<latMin) rStart++;
      let rEnd=h-1; while(rEnd>=0 && gYs[rEnd]>latMax) rEnd--;
      let cStart=0; while(cStart<w && gXs[cStart]<lonMin) cStart++;
      let cEnd=w-1; while(cEnd>=0 && gXs[cEnd]>lonMax) cEnd--;
      if(rStart>=rEnd || cStart>=cEnd){ document.getElementById('squarePlot').innerHTML='<div class="p-3 text-sm text-gray-600">正方形范围超出当前瓦片或无有效像元</div>'; return; }
      const xs=gXs.slice(cStart,cEnd+1), ys=gYs.slice(rStart,rEnd+1);
      const colors=["#0000FF","#00BFFF","#00FFFF","#7FFFD4","#7CFC00","#ADFF2F","#FFFF00","#FFD700","#FFA500","#FF8C00","#FF4500","#FF0000","#DC143C","#C71585","#8B008B"];
      const scale=[]; for(let i=0;i<colors.length;i++){ const tpos=colors.length===1?0:i/(colors.length-1); scale.push([tpos,colors[i]]); }
      const zBins=[], custom=[];
      for(let r=rStart;r<=rEnd;r++){
        const rowBins=[], rowCustom=[];
        for(let c=cStart;c<=cEnd;c++){
          const val=gTileValues[r][c]; if(val==null){ rowBins.push(null); rowCustom.push([r,c,null]); continue; }
          let dbz=Number(val); if(dbz<0)dbz=0; if(dbz>75)dbz=75; let bin=Math.floor(dbz/5); if(bin>=15)bin=14;
          rowBins.push(bin); rowCustom.push([r,c,dbz]);
        }
        zBins.push(rowBins); custom.push(rowCustom);
      }
      const data=[{ type:'heatmap', x:xs, y:ys, z:zBins, customdata:custom, colorscale:scale, zmin:0, zmax:14, hoverinfo:'skip', showscale:false }];
      if(gWindFromDeg!==null && gWindSpeedMS>0){
        const half=30,samples=64,start=gWindFromDeg-half,end=gWindFromDeg+half;
        const xsFan=[ST_LON], ysFan=[ST_LAT];
        for(let i=0;i<=samples;i++){ const θ=start+(end-start)*(i/samples); const p=destPoint(ST_LAT,ST_LON,((θ%360)+360)%360,rangeM); xsFan.push(p.lon); ysFan.push(p.lat); }
        xsFan.push(ST_LON); ysFan.push(ST_LAT);
        data.push({ type:'scatter', mode:'lines', x:xsFan, y:ysFan, line:{color:'#FFFFFF',width:2,dash:'dash'}, fill:'toself', fillcolor:'rgba(255,255,255,0.18)', hoverinfo:'skip', showlegend:false });
      }
      const layout={ autosize:true, margin:{l:40,r:8,t:8,b:90},
        xaxis:{title:'经度', tickformat:'.2f', zeroline:false, constrain:'domain', automargin:true, range:[lonMin,lonMax]},
        yaxis:{title:{text:'纬度', standoff:12}, tickformat:'.2f', zeroline:false, scaleanchor:'x', scaleratio:1, constrain:'domain', automargin:true, range:[latMin,latMax]},
        shapes:[{type:'rect',xref:'x',yref:'y',x0:lonMin,x1:lonMax,y0:latMin,y1:latMax,line:{color:'#111',width:1,dash:'dot'},fillcolor:'rgba(0,0,0,0)'}] };
      const el=document.getElementById('squarePlot'); Plotly.newPlot(el, data, layout, {responsive:true, displayModeBar:false}).then(()=>{ const s=el.clientWidth; Plotly.relayout(el,{height:s}); }); window.addEventListener('resize',()=>{ const s=el.clientWidth; Plotly.relayout(el,{height:s}); Plotly.Plots.resize(el); });
    }catch(e){ document.getElementById('squarePlot').innerHTML=`<div class="p-3 text-sm text-red-600 bg-red-50 border border-red-200 rounded">正方形热力图渲染失败：${e.message}</div>`; }
  }

  // 初始化默认时间范围与加载顺序
  (function initRange(){ const end=new Date(); const start=new Date(end.getTime()-3*3600*1000); document.getElementById('tsStart').value=fmtDTLocal(start); document.getElementById('tsEnd').value=fmtDTLocal(end); })();
  const startStr=fromDTLocalInput(document.getElementById('tsStart').value);
  const endStr=fromDTLocalInput(document.getElementById('tsEnd').value);
  loadRealtimeLatest().catch(()=>{});
  loadLatestTile().then(()=>populateTimes(startStr,endStr)).catch(err=>{ document.getElementById('radarPlot').innerHTML=`<div class="p-3 text-sm text-red-600 bg-red-50 border border-red-200 rounded">加载失败：${err.message}</div>`; });
  document.getElementById('timeSelect').addEventListener('change', async (e)=>{ const v=e.target.value; if(!v){ if(gTimes.length>0){ gCurrentIdx=0; await loadTileAt(gTimes[0]); } else { gCurrentIdx=-1; await loadLatestTile(); } } else { gCurrentIdx=gTimes.indexOf(v); await loadTileAt(v);} updateCountAndButtons(); updateSlider(); });
  document.getElementById('tsQuery').addEventListener('click', async ()=>{ const s=fromDTLocalInput(document.getElementById('tsStart').value); const e=fromDTLocalInput(document.getElementById('tsEnd').value); await populateTimes(s,e); });
  function updateCountAndButtons(){ const N=gTimes.length; const k=gCurrentIdx>=0?(gCurrentIdx+1):0; document.getElementById('countInfo').textContent=`共${N}条数据，-${k-1}时次`; const prev=document.getElementById('btnPrev'); const next=document.getElementById('btnNext'); prev.disabled=!(N>0 && gCurrentIdx>=0 && gCurrentIdx<N-1); next.disabled=!(N>0 && gCurrentIdx>0); }
  function updateSlider(){ const slider=document.getElementById('timeSlider'); const N=gTimes.length; slider.max=N>0?String(N-1):'0'; if(N>0 && gCurrentIdx>=0){ const sliderVal=(N-1)-gCurrentIdx; slider.value=String(sliderVal);} slider.disabled=N===0; }
  document.getElementById('btnPrev').addEventListener('click', async ()=>{ if(gTimes.length===0) return; if(gCurrentIdx<0) gCurrentIdx=0; if(gCurrentIdx<gTimes.length-1){ gCurrentIdx++; const dt=gTimes[gCurrentIdx]; document.getElementById('timeSelect').value=dt; await loadTileAt(dt);} updateCountAndButtons(); updateSlider(); });
  document.getElementById('btnNext').addEventListener('click', async ()=>{ if(gTimes.length===0) return; if(gCurrentIdx>0){ gCurrentIdx--; const dt=gTimes[gCurrentIdx]; document.getElementById('timeSelect').value=dt; await loadTileAt(dt);} updateCountAndButtons(); updateSlider(); });
  document.getElementById('timeSlider').addEventListener('input', async (e)=>{ const N=gTimes.length; if(N===0) return; const raw=parseInt(e.target.value,10); const sliderVal=Math.max(0,Math.min(N-1,isNaN(raw)?0:raw)); const idx=(N-1)-sliderVal; if(idx===gCurrentIdx) return; gCurrentIdx=idx; const dt=gTimes[gCurrentIdx]; document.getElementById('timeSelect').value=dt; await loadTileAt(dt); updateCountAndButtons(); });
</script>

</body>
</html>
