{"id":936,"date":"2025-11-20T00:04:16","date_gmt":"2025-11-19T16:04:16","guid":{"rendered":"https:\/\/explore2022.com\/?page_id=936"},"modified":"2026-05-31T09:21:30","modified_gmt":"2026-05-31T01:21:30","slug":"%e5%a4%a9%e6%b0%94%e6%9f%a5%e8%af%a2","status":"publish","type":"page","link":"https:\/\/www.explore2022.com\/?page_id=936","title":{"rendered":"\u5929\u6c14\u67e5\u8be2"},"content":{"rendered":"\n<div class=\"weather-embed\" style=\"position:relative;left:50%;right:50%;width:100vw;max-width:100vw;margin-left:-50vw;margin-right:-50vw;margin-top:0;margin-bottom:0;overflow:hidden;\">\n<iframe id=\"weather-app-frame\" title=\"\u5929\u6c14\u67e5\u8be2\" scrolling=\"no\" style=\"width:100%;min-height:100vh;border:0;display:block;overflow:hidden;\" srcdoc=\"&lt;!DOCTYPE html&gt;\n&lt;html lang=&quot;zh-CN&quot;&gt;\n&lt;head&gt;\n  &lt;meta charset=&quot;UTF-8&quot; \/&gt;\n  &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; \/&gt;\n  &lt;title&gt;\u4e2d\u56fd\u5929\u6c14 \u00b7 Windy Weather Map&lt;\/title&gt;\n  &lt;style&gt;\n    * { margin: 0; padding: 0; box-sizing: border-box; }\n    html, body { height: 100%; font-family: &#x27;PingFang SC&#x27;, &#x27;Microsoft YaHei&#x27;, system-ui, sans-serif; color: #fff; }\n    body {\n      background: linear-gradient(135deg, #0f2027 0%, #203a43 50%, #2c5364 100%);\n      display: flex; overflow: hidden;\n    }\n\n    #mapWrap { flex: 1; height: 100vh; position: relative; display: flex; flex-direction: column; }\n    #layerBar {\n      display: flex; flex-wrap: wrap; gap: 6px;\n      padding: 10px 14px;\n      background: rgba(15, 25, 40, 0.95);\n      border-bottom: 1px solid rgba(255,255,255,0.06);\n      z-index: 5;\n    }\n    .layer-btn {\n      padding: 6px 12px; border: 1px solid rgba(255,255,255,0.15); border-radius: 16px;\n      background: rgba(255,255,255,0.04); color: #fff; font-size: 0.78rem; cursor: pointer;\n      transition: all 0.15s;\n    }\n    .layer-btn:hover { background: rgba(255,255,255,0.1); }\n    .layer-btn.active { background: #4a90e2; border-color: #4a90e2; }\n    #windy { flex: 1; border: 0; background: #0e1626; }\n\n    #panel {\n      width: 380px; height: 100vh; overflow-y: auto;\n      background: rgba(15, 25, 40, 0.92); backdrop-filter: blur(10px);\n      padding: 20px; box-shadow: -4px 0 20px rgba(0,0,0,0.4);\n    }\n    .header h1 { font-size: 1.2rem; font-weight: 500; letter-spacing: 2px; opacity: 0.95; }\n    .header p { font-size: 0.78rem; opacity: 0.6; margin-top: 4px; }\n    .search { display: flex; gap: 6px; margin: 14px 0; }\n    .search input {\n      flex: 1; padding: 10px 12px; border: none; border-radius: 8px;\n      background: rgba(255,255,255,0.08); color: #fff; font-size: 0.9rem;\n    }\n    .search input::placeholder { color: rgba(255,255,255,0.5); }\n    .search button {\n      padding: 10px 16px; border: none; border-radius: 8px; cursor: pointer;\n      background: #4a90e2; color: #fff; font-size: 0.9rem;\n    }\n    .search button:hover { background: #357abd; }\n    .card {\n      background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.08);\n      border-radius: 12px; padding: 14px 16px; margin-bottom: 12px;\n    }\n    .card h3 { font-size: 0.78rem; font-weight: 500; opacity: 0.6; letter-spacing: 1px; margin-bottom: 10px; text-transform: uppercase; }\n    .now { display: flex; align-items: baseline; gap: 10px; }\n    .now .temp { font-size: 3rem; font-weight: 200; }\n    .now .text { font-size: 1rem; opacity: 0.85; }\n    .meta { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; margin-top: 10px; font-size: 0.82rem; opacity: 0.78; }\n    .meta div span { opacity: 0.6; margin-right: 4px; }\n    .forecast-row {\n      display: grid; grid-template-columns: 50px 1fr 64px 72px; gap: 8px; align-items: center;\n      padding: 8px 0; border-bottom: 1px solid rgba(255,255,255,0.06); font-size: 0.85rem;\n    }\n    .forecast-row:last-child { border-bottom: none; }\n    .forecast-row .date { opacity: 0.7; }\n    .forecast-row .temps { text-align: right; }\n    .fc-aqi { text-align: center; }\n    .fc-aqi .dot { display: inline-block; padding: 1px 7px; border-radius: 10px; font-size: 0.66rem; white-space: nowrap; }\n    .fc-aqi .n { font-size: 0.62rem; opacity: 0.55; display: block; margin-top: 1px; }\n    .forecast-row .temps .max { color: #ffb86b; }\n    .forecast-row .temps .min { opacity: 0.6; margin-left: 4px; }\n    .aqi-num { font-size: 2rem; font-weight: 200; }\n    .aqi-cat { display: inline-block; padding: 2px 10px; border-radius: 12px; font-size: 0.75rem; margin-left: 8px; }\n    .aqi-good { background: #2ecc71; }\n    .aqi-mod { background: #f39c12; }\n    .aqi-bad { background: #e74c3c; }\n    .warning-item {\n      padding: 10px 12px; border-left: 4px solid #e74c3c;\n      background: rgba(231,76,60,0.1);\n      margin-bottom: 8px; border-radius: 0 8px 8px 0;\n      cursor: pointer; transition: background 0.15s;\n      position: relative;\n    }\n    .warning-item:hover { background: rgba(231,76,60,0.2); }\n    .warning-item .head { display: flex; align-items: flex-start; gap: 10px; }\n    .warning-item .icon { width: 38px; height: 38px; flex: 0 0 38px; }\n    .warning-item .icon img { width: 100%; height: 100%; object-fit: contain; }\n    .warning-item .head-text { flex: 1; min-width: 0; }\n    .warning-item .badges { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 4px; }\n    .warning-item .badge {\n      display: inline-block; padding: 1px 8px; border-radius: 10px;\n      font-size: 0.68rem; font-weight: 500;\n    }\n    .badge-blue { background: #4a90e2; }\n    .badge-yellow { background: #f1c40f; color: #333; }\n    .badge-orange { background: #e67e22; }\n    .badge-red { background: #e74c3c; }\n    .badge-gray { background: rgba(255,255,255,0.15); }\n    .warning-item .title { font-size: 0.88rem; font-weight: 500; line-height: 1.4; }\n    .warning-item .time { font-size: 0.7rem; opacity: 0.55; margin-top: 2px; }\n    .warning-item .toggle { font-size: 0.7rem; opacity: 0.5; margin-top: 6px; }\n    .warning-item .detail {\n      display: none; margin-top: 10px; padding-top: 10px;\n      border-top: 1px solid rgba(255,255,255,0.1); font-size: 0.8rem; line-height: 1.65;\n    }\n    .warning-item.open .detail { display: block; }\n    .warning-item .detail-section { margin-bottom: 8px; }\n    .warning-item .detail-section .sec-label {\n      font-size: 0.7rem; opacity: 0.5; margin-bottom: 4px; letter-spacing: 1px;\n    }\n    .warning-item .detail-section .sec-body {\n      background: rgba(0,0,0,0.2); padding: 8px 10px; border-radius: 6px;\n      font-size: 0.78rem;\n    }\n    .warning-item a { color: #6ab7ff; text-decoration: none; }\n    .warning-item a:hover { text-decoration: underline; }\n    .warning-item .loading-detail { opacity: 0.5; font-size: 0.75rem; }\n    .lvl-blue { border-left-color: #4a90e2; background: rgba(74,144,226,0.1); }\n    .lvl-yellow { border-left-color: #f1c40f; background: rgba(241,196,15,0.1); }\n    .lvl-orange { border-left-color: #e67e22; background: rgba(230,126,34,0.1); }\n    .lvl-red { border-left-color: #e74c3c; background: rgba(231,76,60,0.1); }\n    \/* \u5206\u949f\u964d\u6c34 *\/\n    .minutely-summary { font-size: 0.95rem; margin-bottom: 8px; }\n    .minutely-bars { display: flex; gap: 3px; height: 50px; align-items: flex-end; }\n    .minutely-bars .bar { flex: 1; background: linear-gradient(180deg, #4a90e2, #2c5f9c); border-radius: 2px 2px 0 0; min-height: 2px; }\n    .minutely-bars .bar.dry { background: rgba(255,255,255,0.08); }\n    .minutely-labels { display: flex; justify-content: space-between; font-size: 0.65rem; opacity: 0.5; margin-top: 4px; }\n    \/* \u9010\u5c0f\u65f6\u6eda\u52a8 *\/\n    .hourly-scroll { display: flex; overflow-x: auto; gap: 10px; padding-bottom: 6px; }\n    .hourly-scroll::-webkit-scrollbar { height: 4px; }\n    .hourly-scroll::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.15); }\n    .hour-cell { flex: 0 0 56px; text-align: center; font-size: 0.75rem; opacity: 0.85; }\n    .hour-cell .hr-time { opacity: 0.55; }\n    .hour-cell .hr-temp { font-size: 1rem; margin: 4px 0; font-weight: 500; }\n    .hour-cell .hr-pop { color: #6ab7ff; font-size: 0.7rem; }\n    \/* \u751f\u6d3b\u6307\u6570 *\/\n    .idx-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }\n    .idx-cell { background: rgba(255,255,255,0.04); padding: 8px 10px; border-radius: 8px; }\n    .idx-cell .label { font-size: 0.7rem; opacity: 0.55; }\n    .idx-cell .val { font-size: 0.9rem; font-weight: 500; margin: 2px 0; }\n    .idx-cell .tip { font-size: 0.7rem; opacity: 0.7; }\n    .empty { opacity: 0.45; font-size: 0.85rem; text-align: center; padding: 8px 0; }\n    .loading { opacity: 0.6; font-size: 0.85rem; }\n    .source { font-size: 0.7rem; opacity: 0.45; margin-top: 6px; }\n    \/* \u6570\u636e\u6765\u6e90 *\/\n    .credits { font-size: 0.72rem; line-height: 1.7; opacity: 0.7; }\n    .credits .cr-row { display: flex; padding: 4px 0; border-bottom: 1px dashed rgba(255,255,255,0.06); }\n    .credits .cr-row:last-child { border-bottom: none; }\n    .credits .cr-name { flex: 0 0 90px; opacity: 0.55; }\n    .credits .cr-use { flex: 1; }\n    .credits a { color: #6ab7ff; text-decoration: none; }\n    .credits a:hover { text-decoration: underline; }\n    .credits .disclaimer {\n      font-size: 0.68rem; opacity: 0.5; margin-top: 10px; padding-top: 8px;\n      border-top: 1px solid rgba(255,255,255,0.08); line-height: 1.5;\n    }\n    \/* \u5929\u6587\u5361 *\/\n    .astro-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }\n    .astro-cell { background: rgba(255,255,255,0.04); padding: 8px 10px; border-radius: 8px; font-size: 0.82rem; }\n    .astro-cell .label { font-size: 0.7rem; opacity: 0.55; }\n    .astro-cell .val { font-size: 0.95rem; font-weight: 500; margin-top: 2px; }\n    .moon-row { display: flex; align-items: center; gap: 10px; margin-top: 10px; }\n    .moon-row .phase { font-size: 1.6rem; }\n    .spark { width: 100%; height: 70px; display: block; }\n    .spark .line { fill: none; stroke: #6ab7ff; stroke-width: 2; }\n    .spark .area { fill: rgba(106,183,255,0.12); }\n    .spark .uvline { stroke: #ffb86b; }\n    .spark .uvarea { fill: rgba(255,184,107,0.12); }\n    .spark-labels { display: flex; justify-content: space-between; font-size: 0.65rem; opacity: 0.5; margin-top: 2px; }\n    .uv-now { display: flex; align-items: baseline; gap: 8px; margin-bottom: 8px; }\n    .uv-now .v { font-size: 2rem; font-weight: 200; }\n    .uv-now .lvl { font-size: 0.9rem; opacity: 0.8; }\n    .cmp-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }\n    .cmp-cell { background: rgba(255,255,255,0.04); padding: 10px; border-radius: 8px; text-align: center; }\n    .cmp-cell .when { font-size: 0.7rem; opacity: 0.55; }\n    .cmp-cell .t { font-size: 1.1rem; font-weight: 500; margin-top: 4px; }\n    .cmp-cell .t .max { color: #ffb86b; } .cmp-cell .t .min { opacity: 0.6; }\n    .airfc-row { display: grid; grid-template-columns: 56px 1fr 60px; gap: 8px; align-items: center; padding: 6px 0; border-bottom: 1px solid rgba(255,255,255,0.06); font-size: 0.82rem; }\n    .airfc-row:last-child { border-bottom: none; }\n    .airfc-row .dot { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 0.72rem; }\n    .ty-item { padding: 8px 10px; border-left: 4px solid #e67e22; background: rgba(230,126,34,0.1); border-radius: 0 8px 8px 0; margin-bottom: 8px; cursor: pointer; }\n    .ty-item.stopped { border-left-color: rgba(255,255,255,0.2); background: rgba(255,255,255,0.03); opacity: 0.7; }\n    .ty-item .name { font-size: 0.9rem; font-weight: 500; }\n    .ty-item .meta2 { font-size: 0.72rem; opacity: 0.7; margin-top: 3px; }\n    .ty-badge { display:inline-block; padding:1px 7px; border-radius:10px; font-size:0.66rem; background:#e67e22; margin-left:6px; }\n    .ty-badge.off { background: rgba(255,255,255,0.15); }\n  &lt;\/style&gt;\n&lt;script&gt;window.WEATHER_API_BASE=&quot;https:\/\/weather.202620260.xyz&quot;;&lt;\/script&gt;\n&lt;script&gt;(function(){function p(){try{parent.postMessage({__wapp_h:Math.max(document.documentElement.scrollHeight,document.body?document.body.scrollHeight:0)},&quot;*&quot;);}catch(e){}}window.addEventListener(&quot;load&quot;,p);window.addEventListener(&quot;resize&quot;,p);if(window.ResizeObserver){new ResizeObserver(p).observe(document.documentElement);}setInterval(p,800);})();&lt;\/script&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;div id=&quot;mapWrap&quot;&gt;\n    &lt;div id=&quot;layerBar&quot;&gt;\n      &lt;!-- Windy \u514d\u8d39\u56fe\u5c42\u5168\u96c6 --&gt;\n      &lt;button class=&quot;layer-btn active&quot; data-overlay=&quot;wind&quot;&gt;\ud83c\udf2c\ufe0f \u98ce&lt;\/button&gt;\n      &lt;button class=&quot;layer-btn&quot; data-overlay=&quot;gust&quot;&gt;\ud83d\udca8 \u9635\u98ce&lt;\/button&gt;\n      &lt;button class=&quot;layer-btn&quot; data-overlay=&quot;rain&quot;&gt;\ud83c\udf27\ufe0f \u964d\u96e8&lt;\/button&gt;\n      &lt;button class=&quot;layer-btn&quot; data-overlay=&quot;rainAccu&quot;&gt;\u2614 \u7d2f\u8ba1\u964d\u96e8&lt;\/button&gt;\n      &lt;button class=&quot;layer-btn&quot; data-overlay=&quot;radar&quot;&gt;\ud83d\udce1 \u96f7\u8fbe&lt;\/button&gt;\n      &lt;button class=&quot;layer-btn&quot; data-overlay=&quot;satellite&quot;&gt;\ud83d\udef0\ufe0f \u536b\u661f&lt;\/button&gt;\n      &lt;button class=&quot;layer-btn&quot; data-overlay=&quot;temp&quot;&gt;\ud83c\udf21\ufe0f \u6e29\u5ea6&lt;\/button&gt;\n      &lt;button class=&quot;layer-btn&quot; data-overlay=&quot;dewpoint&quot;&gt;\ud83d\udca7 \u9732\u70b9&lt;\/button&gt;\n      &lt;button class=&quot;layer-btn&quot; data-overlay=&quot;rh&quot;&gt;\ud83d\udca6 \u6e7f\u5ea6&lt;\/button&gt;\n      &lt;button class=&quot;layer-btn&quot; data-overlay=&quot;clouds&quot;&gt;\u2601\ufe0f \u4e91\u91cf&lt;\/button&gt;\n      &lt;button class=&quot;layer-btn&quot; data-overlay=&quot;pressure&quot;&gt;\ud83d\udcca \u6c14\u538b&lt;\/button&gt;\n      &lt;button class=&quot;layer-btn&quot; data-overlay=&quot;snow&quot;&gt;\u2744\ufe0f \u964d\u96ea&lt;\/button&gt;\n      &lt;button class=&quot;layer-btn&quot; data-overlay=&quot;snowAccu&quot;&gt;\u26c4 \u7d2f\u8ba1\u964d\u96ea&lt;\/button&gt;\n      &lt;button class=&quot;layer-btn&quot; data-overlay=&quot;waves&quot;&gt;\ud83c\udf0a \u6d77\u6d6a&lt;\/button&gt;\n      &lt;button class=&quot;layer-btn&quot; data-overlay=&quot;swell1&quot;&gt;\ud83c\udf0a \u6d8c\u6d6a&lt;\/button&gt;\n      &lt;button class=&quot;layer-btn&quot; data-overlay=&quot;currents&quot;&gt;\ud83c\udf00 \u6d0b\u6d41&lt;\/button&gt;\n      &lt;button class=&quot;layer-btn&quot; data-overlay=&quot;sst&quot;&gt;\ud83d\udc1f \u6d77\u6e29&lt;\/button&gt;\n      &lt;button class=&quot;layer-btn&quot; data-overlay=&quot;visibility&quot;&gt;\ud83d\udc41\ufe0f \u80fd\u89c1\u5ea6&lt;\/button&gt;\n      &lt;button class=&quot;layer-btn&quot; data-overlay=&quot;cape&quot;&gt;\u26a1 \u96f7\u66b4&lt;\/button&gt;\n      &lt;button class=&quot;layer-btn&quot; data-overlay=&quot;cloudtop&quot;&gt;\ud83d\udeeb \u4e91\u9876\u9ad8\u5ea6&lt;\/button&gt;\n      &lt;button class=&quot;layer-btn&quot; data-overlay=&quot;cosc&quot;&gt;\ud83d\ude37 PM2.5&lt;\/button&gt;\n      &lt;button class=&quot;layer-btn&quot; data-overlay=&quot;no2&quot;&gt;\ud83c\udfed NO\u2082&lt;\/button&gt;\n      &lt;button class=&quot;layer-btn&quot; data-overlay=&quot;aod550&quot;&gt;\ud83c\udf2b\ufe0f \u6c14\u6eb6\u80f6&lt;\/button&gt;\n    &lt;\/div&gt;\n    &lt;iframe id=&quot;windy&quot; src=&quot;&quot;&gt;&lt;\/iframe&gt;\n  &lt;\/div&gt;\n\n  &lt;aside id=&quot;panel&quot;&gt;\n    &lt;div class=&quot;header&quot;&gt;\n      &lt;h1&gt;\ud83c\udf24\ufe0f \u4e2d\u56fd\u5929\u6c14 \u00b7 Windy&lt;\/h1&gt;\n      &lt;p&gt;23 \u4e2a\u6c14\u8c61\u56fe\u5c42 \u00b7 \u591a\u6e90\u6570\u636e\u805a\u5408&lt;\/p&gt;\n    &lt;\/div&gt;\n\n    &lt;div class=&quot;search&quot;&gt;\n      &lt;input id=&quot;searchInput&quot; placeholder=&quot;\u641c\u7d22\u57ce\u5e02\uff0c\u5982: \u5317\u4eac\u3001\u4e0a\u6d77\u3001\u6210\u90fd&quot; \/&gt;\n      &lt;button id=&quot;searchBtn&quot;&gt;\u641c\u7d22&lt;\/button&gt;\n    &lt;\/div&gt;\n\n    &lt;div id=&quot;location&quot; class=&quot;card&quot;&gt;\n      &lt;h3&gt;\ud83d\udccd \u4f4d\u7f6e&lt;\/h3&gt;\n      &lt;div id=&quot;locName&quot; class=&quot;empty&quot;&gt;\u641c\u7d22\u57ce\u5e02\u4ee5\u67e5\u8be2\u5929\u6c14&lt;\/div&gt;\n    &lt;\/div&gt;\n\n    &lt;div class=&quot;card&quot;&gt;\n      &lt;h3&gt;\ud83c\udf21\ufe0f \u5b9e\u65f6\u5929\u6c14&lt;\/h3&gt;\n      &lt;div id=&quot;current&quot;&gt;&lt;div class=&quot;empty&quot;&gt;\u2014&lt;\/div&gt;&lt;\/div&gt;\n    &lt;\/div&gt;\n\n    &lt;div class=&quot;card&quot;&gt;\n      &lt;h3&gt;\ud83c\udf05 \u65e5\u51fa\u65e5\u843d \/ \u6708\u76f8&lt;\/h3&gt;\n      &lt;div id=&quot;astronomy&quot;&gt;&lt;div class=&quot;empty&quot;&gt;\u2014&lt;\/div&gt;&lt;\/div&gt;\n    &lt;\/div&gt;\n\n    &lt;div class=&quot;card&quot;&gt;\n      &lt;h3&gt;\ud83d\udcca \u4e0e\u6628\u65e5 \/ \u53bb\u5e74\u540c\u671f\u5bf9\u6bd4&lt;\/h3&gt;\n      &lt;div id=&quot;history&quot;&gt;&lt;div class=&quot;empty&quot;&gt;\u2014&lt;\/div&gt;&lt;\/div&gt;\n    &lt;\/div&gt;\n\n    &lt;div class=&quot;card&quot;&gt;\n      &lt;h3&gt;\u2614 \u672a\u6765 2 \u5c0f\u65f6\u964d\u6c34&lt;\/h3&gt;\n      &lt;div id=&quot;minutely&quot;&gt;&lt;div class=&quot;empty&quot;&gt;\u2014&lt;\/div&gt;&lt;\/div&gt;\n    &lt;\/div&gt;\n\n    &lt;div class=&quot;card&quot;&gt;\n      &lt;h3&gt;\u23f1\ufe0f 48 \u5c0f\u65f6\u9884\u62a5&lt;\/h3&gt;\n      &lt;div id=&quot;hourly&quot;&gt;&lt;div class=&quot;empty&quot;&gt;\u2014&lt;\/div&gt;&lt;\/div&gt;\n    &lt;\/div&gt;\n\n    &lt;div class=&quot;card&quot;&gt;\n      &lt;h3&gt;\ud83d\udcc8 \u6e29\u5ea6\u8d8b\u52bf (48h)&lt;\/h3&gt;\n      &lt;div id=&quot;trend&quot;&gt;&lt;div class=&quot;empty&quot;&gt;\u2014&lt;\/div&gt;&lt;\/div&gt;\n    &lt;\/div&gt;\n\n    &lt;div class=&quot;card&quot;&gt;\n      &lt;h3&gt;\u2600\ufe0f \u7d2b\u5916\u7ebf\u6307\u6570&lt;\/h3&gt;\n      &lt;div id=&quot;uv&quot;&gt;&lt;div class=&quot;empty&quot;&gt;\u2014&lt;\/div&gt;&lt;\/div&gt;\n    &lt;\/div&gt;\n\n    &lt;div class=&quot;card&quot;&gt;\n      &lt;h3&gt;\ud83c\udf1f \u751f\u6d3b\u6307\u6570&lt;\/h3&gt;\n      &lt;div id=&quot;indices&quot;&gt;&lt;div class=&quot;empty&quot;&gt;\u2014&lt;\/div&gt;&lt;\/div&gt;\n    &lt;\/div&gt;\n\n    &lt;div class=&quot;card&quot;&gt;\n      &lt;h3&gt;\ud83d\ude37 \u7a7a\u6c14\u8d28\u91cf&lt;\/h3&gt;\n      &lt;div id=&quot;air&quot;&gt;&lt;div class=&quot;empty&quot;&gt;\u2014&lt;\/div&gt;&lt;\/div&gt;\n    &lt;\/div&gt;\n\n    &lt;div class=&quot;card&quot; id=&quot;marineCard&quot; style=&quot;display:none&quot;&gt;\n      &lt;h3&gt;\ud83c\udf0a \u6d77\u6d0b \/ \u6f6e\u6c50&lt;\/h3&gt;\n      &lt;div id=&quot;marine&quot;&gt;&lt;div class=&quot;empty&quot;&gt;\u2014&lt;\/div&gt;&lt;\/div&gt;\n    &lt;\/div&gt;\n\n    &lt;div class=&quot;card&quot;&gt;\n      &lt;h3&gt;\ud83c\udf00 \u53f0\u98ce\u52a8\u6001&lt;\/h3&gt;\n      &lt;div id=&quot;typhoon&quot;&gt;&lt;div class=&quot;empty&quot;&gt;\u52a0\u8f7d\u4e2d...&lt;\/div&gt;&lt;\/div&gt;\n    &lt;\/div&gt;\n\n    &lt;div class=&quot;card&quot;&gt;\n      &lt;h3&gt;\u26a0\ufe0f \u6c14\u8c61\u9884\u8b66&lt;\/h3&gt;\n      &lt;div id=&quot;warning&quot;&gt;&lt;div class=&quot;empty&quot;&gt;\u65e0\u9884\u8b66&lt;\/div&gt;&lt;\/div&gt;\n    &lt;\/div&gt;\n\n    &lt;div class=&quot;card&quot;&gt;\n      &lt;h3&gt;\ud83d\udcc5 7 \u5929\u9884\u62a5 \u00b7 \u7a7a\u6c14\u8d28\u91cf&lt;\/h3&gt;\n      &lt;div id=&quot;forecast&quot;&gt;&lt;div class=&quot;empty&quot;&gt;\u2014&lt;\/div&gt;&lt;\/div&gt;\n    &lt;\/div&gt;\n\n    &lt;div class=&quot;card&quot;&gt;\n      &lt;h3&gt;\ud83d\udce1 \u6570\u636e\u6765\u6e90 \/ Credits&lt;\/h3&gt;\n      &lt;div class=&quot;credits&quot;&gt;\n        &lt;div class=&quot;cr-row&quot;&gt;\n          &lt;span class=&quot;cr-name&quot;&gt;\u5b9e\u65f6 \/ \u9884\u62a5 \/ \u7a7a\u6c14 \/ \u5206\u949f\u964d\u6c34 \/ \u751f\u6d3b\u6307\u6570&lt;\/span&gt;\n          &lt;span class=&quot;cr-use&quot;&gt;\n            &lt;a href=&quot;https:\/\/open-meteo.com\/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Open-Meteo&lt;\/a&gt;\n            \uff08CC-BY 4.0\uff1b\u5e95\u5c42\u6a21\u578b ECMWF \/ DWD ICON \/ NOAA GFS\uff09\n          &lt;\/span&gt;\n        &lt;\/div&gt;\n        &lt;div class=&quot;cr-row&quot;&gt;\n          &lt;span class=&quot;cr-name&quot;&gt;\u6c14\u8c61\u9884\u8b66&lt;\/span&gt;\n          &lt;span class=&quot;cr-use&quot;&gt;\n            &lt;a href=&quot;http:\/\/www.nmc.cn\/publish\/alarm.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;\u4e2d\u592e\u6c14\u8c61\u53f0 (NMC)&lt;\/a&gt;\n            \u00b7 \u4e2d\u56fd\u6c14\u8c61\u5c40 CMA \u5b98\u65b9\u53d1\u5e03\n          &lt;\/span&gt;\n        &lt;\/div&gt;\n        &lt;div class=&quot;cr-row&quot;&gt;\n          &lt;span class=&quot;cr-name&quot;&gt;\u6c14\u8c61\u56fe\u5c42 \/ \u5730\u56fe&lt;\/span&gt;\n          &lt;span class=&quot;cr-use&quot;&gt;\n            &lt;a href=&quot;https:\/\/www.windy.com\/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Windy.com&lt;\/a&gt;\n            \uff08iframe \u5d4c\u5165\uff09\n          &lt;\/span&gt;\n        &lt;\/div&gt;\n        &lt;div class=&quot;cr-row&quot;&gt;\n          &lt;span class=&quot;cr-name&quot;&gt;\u57ce\u5e02\u641c\u7d22&lt;\/span&gt;\n          &lt;span class=&quot;cr-use&quot;&gt;\n            &lt;a href=&quot;https:\/\/nominatim.openstreetmap.org\/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Nominatim&lt;\/a&gt;\n            \u00b7 \u00a9 OpenStreetMap \u8d21\u732e\u8005 (ODbL)\n          &lt;\/span&gt;\n        &lt;\/div&gt;\n        &lt;div class=&quot;cr-row&quot;&gt;\n          &lt;span class=&quot;cr-name&quot;&gt;\u53cd\u5411\u5730\u7406\u7f16\u7801&lt;\/span&gt;\n          &lt;span class=&quot;cr-use&quot;&gt;\n            &lt;a href=&quot;https:\/\/photon.komoot.io\/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Photon&lt;\/a&gt;\n            by Komoot \u00b7 \u00a9 OpenStreetMap \u8d21\u732e\u8005 (ODbL)\n          &lt;\/span&gt;\n        &lt;\/div&gt;\n        &lt;div class=&quot;disclaimer&quot;&gt;\n          \u26a0\ufe0f \u672c\u9879\u76ee\u4ec5\u4f9b\u5b66\u4e60\/\u4e2a\u4eba\u53c2\u8003\u4f7f\u7528\uff0c\u9884\u8b66\u4ee5\n          &lt;a href=&quot;http:\/\/www.nmc.cn\/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;\u4e2d\u592e\u6c14\u8c61\u53f0 nmc.cn&lt;\/a&gt;\n          \u5b98\u65b9\u53d1\u5e03\u4e3a\u51c6\u3002&lt;br&gt;\n          \u5404\u4e0a\u6e38\u6570\u636e\u5747\u5c0a\u91cd\u539f\u7248\u6743\u4e0e\u670d\u52a1\u6761\u6b3e\uff0c\u8bf7\u52ff\u7528\u4e8e\u5546\u4e1a\u7528\u9014\u3002\n        &lt;\/div&gt;\n      &lt;\/div&gt;\n    &lt;\/div&gt;\n  &lt;\/aside&gt;\n\n  &lt;script&gt;\n    \/\/ \u540c\u6e90\u65f6 API_BASE \u4e3a\u7a7a (\u8d70 nginx \u53cd\u4ee3 \/api\/*); \u672c\u5730\u5f00\u53d1\u7528 http:\/\/localhost:8000\n    const API_BASE = (window.WEATHER_API_BASE !== undefined)\n      ? window.WEATHER_API_BASE\n      : (location.port === &#x27;5173&#x27; || location.protocol === &#x27;file:&#x27;\n          ? &#x27;http:\/\/localhost:8000&#x27; : &#x27;&#x27;);\n\n    \/\/ Windy iframe \u72b6\u6001\n    \/\/ \u9ed8\u8ba4\u805a\u7126\u897f\u5b89\n    const DEFAULT_CITY = { name: &#x27;\u897f\u5b89&#x27;, lat: 34.3416, lon: 108.9398, level: &#x27;city&#x27; };\n    let state = { lat: DEFAULT_CITY.lat, lon: DEFAULT_CITY.lon, zoom: 9,\n                  overlay: &#x27;wind&#x27;, showDetail: true };\n    const iframe = document.getElementById(&#x27;windy&#x27;);\n\n    function buildWindyUrl() {\n      const p = new URLSearchParams({\n        lat: state.lat, lon: state.lon, zoom: state.zoom,\n        overlay: state.overlay,\n        level: &#x27;surface&#x27;,\n        product: &#x27;ecmwf&#x27;,\n        menu: &#x27;&#x27;, message: &#x27;true&#x27;, marker: &#x27;true&#x27;,\n        calendar: &#x27;now&#x27;, pressure: &#x27;&#x27;,\n        type: &#x27;map&#x27;, location: &#x27;coordinates&#x27;,\n        \/\/ \u5173\u952e: detail=true \u624d\u4f1a\u81ea\u52a8\u5f39\u51fa\u8be5\u70b9\u7684\u9010\u65f6\u9884\u62a5\u9762\u677f\n        detail: state.showDetail ? &#x27;true&#x27; : &#x27;&#x27;,\n        detailLat: state.lat, detailLon: state.lon,\n        metricWind: &#x27;default&#x27;, metricTemp: &#x27;default&#x27;,\n        radarRange: &#x27;-1&#x27;,\n      });\n      return `https:\/\/embed.windy.com\/embed2.html?${p}`;\n    }\n\n    function refreshWindy() {\n      iframe.src = buildWindyUrl();\n    }\n\n    \/\/ \u5207\u6362\u56fe\u5c42 (\u4fdd\u7559\u5f53\u524d detail \u72b6\u6001: \u5df2\u9009\u4f4d\u7f6e\u5219\u5207\u56fe\u5c42\u65f6\u4ecd\u663e\u793a\u8be6\u60c5)\n    document.querySelectorAll(&#x27;.layer-btn&#x27;).forEach(btn =&gt; {\n      btn.addEventListener(&#x27;click&#x27;, () =&gt; {\n        document.querySelectorAll(&#x27;.layer-btn&#x27;).forEach(b =&gt; b.classList.remove(&#x27;active&#x27;));\n        btn.classList.add(&#x27;active&#x27;);\n        state.overlay = btn.dataset.overlay;\n        refreshWindy();\n      });\n    });\n\n    refreshWindy();\n\n    \/\/ \u9996\u6b21\u8fdb\u5165: \u81ea\u52a8\u52a0\u8f7d\u9ed8\u8ba4\u57ce\u5e02 (\u897f\u5b89)\n    window.addEventListener(&#x27;DOMContentLoaded&#x27;, () =&gt; {\n      loadWeather(DEFAULT_CITY.lat, DEFAULT_CITY.lon, DEFAULT_CITY.level, DEFAULT_CITY.name);\n      document.getElementById(&#x27;searchInput&#x27;).value = DEFAULT_CITY.name;\n      loadTyphoon();\n    });\n\n    \/\/ ============ \u4fa7\u680f\u6570\u636e ============\n    \/\/ \u9ed8\u8ba4\u6309\u533a\/\u53bf\u7c92\u5ea6\u67e5\uff08\u5730\u56fe\u70b9\u51fb = \u4e00\u4e2a\u70b9 = \u6700\u7ec6\u7c92\u5ea6\uff09\n    async function loadWeather(lat, lon, level = &#x27;district&#x27;, name = &#x27;&#x27;) {\n      \/\/ \u641c\u7d22\/\u70b9\u51fb \u2192 \u7f29\u653e\u5e76\u5f39\u51fa Windy \u8be6\u60c5\u9762\u677f, \u8ba9\u7528\u6237\u770b\u5230\u8be5\u5730\u70b9\u7684\u9010\u65f6\u5929\u6c14\n      state.lat = lat; state.lon = lon;\n      state.zoom = level === &#x27;province&#x27; ? 7 : level === &#x27;city&#x27; ? 9 : 10;\n      state.showDetail = true;\n      refreshWindy();\n      setLoading();\n      try {\n        const qs = new URLSearchParams({ lat, lon, level });\n        if (name) qs.set(&#x27;name&#x27;, name);\n        const r = await fetch(`${API_BASE}\/api\/all?${qs}`);\n        if (!r.ok) throw new Error(await r.text());\n        renderAll(await r.json());\n      } catch (err) {\n        document.getElementById(&#x27;current&#x27;).innerHTML = `&lt;div class=&quot;empty&quot;&gt;\u52a0\u8f7d\u5931\u8d25: ${err.message}&lt;\/div&gt;`;\n      }\n    }\n\n    function setLoading() {\n      for (const id of [&#x27;current&#x27;, &#x27;air&#x27;, &#x27;warning&#x27;, &#x27;forecast&#x27;, &#x27;hourly&#x27;, &#x27;minutely&#x27;, &#x27;indices&#x27;,\n                        &#x27;astronomy&#x27;, &#x27;history&#x27;, &#x27;trend&#x27;, &#x27;uv&#x27;]) {\n        document.getElementById(id).innerHTML = &#x27;&lt;div class=&quot;loading&quot;&gt;\u52a0\u8f7d\u4e2d...&lt;\/div&gt;&#x27;;\n      }\n    }\n\n    function renderAll(d) {\n      document.getElementById(&#x27;locName&#x27;).textContent =\n        d.location.name || `${d.location.lat.toFixed(3)}, ${d.location.lon.toFixed(3)}`;\n      renderCurrent(d.current);\n      renderAstronomy(d.astronomy);\n      renderHistory(d.history);\n      renderMinutely(d.minutely);\n      renderHourly(d.hourly);\n      renderTrend(d.hourly);\n      renderUV(d.uv);\n      renderIndices(d.indices);\n      renderAir(d.air);\n      renderMarine(d.marine);\n      renderWarning(d.warning);\n      renderForecast(d.forecast, d.airForecast);\n    }\n\n    function sparkline(values, { cls = &#x27;&#x27;, width = 320, height = 70 } = {}) {\n      const vals = values.filter(v =&gt; v !== null &amp;&amp; v !== undefined &amp;&amp; !isNaN(v));\n      if (vals.length &lt; 2) return &#x27;&lt;div class=&quot;empty&quot;&gt;\u6570\u636e\u4e0d\u8db3&lt;\/div&gt;&#x27;;\n      const min = Math.min(...vals), max = Math.max(...vals);\n      const span = (max - min) || 1;\n      const pad = 6;\n      const n = values.length;\n      const xs = i =&gt; pad + (i \/ (n - 1)) * (width - 2 * pad);\n      const ys = v =&gt; pad + (1 - (v - min) \/ span) * (height - 2 * pad);\n      let line = &#x27;&#x27;;\n      values.forEach((v, i) =&gt; {\n        if (v === null || v === undefined || isNaN(v)) return;\n        const cmd = line ? &#x27;L&#x27; : &#x27;M&#x27;;\n        line += `${cmd}${xs(i).toFixed(1)},${ys(v).toFixed(1)} `;\n      });\n      const firstI = values.findIndex(v =&gt; v !== null &amp;&amp; !isNaN(v));\n      let lastI = 0; for (let i = n - 1; i &gt;= 0; i--) { if (values[i] !== null &amp;&amp; !isNaN(values[i])) { lastI = i; break; } }\n      const area = `M${xs(firstI).toFixed(1)},${(height - pad).toFixed(1)} ` +\n                   line.replace(\/^M\/, &#x27;L&#x27;) +\n                   `L${xs(lastI).toFixed(1)},${(height - pad).toFixed(1)} Z`;\n      const lineCls = cls === &#x27;uv&#x27; ? &#x27;line uvline&#x27; : &#x27;line&#x27;;\n      const areaCls = cls === &#x27;uv&#x27; ? &#x27;area uvarea&#x27; : &#x27;area&#x27;;\n      return `&lt;svg class=&quot;spark&quot; viewBox=&quot;0 0 ${width} ${height}&quot; preserveAspectRatio=&quot;none&quot;&gt;\n        &lt;path class=&quot;${areaCls}&quot; d=&quot;${area}&quot;\/&gt;\n        &lt;path class=&quot;${lineCls}&quot; d=&quot;${line.trim()}&quot;\/&gt;\n      &lt;\/svg&gt;`;\n    }\n\n    function renderAstronomy(a) {\n      const el = document.getElementById(&#x27;astronomy&#x27;);\n      if (!a) { el.innerHTML = &#x27;&lt;div class=&quot;empty&quot;&gt;\u65e0\u6570\u636e&lt;\/div&gt;&#x27;; return; }\n      const moonEmoji = {\n        &#x27;\u65b0\u6708&#x27;:&#x27;\ud83c\udf11&#x27;,&#x27;\u86fe\u7709\u6708&#x27;:&#x27;\ud83c\udf12&#x27;,&#x27;\u4e0a\u5f26\u6708&#x27;:&#x27;\ud83c\udf13&#x27;,&#x27;\u76c8\u51f8\u6708&#x27;:&#x27;\ud83c\udf14&#x27;,\n        &#x27;\u6ee1\u6708&#x27;:&#x27;\ud83c\udf15&#x27;,&#x27;\u4e8f\u51f8\u6708&#x27;:&#x27;\ud83c\udf16&#x27;,&#x27;\u4e0b\u5f26\u6708&#x27;:&#x27;\ud83c\udf17&#x27;,&#x27;\u6b8b\u6708&#x27;:&#x27;\ud83c\udf18&#x27;\n      }[a.moonPhase] || &#x27;\ud83c\udf19&#x27;;\n      el.innerHTML = `\n        &lt;div class=&quot;astro-grid&quot;&gt;\n          &lt;div class=&quot;astro-cell&quot;&gt;&lt;div class=&quot;label&quot;&gt;\u2600\ufe0f \u65e5\u51fa&lt;\/div&gt;&lt;div class=&quot;val&quot;&gt;${a.sunrise || &#x27;\u2014&#x27;}&lt;\/div&gt;&lt;\/div&gt;\n          &lt;div class=&quot;astro-cell&quot;&gt;&lt;div class=&quot;label&quot;&gt;\ud83c\udf07 \u65e5\u843d&lt;\/div&gt;&lt;div class=&quot;val&quot;&gt;${a.sunset || &#x27;\u2014&#x27;}&lt;\/div&gt;&lt;\/div&gt;\n          &lt;div class=&quot;astro-cell&quot;&gt;&lt;div class=&quot;label&quot;&gt;\ud83c\udf1d \u6708\u5347&lt;\/div&gt;&lt;div class=&quot;val&quot;&gt;${a.moonrise || &#x27;\u2014&#x27;}&lt;\/div&gt;&lt;\/div&gt;\n          &lt;div class=&quot;astro-cell&quot;&gt;&lt;div class=&quot;label&quot;&gt;\ud83c\udf1a \u6708\u843d&lt;\/div&gt;&lt;div class=&quot;val&quot;&gt;${a.moonset || &#x27;\u2014&#x27;}&lt;\/div&gt;&lt;\/div&gt;\n        &lt;\/div&gt;\n        &lt;div class=&quot;moon-row&quot;&gt;\n          &lt;span class=&quot;phase&quot;&gt;${moonEmoji}&lt;\/span&gt;\n          &lt;div&gt;&lt;div class=&quot;val&quot;&gt;${a.moonPhase || &#x27;\u2014&#x27;}&lt;\/div&gt;\n          &lt;div class=&quot;label&quot;&gt;\u6708\u9762\u7167\u5ea6 ${a.moonIllumination != null ? a.moonIllumination + &#x27;%&#x27; : &#x27;\u2014&#x27;}&lt;\/div&gt;&lt;\/div&gt;\n        &lt;\/div&gt;`;\n    }\n\n    function renderHistory(h) {\n      const el = document.getElementById(&#x27;history&#x27;);\n      if (!h || (!h.yesterday &amp;&amp; !h.lastYear)) { el.innerHTML = &#x27;&lt;div class=&quot;empty&quot;&gt;\u65e0\u6570\u636e&lt;\/div&gt;&#x27;; return; }\n      const cell = (label, d) =&gt; d\n        ? `&lt;div class=&quot;cmp-cell&quot;&gt;&lt;div class=&quot;when&quot;&gt;${label}&lt;br&gt;&lt;small style=&quot;opacity:.6&quot;&gt;${d.date}&lt;\/small&gt;&lt;\/div&gt;\n           &lt;div class=&quot;t&quot;&gt;&lt;span class=&quot;max&quot;&gt;${Math.round(d.tempMax)}\u00b0&lt;\/span&gt; \/ &lt;span class=&quot;min&quot;&gt;${Math.round(d.tempMin)}\u00b0&lt;\/span&gt;&lt;\/div&gt;\n           &lt;div class=&quot;label&quot; style=&quot;font-size:.7rem;opacity:.55&quot;&gt;\u964d\u6c34 ${d.precip ?? 0}mm&lt;\/div&gt;&lt;\/div&gt;`\n        : `&lt;div class=&quot;cmp-cell&quot;&gt;&lt;div class=&quot;when&quot;&gt;${label}&lt;\/div&gt;&lt;div class=&quot;empty&quot;&gt;\u65e0&lt;\/div&gt;&lt;\/div&gt;`;\n      el.innerHTML = `&lt;div class=&quot;cmp-grid&quot;&gt;${cell(&#x27;\u6628\u65e5&#x27;, h.yesterday)}${cell(&#x27;\u53bb\u5e74\u540c\u671f&#x27;, h.lastYear)}&lt;\/div&gt;`;\n    }\n\n    function renderTrend(hs) {\n      const el = document.getElementById(&#x27;trend&#x27;);\n      if (!hs || !hs.length) { el.innerHTML = &#x27;&lt;div class=&quot;empty&quot;&gt;\u65e0\u6570\u636e&lt;\/div&gt;&#x27;; return; }\n      const temps = hs.map(h =&gt; h.temp);\n      const min = Math.min(...temps), max = Math.max(...temps);\n      el.innerHTML = sparkline(temps, {}) +\n        `&lt;div class=&quot;spark-labels&quot;&gt;&lt;span&gt;\u73b0\u5728 ${Math.round(temps[0])}\u00b0&lt;\/span&gt;\n         &lt;span&gt;\u6700\u4f4e ${Math.round(min)}\u00b0 \/ \u6700\u9ad8 ${Math.round(max)}\u00b0&lt;\/span&gt;\n         &lt;span&gt;+48h&lt;\/span&gt;&lt;\/div&gt;`;\n    }\n\n    function renderUV(u) {\n      const el = document.getElementById(&#x27;uv&#x27;);\n      if (!u) { el.innerHTML = &#x27;&lt;div class=&quot;empty&quot;&gt;\u65e0\u6570\u636e&lt;\/div&gt;&#x27;; return; }\n      const curve = (u.hourly || []).map(p =&gt; p.uv);\n      el.innerHTML = `\n        &lt;div class=&quot;uv-now&quot;&gt;&lt;span class=&quot;v&quot;&gt;${u.current}&lt;\/span&gt;&lt;span class=&quot;lvl&quot;&gt;${u.level} \u00b7 ${u.tip}&lt;\/span&gt;&lt;\/div&gt;\n        ${curve.length &gt; 1 ? sparkline(curve, { cls: &#x27;uv&#x27; }) +\n          `&lt;div class=&quot;spark-labels&quot;&gt;&lt;span&gt;\u73b0\u5728&lt;\/span&gt;&lt;span&gt;\u672a\u6765 ${curve.length} \u5c0f\u65f6&lt;\/span&gt;&lt;\/div&gt;` : &#x27;&#x27;}`;\n    }\n\n    function renderAirForecast(list) {\n      const el = document.getElementById(&#x27;airForecast&#x27;);\n      if (!list || !list.length) { el.innerHTML = &#x27;&lt;div class=&quot;empty&quot;&gt;\u65e0\u6570\u636e&lt;\/div&gt;&#x27;; return; }\n      const color = aqi =&gt; aqi &lt;= 50 ? &#x27;#2ecc71&#x27; : aqi &lt;= 100 ? &#x27;#f39c12&#x27; : aqi &lt;= 150 ? &#x27;#e67e22&#x27; : &#x27;#e74c3c&#x27;;\n      el.innerHTML = list.map(d =&gt; {\n        const dt = new Date(d.date);\n        const day = [&#x27;\u65e5&#x27;,&#x27;\u4e00&#x27;,&#x27;\u4e8c&#x27;,&#x27;\u4e09&#x27;,&#x27;\u56db&#x27;,&#x27;\u4e94&#x27;,&#x27;\u516d&#x27;][dt.getDay()];\n        return `&lt;div class=&quot;airfc-row&quot;&gt;\n          &lt;div class=&quot;date&quot; style=&quot;opacity:.7&quot;&gt;\u5468${day}&lt;\/div&gt;\n          &lt;div&gt;&lt;span class=&quot;dot&quot; style=&quot;background:${color(d.aqi)}&quot;&gt;${d.category}&lt;\/span&gt;&lt;\/div&gt;\n          &lt;div style=&quot;text-align:right&quot;&gt;AQI ${d.aqi}&lt;\/div&gt;\n        &lt;\/div&gt;`;\n      }).join(&#x27;&#x27;);\n    }\n\n    function renderMarine(m) {\n      const card = document.getElementById(&#x27;marineCard&#x27;);\n      const el = document.getElementById(&#x27;marine&#x27;);\n      if (!m) { card.style.display = &#x27;none&#x27;; return; }\n      card.style.display = &#x27;&#x27;;\n      const waves = (m.waveHeight || []).map(p =&gt; p.v);\n      el.innerHTML = `\n        &lt;div class=&quot;meta&quot;&gt;\n          &lt;div&gt;&lt;span&gt;\u5f53\u524d\u6d6a\u9ad8&lt;\/span&gt;${m.currentWaveHeight ?? &#x27;\u2014&#x27;} m&lt;\/div&gt;\n          &lt;div&gt;&lt;span&gt;\u6f6e\u4f4d\u533a\u95f4&lt;\/span&gt;${m.tideLow ?? &#x27;\u2014&#x27;} ~ ${m.tideHigh ?? &#x27;\u2014&#x27;} m&lt;\/div&gt;\n        &lt;\/div&gt;\n        ${waves.length &gt; 1 ? `&lt;div style=&quot;margin-top:8px&quot;&gt;${sparkline(waves, {})}\n          &lt;div class=&quot;spark-labels&quot;&gt;&lt;span&gt;\u6d6a\u9ad8 (m)&lt;\/span&gt;&lt;span&gt;\u672a\u6765 ${waves.length}h&lt;\/span&gt;&lt;\/div&gt;&lt;\/div&gt;` : &#x27;&#x27;}`;\n    }\n\n    async function loadTyphoon() {\n      const el = document.getElementById(&#x27;typhoon&#x27;);\n      try {\n        const r = await fetch(`${API_BASE}\/api\/typhoon`);\n        if (!r.ok) throw new Error(&#x27;\u8bf7\u6c42\u5931\u8d25&#x27;);\n        const list = await r.json();\n        if (!list.length) { el.innerHTML = &#x27;&lt;div class=&quot;empty&quot;&gt;\u5f53\u524d\u65e0\u53f0\u98ce\u7f16\u53f7\u4fe1\u606f&lt;\/div&gt;&#x27;; return; }\n        const active = list.filter(t =&gt; t.active);\n        const show = (active.length ? active : list).slice(0, 5);\n        el.innerHTML = show.map(t =&gt; `\n          &lt;div class=&quot;ty-item ${t.active ? &#x27;&#x27; : &#x27;stopped&#x27;}&quot; data-id=&quot;${t.id}&quot;&gt;\n            &lt;div class=&quot;name&quot;&gt;${t.code} ${t.nameCn} &lt;small style=&quot;opacity:.6&quot;&gt;${t.nameEn}&lt;\/small&gt;\n              &lt;span class=&quot;ty-badge ${t.active ? &#x27;&#x27; : &#x27;off&#x27;}&quot;&gt;${t.active ? &#x27;\u6d3b\u8dc3&#x27; : &#x27;\u5df2\u505c\u7f16&#x27;}&lt;\/span&gt;&lt;\/div&gt;\n            &lt;div class=&quot;meta2&quot; data-track&gt;\u70b9\u51fb\u67e5\u770b\u8def\u5f84 \u25be&lt;\/div&gt;\n          &lt;\/div&gt;`).join(&#x27;&#x27;) +\n          (active.length === 0 ? &#x27;&lt;div class=&quot;source&quot;&gt;\u672c\u5b63\u6682\u65e0\u6d3b\u8dc3\u53f0\u98ce\uff0c\u663e\u793a\u6700\u8fd1\u7f16\u53f7&lt;\/div&gt;&#x27; : &#x27;&#x27;);\n        el.querySelectorAll(&#x27;.ty-item&#x27;).forEach(it =&gt; {\n          it.addEventListener(&#x27;click&#x27;, () =&gt; loadTyphoonTrack(it));\n        });\n      } catch (err) {\n        el.innerHTML = `&lt;div class=&quot;empty&quot;&gt;\u53f0\u98ce\u6570\u636e\u52a0\u8f7d\u5931\u8d25&lt;\/div&gt;`;\n      }\n    }\n\n    async function loadTyphoonTrack(item) {\n      const meta = item.querySelector(&#x27;[data-track]&#x27;);\n      if (item.dataset.loaded === &#x27;1&#x27;) {\n        meta.style.display = meta.style.display === &#x27;none&#x27; ? &#x27;&#x27; : &#x27;none&#x27;;\n        return;\n      }\n      meta.textContent = &#x27;\u52a0\u8f7d\u8def\u5f84\u4e2d...&#x27;;\n      try {\n        const r = await fetch(`${API_BASE}\/api\/typhoon\/track?id=${item.dataset.id}`);\n        if (!r.ok) throw new Error();\n        const d = await r.json();\n        const p = d.latest || {};\n        item.dataset.loaded = &#x27;1&#x27;;\n        meta.innerHTML = `\u6700\u65b0: ${p.time ? p.time.slice(4,6)+&#x27;\/&#x27;+p.time.slice(6,8)+&#x27; &#x27;+p.time.slice(8,10)+&#x27;:00&#x27; : &#x27;\u2014&#x27;}\n          \u00b7 ${p.grade || &#x27;&#x27;} \u00b7 ${p.lat}\u00b0N ${p.lon}\u00b0E\n          \u00b7 \u6c14\u538b ${p.pressure}hPa \u00b7 \u98ce\u901f ${p.windSpeed}m\/s \u00b7 \u79fb\u5411 ${p.moveDir||&#x27;&#x27;} ${p.moveSpeed||&#x27;&#x27;}km\/h\n          \u00b7 \u5171 ${d.points.length} \u4e2a\u8def\u5f84\u70b9`;\n      } catch {\n        meta.textContent = &#x27;\u8def\u5f84\u52a0\u8f7d\u5931\u8d25&#x27;;\n      }\n    }\n\n    function renderMinutely(m) {\n      const el = document.getElementById(&#x27;minutely&#x27;);\n      if (!m || !m.points || !m.points.length) { el.innerHTML = &#x27;&lt;div class=&quot;empty&quot;&gt;\u65e0\u6570\u636e&lt;\/div&gt;&#x27;; return; }\n      const maxP = Math.max(0.5, ...m.points.map(p =&gt; p.precip));\n      const bars = m.points.map(p =&gt; {\n        const h = Math.max(2, (p.precip \/ maxP) * 100);\n        return `&lt;div class=&quot;bar ${p.precip &lt; 0.05 ? &#x27;dry&#x27; : &#x27;&#x27;}&quot; style=&quot;height:${h}%&quot; title=&quot;${p.time.slice(11,16)} ${p.precip}mm&quot;&gt;&lt;\/div&gt;`;\n      }).join(&#x27;&#x27;);\n      el.innerHTML = `\n        &lt;div class=&quot;minutely-summary&quot;&gt;${m.summary}&lt;\/div&gt;\n        &lt;div class=&quot;minutely-bars&quot;&gt;${bars}&lt;\/div&gt;\n        &lt;div class=&quot;minutely-labels&quot;&gt;&lt;span&gt;\u73b0\u5728&lt;\/span&gt;&lt;span&gt;+1h&lt;\/span&gt;&lt;span&gt;+2h&lt;\/span&gt;&lt;\/div&gt;\n      `;\n    }\n\n    function renderHourly(hs) {\n      const el = document.getElementById(&#x27;hourly&#x27;);\n      if (!hs || !hs.length) { el.innerHTML = &#x27;&lt;div class=&quot;empty&quot;&gt;\u65e0\u6570\u636e&lt;\/div&gt;&#x27;; return; }\n      el.innerHTML = `&lt;div class=&quot;hourly-scroll&quot;&gt;${hs.map(h =&gt; {\n        const t = new Date(h.time);\n        const hr = t.getHours().toString().padStart(2, &#x27;0&#x27;);\n        return `\n          &lt;div class=&quot;hour-cell&quot; title=&quot;${h.text} ${h.feelsLike}\u00b0&quot;&gt;\n            &lt;div class=&quot;hr-time&quot;&gt;${hr}\u65f6&lt;\/div&gt;\n            &lt;div class=&quot;hr-temp&quot;&gt;${Math.round(h.temp)}\u00b0&lt;\/div&gt;\n            ${h.pop &gt; 0 ? `&lt;div class=&quot;hr-pop&quot;&gt;\ud83d\udca7${h.pop}%&lt;\/div&gt;` : &#x27;&lt;div class=&quot;hr-pop&quot;&gt;&amp;nbsp;&lt;\/div&gt;&#x27;}\n          &lt;\/div&gt;\n        `;\n      }).join(&#x27;&#x27;)}&lt;\/div&gt;`;\n    }\n\n    function renderIndices(idx) {\n      const el = document.getElementById(&#x27;indices&#x27;);\n      if (!idx) { el.innerHTML = &#x27;&lt;div class=&quot;empty&quot;&gt;\u65e0\u6570\u636e&lt;\/div&gt;&#x27;; return; }\n      const items = [\n        { emoji: &#x27;\u2600\ufe0f&#x27;, label: &#x27;\u7d2b\u5916\u7ebf&#x27;, d: idx.uv, extra: `(${idx.uv.value})` },\n        { emoji: &#x27;\ud83d\udc55&#x27;, label: &#x27;\u7a7f\u8863&#x27;, d: idx.clothing },\n        { emoji: &#x27;\ud83c\udfc3&#x27;, label: &#x27;\u8fd0\u52a8&#x27;, d: idx.sport },\n        { emoji: &#x27;\ud83d\ude97&#x27;, label: &#x27;\u6d17\u8f66&#x27;, d: idx.carwash },\n        { emoji: &#x27;\ud83e\udd27&#x27;, label: &#x27;\u611f\u5192&#x27;, d: idx.cold },\n      ];\n      el.innerHTML = `&lt;div class=&quot;idx-grid&quot;&gt;${items.map(it =&gt; `\n        &lt;div class=&quot;idx-cell&quot;&gt;\n          &lt;div class=&quot;label&quot;&gt;${it.emoji} ${it.label}&lt;\/div&gt;\n          &lt;div class=&quot;val&quot;&gt;${it.d.level} ${it.extra || &#x27;&#x27;}&lt;\/div&gt;\n          &lt;div class=&quot;tip&quot;&gt;${it.d.tip}&lt;\/div&gt;\n        &lt;\/div&gt;\n      `).join(&#x27;&#x27;)}&lt;\/div&gt;`;\n    }\n\n    function renderCurrent(c) {\n      const el = document.getElementById(&#x27;current&#x27;);\n      if (!c) { el.innerHTML = &#x27;&lt;div class=&quot;empty&quot;&gt;\u65e0\u6570\u636e&lt;\/div&gt;&#x27;; return; }\n      el.innerHTML = `\n        &lt;div class=&quot;now&quot;&gt;\n          &lt;div class=&quot;temp&quot;&gt;${Math.round(c.temp)}\u00b0&lt;\/div&gt;\n          &lt;div class=&quot;text&quot;&gt;${c.text}&lt;\/div&gt;\n        &lt;\/div&gt;\n        &lt;div class=&quot;meta&quot;&gt;\n          &lt;div&gt;&lt;span&gt;\u4f53\u611f&lt;\/span&gt;${Math.round(c.feelsLike)}\u00b0&lt;\/div&gt;\n          &lt;div&gt;&lt;span&gt;\u6e7f\u5ea6&lt;\/span&gt;${c.humidity}%&lt;\/div&gt;\n          &lt;div&gt;&lt;span&gt;\u98ce\u5411&lt;\/span&gt;${c.windDir}&lt;\/div&gt;\n          &lt;div&gt;&lt;span&gt;\u98ce\u529b&lt;\/span&gt;${c.windScale} \u7ea7&lt;\/div&gt;\n          &lt;div&gt;&lt;span&gt;\u6c14\u538b&lt;\/span&gt;${c.pressure} hPa&lt;\/div&gt;\n          &lt;div&gt;&lt;span&gt;\u80fd\u89c1\u5ea6&lt;\/span&gt;${c.vis} km&lt;\/div&gt;\n        &lt;\/div&gt;\n        &lt;div class=&quot;source&quot;&gt;\u6765\u6e90: ${c.source}&lt;\/div&gt;\n      `;\n    }\n\n    function renderAir(a) {\n      const el = document.getElementById(&#x27;air&#x27;);\n      if (!a) { el.innerHTML = &#x27;&lt;div class=&quot;empty&quot;&gt;\u65e0\u6570\u636e&lt;\/div&gt;&#x27;; return; }\n      const cls = a.aqi &lt;= 50 ? &#x27;aqi-good&#x27; : a.aqi &lt;= 100 ? &#x27;aqi-mod&#x27; : &#x27;aqi-bad&#x27;;\n      el.innerHTML = `\n        &lt;div&gt;&lt;span class=&quot;aqi-num&quot;&gt;${a.aqi}&lt;\/span&gt;&lt;span class=&quot;aqi-cat ${cls}&quot;&gt;${a.category}&lt;\/span&gt;&lt;\/div&gt;\n        &lt;div class=&quot;meta&quot;&gt;\n          &lt;div&gt;&lt;span&gt;PM2.5&lt;\/span&gt;${a.pm2p5}&lt;\/div&gt;\n          &lt;div&gt;&lt;span&gt;PM10&lt;\/span&gt;${a.pm10}&lt;\/div&gt;\n          &lt;div&gt;&lt;span&gt;O\u2083&lt;\/span&gt;${a.o3}&lt;\/div&gt;\n          &lt;div&gt;&lt;span&gt;NO\u2082&lt;\/span&gt;${a.no2}&lt;\/div&gt;\n        &lt;\/div&gt;\n      `;\n    }\n\n    function levelInfo(level) {\n      const tbl = {\n        &#x27;\u84dd\u8272&#x27;: { cls: &#x27;lvl-blue&#x27;, badge: &#x27;badge-blue&#x27; },\n        &#x27;\u9ec4\u8272&#x27;: { cls: &#x27;lvl-yellow&#x27;, badge: &#x27;badge-yellow&#x27; },\n        &#x27;\u6a59\u8272&#x27;: { cls: &#x27;lvl-orange&#x27;, badge: &#x27;badge-orange&#x27; },\n        &#x27;\u7ea2\u8272&#x27;: { cls: &#x27;lvl-red&#x27;, badge: &#x27;badge-red&#x27; },\n      };\n      for (const k in tbl) if (level &amp;&amp; level.includes(k)) return tbl[k];\n      return { cls: &#x27;lvl-red&#x27;, badge: &#x27;badge-gray&#x27; };\n    }\n\n    function locationOf(w) {\n      \/\/ \u4f18\u5148\u7528\u89e3\u6790\u51fa\u6765\u7684 county\/city\/province\n      const parts = [w.province, w.city, w.county].filter(Boolean);\n      return parts.length ? parts.join(&#x27; \u00b7 &#x27;) : &#x27;&#x27;;\n    }\n\n    function escapeHtml(s) {\n      return (s || &#x27;&#x27;).replace(\/[&amp;&lt;&gt;&quot;&#x27;]\/g, c =&gt; ({\n        &#x27;&amp;&#x27;:&#x27;&amp;amp;&#x27;,&#x27;&lt;&#x27;:&#x27;&amp;lt;&#x27;,&#x27;&gt;&#x27;:&#x27;&amp;gt;&#x27;,&#x27;&quot;&#x27;:&#x27;&amp;quot;&#x27;,&quot;&#x27;&quot;:&#x27;&amp;#39;&#x27;\n      })[c]);\n    }\n\n    function renderWarning(ws) {\n      const el = document.getElementById(&#x27;warning&#x27;);\n      if (!ws || ws.length === 0) {\n        el.innerHTML = &#x27;&lt;div class=&quot;empty&quot;&gt;\u5f53\u524d\u65e0\u6c14\u8c61\u9884\u8b66&lt;\/div&gt;&#x27;;\n        return;\n      }\n      el.innerHTML = ws.map((w, i) =&gt; {\n        const lvl = levelInfo(w.level);\n        const loc = locationOf(w);\n        return `\n          &lt;div class=&quot;warning-item ${lvl.cls}&quot; data-i=&quot;${i}&quot; data-url=&quot;${escapeHtml(w.url || &#x27;&#x27;)}&quot;&gt;\n            &lt;div class=&quot;head&quot;&gt;\n              ${w.pic ? `&lt;div class=&quot;icon&quot;&gt;&lt;img src=&quot;${escapeHtml(w.pic)}&quot; alt=&quot;&quot;&gt;&lt;\/div&gt;` : &#x27;&#x27;}\n              &lt;div class=&quot;head-text&quot;&gt;\n                &lt;div class=&quot;badges&quot;&gt;\n                  ${w.type ? `&lt;span class=&quot;badge badge-gray&quot;&gt;${escapeHtml(w.type)}&lt;\/span&gt;` : &#x27;&#x27;}\n                  ${w.level ? `&lt;span class=&quot;badge ${lvl.badge}&quot;&gt;${escapeHtml(w.level)}&lt;\/span&gt;` : &#x27;&#x27;}\n                  ${loc ? `&lt;span class=&quot;badge badge-gray&quot;&gt;${escapeHtml(loc)}&lt;\/span&gt;` : &#x27;&#x27;}\n                &lt;\/div&gt;\n                &lt;div class=&quot;title&quot;&gt;${escapeHtml(w.title || &#x27;&#x27;)}&lt;\/div&gt;\n                &lt;div class=&quot;time&quot;&gt;${escapeHtml(w.pubTime || &#x27;&#x27;)}&lt;\/div&gt;\n                &lt;div class=&quot;toggle&quot;&gt;\u70b9\u51fb\u5c55\u5f00\u67e5\u770b\u8be6\u60c5 \u25be&lt;\/div&gt;\n              &lt;\/div&gt;\n            &lt;\/div&gt;\n            &lt;div class=&quot;detail&quot; data-loaded=&quot;0&quot;&gt;\n              &lt;div class=&quot;loading-detail&quot;&gt;\u70b9\u51fb\u5c55\u5f00\u540e\u52a0\u8f7d\u8be6\u60c5...&lt;\/div&gt;\n            &lt;\/div&gt;\n          &lt;\/div&gt;\n        `;\n      }).join(&#x27;&#x27;);\n\n      el.querySelectorAll(&#x27;.warning-item&#x27;).forEach(item =&gt; {\n        item.addEventListener(&#x27;click&#x27;, async (e) =&gt; {\n          \/\/ \u70b9\u51fb &lt;a&gt; \u4e0d\u8981\u89e6\u53d1\u6298\u53e0\n          if (e.target.tagName === &#x27;A&#x27;) return;\n          item.classList.toggle(&#x27;open&#x27;);\n          item.querySelector(&#x27;.toggle&#x27;).textContent =\n            item.classList.contains(&#x27;open&#x27;) ? &#x27;\u70b9\u51fb\u6536\u8d77 \u25b4&#x27; : &#x27;\u70b9\u51fb\u5c55\u5f00\u67e5\u770b\u8be6\u60c5 \u25be&#x27;;\n\n          const detail = item.querySelector(&#x27;.detail&#x27;);\n          const url = item.dataset.url;\n          if (item.classList.contains(&#x27;open&#x27;) &amp;&amp; detail.dataset.loaded === &#x27;0&#x27;) {\n            detail.dataset.loaded = &#x27;1&#x27;;\n            await loadWarningDetail(detail, url);\n          }\n        });\n      });\n    }\n\n    async function loadWarningDetail(detailEl, url) {\n      if (!url) {\n        detailEl.innerHTML = &#x27;&lt;div class=&quot;empty&quot;&gt;\u65e0\u8be6\u60c5\u94fe\u63a5&lt;\/div&gt;&#x27;;\n        return;\n      }\n      detailEl.innerHTML = &#x27;&lt;div class=&quot;loading-detail&quot;&gt;\u52a0\u8f7d\u8be6\u60c5\u4e2d...&lt;\/div&gt;&#x27;;\n      try {\n        const r = await fetch(`${API_BASE}\/api\/warning\/detail?url=${encodeURIComponent(url)}`);\n        if (!r.ok) throw new Error(&#x27;\u8bf7\u6c42\u5931\u8d25&#x27;);\n        const d = await r.json();\n        const sections = [];\n        if (d.iconUrl) {\n          sections.push(`&lt;div style=&quot;text-align:center;margin-bottom:10px&quot;&gt;\n            &lt;img src=&quot;${escapeHtml(d.iconUrl)}&quot; style=&quot;height:60px&quot; alt=&quot;&quot;&gt;\n          &lt;\/div&gt;`);\n        }\n        if (d.title) {\n          sections.push(`&lt;div class=&quot;detail-section&quot;&gt;\n            &lt;div class=&quot;sec-label&quot;&gt;\ud83d\udccc \u6807\u9898&lt;\/div&gt;\n            &lt;div class=&quot;sec-body&quot;&gt;${escapeHtml(d.title)}&lt;\/div&gt;\n          &lt;\/div&gt;`);\n        }\n        if (d.pubTime) {\n          sections.push(`&lt;div class=&quot;detail-section&quot;&gt;\n            &lt;div class=&quot;sec-label&quot;&gt;\ud83d\udd50 \u53d1\u5e03\u65f6\u95f4&lt;\/div&gt;\n            &lt;div class=&quot;sec-body&quot;&gt;${escapeHtml(d.pubTime)}&lt;\/div&gt;\n          &lt;\/div&gt;`);\n        }\n        if (d.text) {\n          sections.push(`&lt;div class=&quot;detail-section&quot;&gt;\n            &lt;div class=&quot;sec-label&quot;&gt;\ud83d\udcc4 \u9884\u8b66\u6b63\u6587&lt;\/div&gt;\n            &lt;div class=&quot;sec-body&quot; style=&quot;white-space:pre-wrap&quot;&gt;${escapeHtml(d.text)}&lt;\/div&gt;\n          &lt;\/div&gt;`);\n        }\n        if (d.defenseGuide) {\n          sections.push(`&lt;div class=&quot;detail-section&quot;&gt;\n            &lt;div class=&quot;sec-label&quot;&gt;\ud83d\udee1\ufe0f \u9632\u5fa1\u6307\u5357&lt;\/div&gt;\n            &lt;div class=&quot;sec-body&quot;&gt;${escapeHtml(d.defenseGuide)}&lt;\/div&gt;\n          &lt;\/div&gt;`);\n        }\n        sections.push(`&lt;div style=&quot;margin-top:8px&quot;&gt;\n          &lt;a href=&quot;${escapeHtml(url)}&quot; target=&quot;_blank&quot;&gt;\u5728 nmc.cn \u67e5\u770b\u539f\u6587 \u2197&lt;\/a&gt;\n        &lt;\/div&gt;`);\n        detailEl.innerHTML = sections.join(&#x27;&#x27;);\n      } catch (err) {\n        detailEl.innerHTML = `&lt;div class=&quot;empty&quot;&gt;\u52a0\u8f7d\u8be6\u60c5\u5931\u8d25: ${escapeHtml(err.message)}&lt;\/div&gt;`;\n      }\n    }\n\n    function renderForecast(fs, airfc) {\n      const el = document.getElementById(&#x27;forecast&#x27;);\n      if (!fs || fs.length === 0) { el.innerHTML = &#x27;&lt;div class=&quot;empty&quot;&gt;\u65e0\u6570\u636e&lt;\/div&gt;&#x27;; return; }\n      const airMap = {};\n      (airfc || []).forEach(a =&gt; { airMap[a.date] = a; });\n      const color = aqi =&gt; aqi &lt;= 50 ? &#x27;#2ecc71&#x27; : aqi &lt;= 100 ? &#x27;#f39c12&#x27; : aqi &lt;= 150 ? &#x27;#e67e22&#x27; : &#x27;#e74c3c&#x27;;\n      el.innerHTML = fs.map(d =&gt; {\n        const dt = new Date(d.date);\n        const day = [&#x27;\u65e5&#x27;,&#x27;\u4e00&#x27;,&#x27;\u4e8c&#x27;,&#x27;\u4e09&#x27;,&#x27;\u56db&#x27;,&#x27;\u4e94&#x27;,&#x27;\u516d&#x27;][dt.getDay()];\n        const a = airMap[d.date];\n        const aqiCell = a\n          ? `&lt;div class=&quot;fc-aqi&quot;&gt;&lt;span class=&quot;dot&quot; style=&quot;background:${color(a.aqi)}&quot;&gt;${a.category}&lt;\/span&gt;&lt;span class=&quot;n&quot;&gt;${a.aqi}&lt;\/span&gt;&lt;\/div&gt;`\n          : `&lt;div class=&quot;fc-aqi&quot;&gt;&lt;span class=&quot;n&quot;&gt;\u2014&lt;\/span&gt;&lt;\/div&gt;`;\n        return `\n          &lt;div class=&quot;forecast-row&quot;&gt;\n            &lt;div class=&quot;date&quot;&gt;\u5468${day}&lt;br&gt;&lt;small style=&quot;opacity:.6&quot;&gt;${d.date.slice(5)}&lt;\/small&gt;&lt;\/div&gt;\n            &lt;div&gt;${d.textDay}&lt;\/div&gt;\n            ${aqiCell}\n            &lt;div class=&quot;temps&quot;&gt;&lt;span class=&quot;max&quot;&gt;${Math.round(d.tempMax)}\u00b0&lt;\/span&gt;&lt;span class=&quot;min&quot;&gt;${Math.round(d.tempMin)}\u00b0&lt;\/span&gt;&lt;\/div&gt;\n          &lt;\/div&gt;\n        `;\n      }).join(&#x27;&#x27;);\n    }\n\n    document.getElementById(&#x27;searchBtn&#x27;).onclick = doSearch;\n    document.getElementById(&#x27;searchInput&#x27;).addEventListener(&#x27;keydown&#x27;, e =&gt; {\n      if (e.key === &#x27;Enter&#x27;) doSearch();\n    });\n\n    \/\/ \u6839\u636e\u641c\u7d22\u7ed3\u679c + \u7528\u6237\u8f93\u5165\u63a8\u65ad\u7c92\u5ea6\n    function detectLevel(q, result) {\n      \/\/ \u4f18\u5148\u770b Nominatim \u8fd4\u56de\u7684 addresstype\n      const at = (result.addresstype || result.type || &#x27;&#x27;).toLowerCase();\n      if ([&#x27;state&#x27;, &#x27;province&#x27;, &#x27;region&#x27;].includes(at)) return &#x27;province&#x27;;\n      if ([&#x27;city&#x27;, &#x27;town&#x27;, &#x27;county&#x27;, &#x27;municipality&#x27;].includes(at)) return &#x27;city&#x27;;\n      if ([&#x27;suburb&#x27;, &#x27;city_district&#x27;, &#x27;quarter&#x27;, &#x27;borough&#x27;, &#x27;neighbourhood&#x27;].includes(at))\n        return &#x27;district&#x27;;\n      \/\/ \u515c\u5e95: \u6309\u8f93\u5165\u540e\u7f00\n      if (\/\u7701$|\u81ea\u6cbb\u533a$|\u7279\u522b\u884c\u653f\u533a$\/.test(q)) return &#x27;province&#x27;;\n      if (\/\u5e02$|\u5dde$|\u76df$\/.test(q)) return &#x27;city&#x27;;\n      if (\/\u533a$|\u53bf$|\u65d7$\/.test(q)) return &#x27;district&#x27;;\n      return &#x27;district&#x27;;\n    }\n\n    async function doSearch() {\n      const q = document.getElementById(&#x27;searchInput&#x27;).value.trim();\n      if (!q) return;\n      try {\n        const url = `https:\/\/nominatim.openstreetmap.org\/search?format=json&amp;limit=1&amp;addressdetails=1&amp;q=${encodeURIComponent(q)}`;\n        const r = await fetch(url, { headers: { &#x27;Accept-Language&#x27;: &#x27;zh-CN&#x27; } });\n        const arr = await r.json();\n        if (!arr.length) throw new Error(&#x27;\u627e\u4e0d\u5230\u8be5\u5730\u70b9&#x27;);\n        const level = detectLevel(q, arr[0]);\n        \/\/ \u628a\u7528\u6237\u7684\u5177\u4f53\u67e5\u8be2\u540d\u4f20\u7ed9\u540e\u7aef, \u907f\u514d Photon \u53cd\u5411\u5730\u7406\u8bef\u5224\u7ea7\u522b\n        loadWeather(+arr[0].lat, +arr[0].lon, level, q);\n      } catch (err) {\n        alert(err.message);\n      }\n    }\n  &lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n\"><\/iframe>\n<\/div>\n<script>(function(){var f=document.getElementById(\"weather-app-frame\");window.addEventListener(\"message\",function(e){var h=e&&e.data&&e.data.__wapp_h;if(h&&h>200){f.style.height=(h+20)+\"px\";}});})();<\/script>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"emotion":"","emotion_color":"","title_style":"","license":"","footnotes":""},"_links":{"self":[{"href":"https:\/\/www.explore2022.com\/index.php?rest_route=\/wp\/v2\/pages\/936"}],"collection":[{"href":"https:\/\/www.explore2022.com\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.explore2022.com\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.explore2022.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.explore2022.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=936"}],"version-history":[{"count":4,"href":"https:\/\/www.explore2022.com\/index.php?rest_route=\/wp\/v2\/pages\/936\/revisions"}],"predecessor-version":[{"id":984,"href":"https:\/\/www.explore2022.com\/index.php?rest_route=\/wp\/v2\/pages\/936\/revisions\/984"}],"wp:attachment":[{"href":"https:\/\/www.explore2022.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=936"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}