<!doctype html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Dytchem的订阅 - 高性价比 VPS 与 AI 订阅追踪</title>
<meta name="description" content="Dytchem 的订阅脱敏展示：17个 VPS/AI 订阅，月度平均 ¥255.28、年度平均 ¥3063.36、未来一月内应缴 ¥212.92；按价格降序排列，托管于 AndNode，数据来自 Wallos + Fixer 实时汇率。">
<meta name="keywords" content="Dytchem,VPS,订阅追踪,Wallos,AndNode,Fixer,云服务器,NAT VPS">
<meta name="robots" content="index, follow">
<link rel="canonical" href="https://wallos.dytchem.cn/public/">
<meta property="og:title" content="Dytchem的订阅">
<meta property="og:description" content="17个 VPS/AI 订阅 · 月度平均 ¥255.28 · 年度平均 ¥3063.36 · 未来一月内应缴 ¥212.92 · 托管于 AndNode">
<meta property="og:url" content="https://wallos.dytchem.cn/public/">
<meta property="og:type" content="website">
<meta property="og:site_name" content="Dytchem">
<style>
:root{--bg:#0b0e11;--card:#1e2329;--line:#2b3139;--text:#eaecef;--muted:#848e9c;--yellow:#fcd535;--green:#0ecb81;--bar-bg:#262b31;--cat-bg:#2a313b;--red:#ef454a;--pct-high:#ef454a;--pct-midhigh:#f59e0b;--pct-mid:#eab308;--pct-low:#6b7280;
/* Category Okabe-Ito+Tableau 12-color 色盲安全 高区分度 - 前4色蓝橙绿红刻意拉开 */
--cat-c0-bg:rgba(230,159,0,.20);--cat-c0-color:#E69F00;--cat-c0-border:rgba(230,159,0,.38);
--cat-c1-bg:rgba(86,180,233,.20);--cat-c1-color:#56B4E9;--cat-c1-border:rgba(86,180,233,.38);
--cat-c2-bg:rgba(0,158,115,.20);--cat-c2-color:#009E73;--cat-c2-border:rgba(0,158,115,.38);
--cat-c3-bg:rgba(204,121,167,.20);--cat-c3-color:#CC79A7;--cat-c3-border:rgba(204,121,167,.38);
--cat-c4-bg:rgba(0,114,178,.20);--cat-c4-color:#0072B2;--cat-c4-border:rgba(0,114,178,.38);
--cat-c5-bg:rgba(213,94,0,.20);--cat-c5-color:#D55E00;--cat-c5-border:rgba(213,94,0,.38);
--cat-c6-bg:rgba(240,228,66,.20);--cat-c6-color:#F0E442;--cat-c6-border:rgba(240,228,66,.38);
--cat-c7-bg:rgba(78,121,167,.20);--cat-c7-color:#4E79A7;--cat-c7-border:rgba(78,121,167,.38);
--cat-c8-bg:rgba(242,142,43,.20);--cat-c8-color:#F28E2B;--cat-c8-border:rgba(242,142,43,.38);
--cat-c9-bg:rgba(89,161,79,.20);--cat-c9-color:#59A14F;--cat-c9-border:rgba(89,161,79,.38);
--cat-c10-bg:rgba(176,122,161,.20);--cat-c10-color:#B07AA1;--cat-c10-border:rgba(176,122,161,.38);
--cat-c11-bg:rgba(156,117,95,.20);--cat-c11-color:#9C755F;--cat-c11-border:rgba(156,117,95,.38);
}
[data-theme="light"]{--bg:#f5f7fb;--card:#ffffff;--line:#e5e7eb;--text:#111827;--muted:#6b7280;--bar-bg:#eceef2;--cat-bg:#eef1f6;--red:#ef454a;--pct-high:#dc2626;--pct-midhigh:#d97706;--pct-mid:#a16207;--pct-low:#9ca3af;
--cat-c0-bg:rgba(230,159,0,.16);--cat-c0-color:#8a5a00;--cat-c0-border:rgba(230,159,0,.30);
--cat-c1-bg:rgba(86,180,233,.16);--cat-c1-color:#2f6d8a;--cat-c1-border:rgba(86,180,233,.30);
--cat-c2-bg:rgba(0,158,115,.16);--cat-c2-color:#00664a;--cat-c2-border:rgba(0,158,115,.30);
--cat-c3-bg:rgba(204,121,167,.16);--cat-c3-color:#7a3a5a;--cat-c3-border:rgba(204,121,167,.30);
--cat-c4-bg:rgba(0,114,178,.16);--cat-c4-color:#004a7a;--cat-c4-border:rgba(0,114,178,.30);
--cat-c5-bg:rgba(213,94,0,.16);--cat-c5-color:#8a3b00;--cat-c5-border:rgba(213,94,0,.30);
--cat-c6-bg:rgba(240,228,66,.16);--cat-c6-color:#8a8000;--cat-c6-border:rgba(240,228,66,.30);
--cat-c7-bg:rgba(78,121,167,.16);--cat-c7-color:#2e4a66;--cat-c7-border:rgba(78,121,167,.30);
--cat-c8-bg:rgba(242,142,43,.16);--cat-c8-color:#8a4e10;--cat-c8-border:rgba(242,142,43,.30);
--cat-c9-bg:rgba(89,161,79,.16);--cat-c9-color:#2f5a2f;--cat-c9-border:rgba(89,161,79,.30);
--cat-c10-bg:rgba(176,122,161,.16);--cat-c10-color:#6b3d5a;--cat-c10-border:rgba(176,122,161,.30);
--cat-c11-bg:rgba(156,117,95,.16);--cat-c11-color:#5a3d2f;--cat-c11-border:rgba(156,117,95,.30);
}
*{box-sizing:border-box} body{margin:0;background:var(--bg);color:var(--text);font-family:BinancePlex,Inter,ui-sans,system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial}
.wrap{max-width:1120px;margin:0 auto;padding:24px 16px 28px}
.page-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px} .page-head-actions{display:flex;align-items:center;gap:8px} .refresh-btn{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--muted);background:var(--card);border:1px solid var(--line);padding:7px 12px;border-radius:999px;cursor:pointer} .refresh-btn:hover{border-color:var(--muted);color:var(--text)} .refresh-btn:disabled{opacity:.6;cursor:not-allowed}
.page-head h1{font-size:22px;font-weight:700;margin:0;letter-spacing:.2px}
.theme-switch{display:flex;gap:6px;align-items:center;background:var(--card);border:1px solid var(--line);border-radius:999px;padding:3px}
.theme-switch button{width:28px;height:28px;border-radius:50%;border:1px solid transparent;background:transparent;color:var(--muted);cursor:pointer;font-size:13px;line-height:1;display:grid;place-items:center}
.theme-switch button.active{background:var(--text);color:var(--bg);border-color:var(--line)}
.theme-switch button:hover{border-color:var(--line)}
.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:14px}
.filters{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:10px 12px;margin-bottom:16px}
.filter-row{display:flex;align-items:center;gap:8px;flex-wrap:nowrap}
#catSelect,#cycleSelect,#sortSelect{text-align:center;text-align-last:center}
.filter-group{display:flex;gap:6px;flex-wrap:wrap}
.filter-group button{font-size:12px;color:var(--muted);background:transparent;border:1px solid var(--line);padding:6px 10px;border-radius:999px;cursor:pointer}
.filter-group button.active{background:var(--text);color:var(--bg);border-color:var(--text)}
.search-wrap{width:100%;margin-left:0;flex:none}
.search-wrap input{width:100%;background:var(--bg);border:1px solid var(--line);border-radius:999px;padding:7px 12px;font-size:12px;color:var(--text);outline:none;text-align:center}
.search-wrap input::placeholder{color:var(--muted);text-align:center}
.stat{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:14px 16px} .label{display:block;font-size:11px;color:var(--muted);margin-bottom:6px} .value{font-size:18px;font-weight:700}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px}
.card{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:14px 14px 12px;position:relative;cursor:pointer;user-select:text;-webkit-user-select:text;-webkit-touch-callout:default;touch-action:auto;transition:border-color .18s,box-shadow .2s} .card:hover{box-shadow:0 0 0 1px var(--line) inset,0 8px 24px rgba(0,0,0,.18)} html,body{ -webkit-touch-callout:default; user-select:text; -webkit-user-select:text; } .card:active{box-shadow:0 0 0 1px var(--line) inset,0 8px 24px rgba(0,0,0,.18)} .card.selected{border-color:var(--yellow);box-shadow:0 0 0 1px var(--yellow) inset,0 8px 24px rgba(252,213,53,.22)} .card.selected:hover,.card.selected:active{box-shadow:0 0 0 1px var(--yellow) inset,0 12px 28px rgba(252,213,53,.32)} .card.selected .dot{background:var(--yellow);box-shadow:0 0 0 6px rgba(252,213,53,.18)} .card .check{position:absolute;top:10px;left:10px;width:20px;height:20px;border-radius:50%;background:var(--yellow);color:#111;display:none;place-items:center;font-size:12px;font-weight:700;line-height:1;z-index:1} .card.selected .check{display:grid} .card-top{display:flex;align-items:center;gap:14px;margin-bottom:8px;padding-right:14px;justify-content:flex-start} .logo-inline{margin-left:0;display:flex;align-items:center;flex:0 0 auto} .cat{font-size:11px;color:var(--cat-color,var(--muted));background:var(--cat-bg);border:1px solid var(--cat-border,var(--line));padding:2px 7px;border-radius:999px;min-width:44px;width:44px;flex:0 0 44px;display:inline-flex;justify-content:center;text-align:center}.cat-c0{--cat-bg:var(--cat-c0-bg);--cat-color:var(--cat-c0-color);--cat-border:var(--cat-c0-border)}.cat-c1{--cat-bg:var(--cat-c1-bg);--cat-color:var(--cat-c1-color);--cat-border:var(--cat-c1-border)}.cat-c2{--cat-bg:var(--cat-c2-bg);--cat-color:var(--cat-c2-color);--cat-border:var(--cat-c2-border)}.cat-c3{--cat-bg:var(--cat-c3-bg);--cat-color:var(--cat-c3-color);--cat-border:var(--cat-c3-border)}.cat-c4{--cat-bg:var(--cat-c4-bg);--cat-color:var(--cat-c4-color);--cat-border:var(--cat-c4-border)}.cat-c5{--cat-bg:var(--cat-c5-bg);--cat-color:var(--cat-c5-color);--cat-border:var(--cat-c5-border)}.cat-c6{--cat-bg:var(--cat-c6-bg);--cat-color:var(--cat-c6-color);--cat-border:var(--cat-c6-border)}.cat-c7{--cat-bg:var(--cat-c7-bg);--cat-color:var(--cat-c7-color);--cat-border:var(--cat-c7-border)}.cat-c8{--cat-bg:var(--cat-c8-bg);--cat-color:var(--cat-c8-color);--cat-border:var(--cat-c8-border)}.cat-c9{--cat-bg:var(--cat-c9-bg);--cat-color:var(--cat-c9-color);--cat-border:var(--cat-c9-border)}.cat-c10{--cat-bg:var(--cat-c10-bg);--cat-color:var(--cat-c10-color);--cat-border:var(--cat-c10-border)}.cat-c11{--cat-bg:var(--cat-c11-bg);--cat-color:var(--cat-c11-color);--cat-border:var(--cat-c11-border)} .dot{position:absolute;top:14px;right:14px;width:8px;height:8px;border-radius:50%;background:var(--green);box-shadow:0 0 0 6px rgba(14,203,129,.14)} .card.is-inactive{opacity:.48;filter:saturate(.35);} .card.is-inactive .dot{background:#cbd5e1;box-shadow:0 0 0 6px rgba(203,213,225,.18)} .card.is-inactive.selected .dot{background:#cbd5e1;box-shadow:0 0 0 6px rgba(203,213,225,.18)} .card.is-inactive .pct{color:#a8b0bf} .card.is-inactive .bar i{background:#cbd5e1} .card.is-inactive .name{color:var(--muted)} .card.is-inactive .price{opacity:.85} .pct{position:absolute;top:14px;right:32px;height:8px;display:inline-flex;align-items:center;font-size:10px;font-weight:700;line-height:1;letter-spacing:.2px;pointer-events:none} .pct-high{color:var(--pct-high)} .pct-midhigh{color:var(--pct-midhigh)} .pct-mid{color:var(--pct-mid)} .pct-low{color:var(--pct-low)}
.name{font-size:15.5px;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:6px} .logo-img{height:18px;width:auto;max-width:88px;object-fit:contain;background:transparent;padding:0;border:none;display:block;border-radius:4px} .logo-img.logo-wide{height:18px;max-width:88px} .avatar{background:transparent;border:none;padding:0;min-width:0;height:auto;display:inline-flex;align-items:center;font-size:12px;font-weight:700;color:var(--muted);flex:0 0 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:88px;letter-spacing:.2px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;text-transform:none}
.price-line{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:4px;flex-wrap:wrap} .price{font-size:14px;font-weight:700;display:inline-flex;align-items:center;gap:3px} .price .cycle{font-size:12px;font-weight:600;color:var(--muted)} .approx{font-size:11px;color:var(--muted);font-weight:500;display:inline-flex;align-items:center} .renew{font-size:12px;color:var(--muted);display:inline-flex;align-items:center}
.meta{display:flex;justify-content:space-between;font-size:12px;color:var(--muted);margin-top:4px} .notes{font-size:12px;color:var(--muted);margin-top:6px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;opacity:.9}
.bar{height:3px;background:var(--bar-bg);border-radius:999px;margin-top:10px;overflow:hidden;border:1px solid var(--line)} .bar i{display:block;height:100%;background:var(--yellow);border-radius:999px}
.empty{grid-column:1/-1;background:var(--card);border:1px dashed var(--line);border-radius:12px;padding:28px;text-align:center;color:var(--muted)}
.foot{margin-top:24px;padding:16px 0 8px;border-top:1px solid var(--line);display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px;color:var(--muted);font-size:12px}
.foot a:visited{color:var(--muted)}
.foot-main{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.foot-main a{color:var(--muted);text-decoration:none}
.foot-main a:hover{color:var(--text);text-decoration:underline}
.foot-powered{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
.foot-powered span{color:var(--muted);opacity:1}
.foot-powered img{height:18px;width:auto;vertical-align:middle;object-fit:contain;border-radius:4px}
.foot-powered .wallos-logo{background:transparent;padding:0}
.foot-links{display:flex;gap:10px;flex-wrap:wrap;align-items:center}
.foot-links a{color:var(--muted);text-decoration:none;display:grid;place-items:center;width:32px;height:32px;border-radius:50%;border:1px solid var(--line);background:var(--card);font-size:14px;opacity:1;transition:all .18s}
.foot-links a:hover{color:var(--text);border-color:var(--muted);transform:translateY(-1px)}
.chart-expand{margin-top:4px;padding:0;border-top:none;}
.chart-toggle{width:100%;display:flex;align-items:center;justify-content:center;gap:4px;font-size:11px;color:var(--muted);background:transparent;border:none;border-radius:0;padding:10px 12px;cursor:pointer;transition:color .18s}
.chart-toggle:hover{border-color:var(--muted);color:var(--text)}
.chart-toggle .arrow{display:inline-block;transition:transform .2s;font-size:10px}
.chart-toggle.open .arrow{transform:rotate(180deg)}
.chart-panel{margin-top:6px;display:flex;flex-direction:column;align-items:center;gap:20px;justify-content:center}
.chart-canvas-wrap,.chart-bar-wrap{position:relative;}
@media(min-width:720px){.chart-panel{flex-direction:row;align-items:center;justify-content:space-evenly;gap:24px} .chart-canvas-wrap,.chart-bar-wrap{flex:0 0 auto;max-width:none;display:block;position:relative}}}
.chart-canvas-wrap{position:relative;flex:0 0 auto}
#expensePie{max-width:280px;max-height:280px;display:block}
#expenseBar{width:100%;max-width:360px;max-height:360px;display:block}
.chart-bar-wrap{position:relative;width:100%;max-width:360px;flex:0 0 auto}
.chart-tooltip{position:absolute;left:0;top:0;transform:translate(-50%,-120%);background:var(--card);border:1px solid var(--line);border-radius:8px;padding:7px 9px;font-size:11px;line-height:1.4;white-space:nowrap;pointer-events:none;opacity:0;transition:opacity .12s;box-shadow:0 8px 24px rgba(0,0,0,.18);z-index:2}
#chartTooltip{position:absolute;left:50%;top:0;transform:translate(-50%,0);margin:0;display:block;text-align:center;opacity:0;white-space:nowrap;} #chartTooltip.show{opacity:1;}
#barTooltip{transform:translate(0,-120%);}
.chart-tooltip.show{opacity:1}
.chart-tooltip .tt-name{font-weight:700}
.chart-tooltip .tt-val{color:var(--muted)}
.chart-tooltip .tt-dot{display:inline-block;width:8px;height:8px;border-radius:2px;vertical-align:middle;margin-right:6px}
@media(max-width:640px){.foot{flex-direction:column;align-items:center;gap:12px;text-align:center} .foot-main{justify-content:center} .foot-powered{justify-content:center} .foot-links{justify-content:center} }
@media(max-width:640px){.stats{grid-template-columns:repeat(2,1fr);gap:8px} .stat{padding:10px 12px} .value{font-size:15px} .filters{padding:10px 12px} .filter-row{gap:6px} #catSelect,#cycleSelect,#sortSelect{padding:6px 8px;font-size:11px;flex:1 1 0;min-width:0;max-width:none} #selectAllBtn,#clearSelectBtn{padding:6px 8px;font-size:11px;white-space:nowrap} }
</style>
</head>
<body>
<div class="wrap">
  <div class="page-head">
    <h1>Dytchem的订阅</h1>
    <div class="page-head-actions">
      <div class="theme-switch" id="themeSwitch">
        <button data-mode="light" title="白天">☀️</button>
        <button data-mode="dark" title="黑夜">🌙</button>
        <button data-mode="auto" title="自动" class="active">A</button>
      </div>
      <button id="hardRefresh" class="refresh-btn" title="强制刷新获取最新数据">⟳ 刷新</button>
    </div>
  </div>
  
<div class="stats" id="statsBar">
  <div class="stat"><span class="label" id="statCountLabel">订阅数</span><span class="value" id="statCount" data-total="17">17</span><span style="font-size:11px;color:var(--muted);font-weight:500;margin-left:4px" id="statInactiveBadge">+ 2 已停用</span></div>
  <div class="stat"><span class="label" id="statMonthlyLabel">月度平均</span><span class="value" id="statMonthly" data-total="255.2797939280">¥255.28</span></div>
  <div class="stat"><span class="label" id="statYearlyLabel">年度平均</span><span class="value" id="statYearly" data-total="3063.3575271365">¥3063.36</span></div>
  <div class="stat"><span class="label" id="statDue30Label">未来一月内应缴</span><span class="value" id="statDue30" data-total="212.9198725970">¥212.92</span></div>
</div>
  <div class="filters">
    <div class="search-wrap" style="margin-bottom:8px">
      <input type="search" id="searchInput" placeholder="搜索" style="text-align:center" autocomplete="off">
    </div>
    <div class="filter-row">
      <div class="filter-group" style="flex:1 1 0;min-width:0">
        <select id="catSelect" style="width:100%;height:32px;font-size:12px;color:var(--muted);background:var(--bg);border:1px solid var(--line);border-radius:999px;padding:0 10px;text-align:center;text-align-last:center;">
          <option value="all">全部分类</option><option value="AI">AI</option><option value="DS">DS</option><option value="Net">Net</option><option value="VPS">VPS</option>
        </select>
      </div>
      <div class="filter-group" style="flex:1 1 0;min-width:0">
        <select id="cycleSelect" style="width:100%;height:32px;font-size:12px;color:var(--muted);background:var(--bg);border:1px solid var(--line);border-radius:999px;padding:0 10px;text-align:center;text-align-last:center;">
          <option value="all">全部周期</option>
          <option value="月">月付</option>
          <option value="年">年付</option>
        </select>
      </div>
      <div class="filter-group" style="flex:1 1 0;min-width:0">
        <select id="sortSelect" autocomplete="off" style="width:100%;height:32px;font-size:12px;color:var(--muted);background:var(--bg);border:1px solid var(--line);border-radius:999px;padding:0 10px;text-align:center;text-align-last:center;">
          <option value="next-asc">续费时间↑</option>
          <option value="next-desc">续费时间↓</option>
          <option value="price-asc">价格↑</option>
          <option value="price-desc" selected>价格↓</option>
        </select>
      </div>
      <div class="filter-group" style="flex:1 1 0;min-width:0">
        <button id="selectAllBtn" style="width:100%;height:32px;font-size:12px;padding:0 10px;border-radius:999px;border:1px solid var(--line);background:transparent;color:var(--text);cursor:pointer;white-space:nowrap">全部选中</button>
      </div>
      <div class="filter-group" style="flex:1 1 0;min-width:0">
        <button id="clearSelectBtn" style="width:100%;height:32px;font-size:12px;padding:0 10px;border-radius:999px;border:1px solid var(--line);background:transparent;color:var(--text);cursor:pointer;white-space:nowrap">清除选中</button>
      </div>
    </div>
    <div class="chart-expand">
      <button id="chartToggle" class="chart-toggle">展开图表 <span class="arrow">▾</span></button>
      <div id="chartPanel" class="chart-panel" style="display:none">
        <div class="chart-canvas-wrap"><canvas id="expensePie" width="320" height="320"></canvas><div id="chartTooltip" class="chart-tooltip"></div></div>
        <div class="chart-bar-wrap"><canvas id="expenseBar" width="360" height="360"></canvas><div id="barTooltip" class="chart-tooltip"></div></div>
      </div>
    </div>
  </div>
  <div class="grid" id="cardGrid">
    
    <div class="card" data-category="AI" data-cycle="/月" data-name="opencode go" data-notes="5 小时 12 美元额度，每周 30 美元额度，每月 60 美元额度" data-price="67.2274736256" data-next="1788796800" data-due30="67.2274736256">
      <span class="check">✓</span><div class="card-top"><span class="cat cat-c0">AI</span><span class="logo-inline"><img class="logo-img logo-wide" src="/logos/1787405855-OpenCode-Go.png" alt="OpenCode Go"></span><span class="pct pct-high">26.3%</span><span class="dot"></span></div>
      <div class="name">OpenCode Go</div>
      <div class="price-line"><span class="price">10.00 USD<span class="cycle">/月</span><span class="approx">≈ ¥67.23 /月</span></span><span class="renew">11天后 9月8日 续费</span></div>
      <div class="notes">5 小时 12 美元额度，每周 30 美元额度，每月 60 美元额度</div>
      <div class="bar"><i style="width:63%"></i></div>
    </div>
    <div class="card" data-category="VPS" data-cycle="/月" data-name="andnode 美国轻量-a型" data-notes="2c 2g 30g 500g 50mbps la netlab 优化线路" data-price="12.8000000000" data-next="1789574400" data-due30="12.8000000000">
      <span class="check">✓</span><div class="card-top"><span class="cat cat-c3">VPS</span><span class="logo-inline"><img class="logo-img logo-wide" src="/logos/1787405781-AndNode-A.png" alt="AndNode 美国轻量-A型"></span><span class="pct pct-mid">5.0%</span><span class="dot"></span></div>
      <div class="name">AndNode 美国轻量-A型</div>
      <div class="price-line"><span class="price">12.80 CNY<span class="cycle">/月</span><span class="approx">≈ ¥12.80 /月</span></span><span class="renew">20天后 9月17日 续费</span></div>
      <div class="notes">2C 2G 30G 500G 50Mbps LA NetLab 优化线路</div>
      <div class="bar"><i style="width:33%"></i></div>
    </div>
    <div class="card" data-category="VPS" data-cycle="/月" data-name="raksmart sv-vps/cloud-vm(shared)" data-notes="2c 4g 50g 1t 硅谷 大陆优化vip" data-price="13.3782672515" data-next="1789920000" data-due30="13.3782672515">
      <span class="check">✓</span><div class="card-top"><span class="cat cat-c3">VPS</span><span class="logo-inline"><img class="logo-img logo-wide" src="/logos/1787405731-RakSmart-SVVPSCloudVMShared.png" alt="RakSmart SV-VPS/Cloud-VM(Shared)"></span><span class="pct pct-mid">5.2%</span><span class="dot"></span></div>
      <div class="name">RakSmart SV-VPS/Cloud-VM(Shared)</div>
      <div class="price-line"><span class="price">1.99 USD<span class="cycle">/月</span><span class="approx">≈ ¥13.38 /月</span></span><span class="renew">24天后 9月21日 续费</span></div>
      <div class="notes">2C 4G 50G 1T 硅谷 大陆优化VIP</div>
      <div class="bar"><i style="width:20%"></i></div>
    </div>
    <div class="card" data-category="VPS" data-cycle="/月" data-name="vmiss us.la.tri.basic" data-notes="1c 1g 10g 500g 200mbps la 三网优化" data-price="21.8262426042" data-next="1790006400" data-due30="21.8262426042">
      <span class="check">✓</span><div class="card-top"><span class="cat cat-c3">VPS</span><span class="logo-inline"><img class="logo-img logo-wide" src="/logos/1787405641-VMISS-USLATRIBasic.png" alt="VMISS US.LA.TRI.Basic"></span><span class="pct pct-midhigh">8.5%</span><span class="dot"></span></div>
      <div class="name">VMISS US.LA.TRI.Basic</div>
      <div class="price-line"><span class="price">4.50 CAD<span class="cycle">/月</span><span class="approx">≈ ¥21.83 /月</span></span><span class="renew">25天后 9月22日 续费</span></div>
      <div class="notes">1C 1G 10G 500G 200Mbps LA 三网优化</div>
      <div class="bar"><i style="width:16%"></i></div>
    </div>
    <div class="card" data-category="VPS" data-cycle="/月" data-name="geelinx my.kul.mco.expert" data-notes="6c 10g 50g 2t 40mbps kuala lumpur mco 大陆优化" data-price="30.4604154900" data-next="1790179200" data-due30="30.4604154900">
      <span class="check">✓</span><div class="card-top"><span class="cat cat-c3">VPS</span><span class="logo-inline"><img class="logo-img logo-wide" src="/logos/1787653983-Geelinx-MYKULMCOExpert.png" alt="Geelinx MY.KUL.MCO.Expert"></span><span class="pct pct-midhigh">11.9%</span><span class="dot"></span></div>
      <div class="name">Geelinx MY.KUL.MCO.Expert</div>
      <div class="price-line"><span class="price">3.89 EUR<span class="cycle">/月</span><span class="approx">≈ ¥30.46 /月</span></span><span class="renew">27天后 9月24日 续费</span></div>
      <div class="notes">6C 10G 50G 2T 40Mbps Kuala Lumpur MCO 大陆优化</div>
      <div class="bar"><i style="width:10%"></i></div>
    </div>
    <div class="card" data-category="DS" data-cycle="/月" data-name="ovh ks-b | intel xeon e5-1620v2 gra" data-notes="独服 4c/8t 3.7ghz/3.9ghz 32g 120g 500mbps 无限流量" data-price="67.2274736256" data-next="1790265600" data-due30="67.2274736256">
      <span class="check">✓</span><div class="card-top"><span class="cat cat-c1">DS</span><span class="logo-inline"><img class="logo-img logo-wide" src="/logos/1787652994-OVH-KSB--Intel-Xeon-E51620v2-.png" alt="OVH KS-B | Intel Xeon E5-1620v2 gra"></span><span class="pct pct-high">26.3%</span><span class="dot"></span></div>
      <div class="name">OVH KS-B | Intel Xeon E5-1620v2 gra</div>
      <div class="price-line"><span class="price">10.00 USD<span class="cycle">/月</span><span class="approx">≈ ¥67.23 /月</span></span><span class="renew">28天后 9月25日 续费</span></div>
      <div class="notes">独服 4c/8t 3.7GHz/3.9GHz 32G 120G 500Mbps 无限流量</div>
      <div class="bar"><i style="width:6%"></i></div>
    </div>
    <div class="card" data-category="VPS" data-cycle="/月" data-name="tierhive nat机" data-notes="按小时计费nat机 最低$0.1/月 目前au/fr/jp/sg四台$0.2/月" data-price="5.5798803109" data-next="1793462400" data-due30="0.0000000000">
      <span class="check">✓</span><div class="card-top"><span class="cat cat-c3">VPS</span><span class="logo-inline"><span class="avatar">TierHive</span></span><span class="pct pct-low">2.2%</span><span class="dot"></span></div>
      <div class="name">TierHive NAT机</div>
      <div class="price-line"><span class="price">0.83 USD<span class="cycle">/月</span><span class="approx">≈ ¥5.58 /月</span></span><span class="renew">65天后 11月1日 续费</span></div>
      <div class="notes">按小时计费NAT机 最低$0.1/月 目前au/fr/jp/sg四台$0.2/月</div>
      <div class="bar"><i style="width:0%"></i></div>
    </div>
    <div class="card" data-category="VPS" data-cycle="/月" data-name="cloudnium los angeles - kvm vps" data-notes="2c 2g 20g 2t la ccs" data-price="3.6302835758" data-next="1793635200" data-due30="0.0000000000">
      <span class="check">✓</span><div class="card-top"><span class="cat cat-c3">VPS</span><span class="logo-inline"><img class="logo-img logo-wide" src="/logos/1787405534-Cloudnium-Los-Angeles--KVM-VPS.png" alt="Cloudnium Los Angeles - KVM VPS"></span><span class="pct pct-low">1.4%</span><span class="dot"></span></div>
      <div class="name">Cloudnium Los Angeles - KVM VPS</div>
      <div class="price-line"><span class="price">0.54 USD<span class="cycle">/月</span><span class="approx">≈ ¥3.63 /月</span></span><span class="renew">67天后 11月3日 续费</span></div>
      <div class="notes">2C 2G 20G 2T LA CCS</div>
      <div class="bar"><i style="width:0%"></i></div>
    </div>
    <div class="card" data-category="VPS" data-cycle="/月" data-name="ulzix nat机" data-notes="低价nat鸡，目前hk不同ip有4台" data-price="3.2000000000" data-next="1796227200" data-due30="0.0000000000">
      <span class="check">✓</span><div class="card-top"><span class="cat cat-c3">VPS</span><span class="logo-inline"><img class="logo-img logo-wide" src="/logos/1787410862-ulzix-nat.webp" alt="ulzix NAT机"></span><span class="pct pct-low">1.3%</span><span class="dot"></span></div>
      <div class="name">ulzix NAT机</div>
      <div class="price-line"><span class="price">3.20 CNY<span class="cycle">/月</span><span class="approx">≈ ¥3.20 /月</span></span><span class="renew">97天后 12月3日 续费</span></div>
      <div class="notes">低价NAT鸡，目前hk不同ip有4台</div>
      <div class="bar"><i style="width:0%"></i></div>
    </div>
    <div class="card" data-category="Net" data-cycle="/月" data-name="轻连derp 纯ip套餐" data-notes="成都tailscale derper中继 淘宝优惠价约30元/半年" data-price="5.0000000000" data-next="1797609600" data-due30="0.0000000000">
      <span class="check">✓</span><div class="card-top"><span class="cat cat-c2">Net</span><span class="logo-inline"><span class="avatar">轻连DERP</span></span><span class="pct pct-low">2.0%</span><span class="dot"></span></div>
      <div class="name">轻连DERP 纯IP套餐</div>
      <div class="price-line"><span class="price">5.00 CNY<span class="cycle">/月</span><span class="approx">≈ ¥5.00 /月</span></span><span class="renew">113天后 12月19日 续费</span></div>
      <div class="notes">成都tailscale derper中继 淘宝优惠价约30元/半年</div>
      <div class="bar"><i style="width:0%"></i></div>
    </div>
    <div class="card" data-category="Net" data-cycle="/年" data-name="spaceship 域名" data-notes="2个.xyz数字域名" data-price="0.9616438356" data-next="1813334400" data-due30="0.0000000000">
      <span class="check">✓</span><div class="card-top"><span class="cat cat-c2">Net</span><span class="logo-inline"><img class="logo-img logo-wide" src="/logos/1787579440-SpaceShip-.png" alt="spaceship 域名"></span><span class="pct pct-low">0.4%</span><span class="dot"></span></div>
      <div class="name">spaceship 域名</div>
      <div class="price-line"><span class="price">11.70 CNY<span class="cycle">/年</span><span class="approx">≈ ¥0.96 /月</span></span><span class="renew">295天后 6月19日 续费</span></div>
      <div class="notes">2个.xyz数字域名</div>
      <div class="bar"><i style="width:0%"></i></div>
    </div>
    <div class="card" data-category="VPS" data-cycle="/年" data-name="cloudcone ssd-vps-4-old" data-notes="4c 4g 55g 4t st. louis, mo - dc1" data-price="13.0347624890" data-next="1813420800" data-due30="0.0000000000">
      <span class="check">✓</span><div class="card-top"><span class="cat cat-c3">VPS</span><span class="logo-inline"><img class="logo-img logo-wide" src="/logos/1787405677-CloudCone-SSDVPS4OLD.png" alt="CloudCone SSD-VPS-4-OLD"></span><span class="pct pct-mid">5.1%</span><span class="dot"></span></div>
      <div class="name">CloudCone SSD-VPS-4-OLD</div>
      <div class="price-line"><span class="price">23.59 USD<span class="cycle">/年</span><span class="approx">≈ ¥13.03 /月</span></span><span class="renew">296天后 6月20日 续费</span></div>
      <div class="notes">4C 4G 55G 4T St. Louis, MO - DC1</div>
      <div class="bar"><i style="width:0%"></i></div>
    </div>
    <div class="card" data-category="VPS" data-cycle="/月" data-name="来财云 美国lxc-a" data-notes="1c 256m 0.8g 30g ipv4-nat ipv6 达拉斯" data-price="0.5000000000" data-next="1814284800" data-due30="0.0000000000">
      <span class="check">✓</span><div class="card-top"><span class="cat cat-c3">VPS</span><span class="logo-inline"><img class="logo-img logo-wide" src="/logos/1787411134--LXCA.jpg" alt="来财云 美国LXC-A"></span><span class="pct pct-low">0.2%</span><span class="dot"></span></div>
      <div class="name">来财云 美国LXC-A</div>
      <div class="price-line"><span class="price">0.50 CNY<span class="cycle">/月</span><span class="approx">≈ ¥0.50 /月</span></span><span class="renew">306天后 6月30日 续费</span></div>
      <div class="notes">1C 256M 0.8G 30G ipv4-nat ipv6 达拉斯</div>
      <div class="bar"><i style="width:0%"></i></div>
    </div>
    <div class="card" data-category="VPS" data-cycle="/年" data-name="wawo hk-ipv6only-0.5g-18" data-notes="1c 512m 5g 500g ipv6 移动快乐机" data-price="1.4383561644" data-next="1815926400" data-due30="0.0000000000">
      <span class="check">✓</span><div class="card-top"><span class="cat cat-c3">VPS</span><span class="logo-inline"><img class="logo-img logo-wide" src="/logos/1787409983-WAWO-HKipv6only05G18.png" alt="WAWO HK-ipv6only-0.5G-18"></span><span class="pct pct-low">0.6%</span><span class="dot"></span></div>
      <div class="name">WAWO HK-ipv6only-0.5G-18</div>
      <div class="price-line"><span class="price">17.50 CNY<span class="cycle">/年</span><span class="approx">≈ ¥1.44 /月</span></span><span class="renew">325天后 7月19日 续费</span></div>
      <div class="notes">1C 512M 5G 500G ipv6 移动快乐机</div>
      <div class="bar"><i style="width:0%"></i></div>
    </div>
    <div class="card" data-category="VPS" data-cycle="/年" data-name="喵云-次元喵 nat 618 限定" data-notes="6c 8g 50g 湖北武汉家宽 广州腾讯云入口nat" data-price="5.0794520548" data-next="1816185600" data-due30="0.0000000000">
      <span class="check">✓</span><div class="card-top"><span class="cat cat-c3">VPS</span><span class="logo-inline"><span class="avatar">喵云-次元喵</span></span><span class="pct pct-low">2.0%</span><span class="dot"></span></div>
      <div class="name">喵云-次元喵 NAT 618 限定</div>
      <div class="price-line"><span class="price">61.80 CNY<span class="cycle">/年</span><span class="approx">≈ ¥5.08 /月</span></span><span class="renew">328天后 7月22日 续费</span></div>
      <div class="notes">6C 8G 50G 湖北武汉家宽 广州腾讯云入口NAT</div>
      <div class="bar"><i style="width:0%"></i></div>
    </div>
    <div class="card" data-category="VPS" data-cycle="/年" data-name="idc.homes 五周年活动款 nat 500" data-notes="1c 512m 10g 512g 美国水牛城 nat" data-price="0.8122552293" data-next="1818345600" data-due30="0.0000000000">
      <span class="check">✓</span><div class="card-top"><span class="cat cat-c3">VPS</span><span class="logo-inline"><img class="logo-img logo-wide" src="/logos/1787409756-IDCHomes--NAT-500.png" alt="IDC.Homes 五周年活动款 NAT 500"></span><span class="pct pct-low">0.3%</span><span class="dot"></span></div>
      <div class="name">IDC.Homes 五周年活动款 NAT 500</div>
      <div class="price-line"><span class="price">1.47 USD<span class="cycle">/年</span><span class="approx">≈ ¥0.81 /月</span></span><span class="renew">353天后 8月16日 续费</span></div>
      <div class="notes">1C 512M 10G 512G 美国水牛城 NAT</div>
      <div class="bar"><i style="width:0%"></i></div>
    </div>
    <div class="card" data-category="Net" data-cycle="/年" data-name="阿里云 域名" data-notes="1个.cn域名" data-price="3.1232876712" data-next="1880294400" data-due30="0.0000000000">
      <span class="check">✓</span><div class="card-top"><span class="cat cat-c2">Net</span><span class="logo-inline"><img class="logo-img logo-wide" src="/logos/1787579481--.png" alt="阿里云 域名"></span><span class="pct pct-low">1.2%</span><span class="dot"></span></div>
      <div class="name">阿里云 域名</div>
      <div class="price-line"><span class="price">38.00 CNY<span class="cycle">/年</span><span class="approx">≈ ¥3.12 /月</span></span><span class="renew">1070天后 8月2日 续费</span></div>
      <div class="notes">1个.cn域名</div>
      <div class="bar"><i style="width:0%"></i></div>
    </div>
    <div class="card is-inactive" data-category="VPS" data-cycle="/月" data-name="wap.ac tw e5 hinet lxc ipv6" data-notes="1c 512m 5g 500g ipv4-nat ipv6 hinet家宽" data-price="0.0000000000" data-next="9999999999" data-due30="0.0000000000">
      <span class="check">✓</span><div class="card-top"><span class="cat cat-c3">VPS</span><span class="logo-inline"><span class="avatar">wap.ac</span></span><span class="pct pct-low">已停用</span><span class="dot"></span></div>
      <div class="name">wap.ac TW E5 HINET LXC IPv6</div>
      <div class="price-line"><span class="price">7.24 CNY<span class="cycle">/月</span><span class="approx">≈ ¥7.24 /月</span></span><span class="renew">13天后 9月10日 续费</span></div>
      <div class="notes">1C 512M 5G 500G ipv4-NAT ipv6 HiNet家宽</div>
      <div class="bar"><i style="width:56%"></i></div>
    </div>
    <div class="card is-inactive" data-category="VPS" data-cycle="/年" data-name="dedirock promo vps la" data-notes="1c 2g 30g 4t la ccs" data-price="0.0000000000" data-next="9999999999" data-due30="0.0000000000">
      <span class="check">✓</span><div class="card-top"><span class="cat cat-c3">VPS</span><span class="logo-inline"><img class="logo-img logo-wide" src="/logos/1787409434-DediRock-PROMO-VPS-LA.png" alt="DediRock PROMO VPS LA"></span><span class="pct pct-low">已停用</span><span class="dot"></span></div>
      <div class="name">DediRock PROMO VPS LA</div>
      <div class="price-line"><span class="price">9.88 USD<span class="cycle">/年</span><span class="approx">≈ ¥5.46 /月</span></span><span class="renew">305天后 6月29日 续费</span></div>
      <div class="notes">1C 2G 30G 4T LA CCS</div>
      <div class="bar"><i style="width:0%"></i></div>
    </div>
  </div>
  <footer class="foot">
    <div class="foot-main">© 2026 <a href="https://dytchem.cn" target="_blank" rel="noopener">Dytchem</a></div>
    <div class="foot-powered"><span>Powered by</span>
      <a href="https://github.com/ellite/Wallos" target="_blank" rel="noopener" title="Wallos"><img class="wallos-logo" src="/public/assets/wallos.png" alt="Wallos"></a>
      <a href="https://fixer.io" target="_blank" rel="noopener" title="Fixer"><img src="/public/assets/fixer.png" alt="Fixer"></a>
      <a href="https://cloud.andnode.com/aff/ECQEKLVT" target="_blank" rel="noopener" title="AndNode"><img src="/public/assets/andnode.png" alt="AndNode"></a>
    </div>
    <div class="foot-links">
      <a href="https://dytchem.cn" target="_blank" rel="noopener" title="dytchem.cn">🌐</a>
      <a href="https://t.me/Dytchem" target="_blank" rel="noopener" title="Telegram @Dytchem">✈️</a>
      <a href="mailto:admin@dytchem.cn" title="Email">✉️</a>
    </div>
  </footer>
</div>
<script>
(function(){
  const key="wallos-public-theme";
  const mql=window.matchMedia("(prefers-color-scheme: light)");
  function safeGet(k){
    try{ return localStorage.getItem(k); }catch(e){ return null; }
  }
  function safeSet(k,v){
    try{ localStorage.setItem(k,v); }catch(e){}
  }
  function apply(mode){
    let real=mode;
    if(mode==="auto") real=mql.matches?"light":"dark";
    document.documentElement.setAttribute("data-theme", real);
    document.querySelectorAll(".theme-switch button").forEach(b=>b.classList.toggle("active", b.dataset.mode===mode));
    safeSet(key, mode);
  }
  let init=safeGet(key)||"auto";
  apply(init);
  mql.addEventListener("change", ()=>{ if((safeGet(key)||"auto")==="auto") apply("auto"); });
  document.getElementById("themeSwitch").addEventListener("click", e=>{
    const b=e.target.closest("button"); if(!b) return; apply(b.dataset.mode);
  });
  // 强制刷新：先让服务端重新生成 public/index.html，再硬刷新
  document.getElementById("hardRefresh").addEventListener("click", async function(){
    const btn=document.getElementById("hardRefresh");
    btn.disabled=true; btn.innerHTML="⟳ 刷新中…";
    try{ await fetch("/api/refresh?t="+Date.now(), {cache:"no-store"}); }catch(e){}
    try{ await fetch(location.href.split("?")[0].split("#")[0]+"?t="+Date.now(), {cache:"no-store"}); }catch(e){}
    location.href=location.href.split("?")[0].split("#")[0]+"?t="+Date.now();
    location.reload(true);
  });
  // 筛选：分类 / 周期 / 搜索（Binance 面板同款胶囊 + 搜索）
  const catSel=document.getElementById("catSelect");
  const cycleSel=document.getElementById("cycleSelect");
  const search=document.getElementById("searchInput");
  const grid=document.getElementById("cardGrid");
  function applyFilter(){
    const selCat=catSel.value;
    const selCycle=cycleSel.value;
    const q=(search.value||"").toLowerCase().trim();
    grid.querySelectorAll(".card").forEach(card=>{
      const cat=card.dataset.category||"";
      const cyc=card.dataset.cycle||"";
      const name=card.dataset.name||"";
      const notes=card.dataset.notes||"";
      const hitCat= selCat==="all" || cat===selCat;
      const hitCycle= selCycle==="all" || cyc.includes(selCycle);
      const hitSearch= !q || name.includes(q) || cat.toLowerCase().includes(q) || notes.includes(q);
      card.style.display= (hitCat && hitCycle && hitSearch) ? "" : "none";
    });
  }
  catSel.addEventListener("change", applyFilter);
  cycleSel.addEventListener("change", applyFilter);
  let searchTimer=null; search.addEventListener("input", ()=>{ clearTimeout(searchTimer); searchTimer=setTimeout(applyFilter,150); });
  // 排序：按价格（月均换算）或按续费时间
  const sortSel=document.getElementById("sortSelect");
  function applySort(){
    const mode=sortSel.value;
    const cards=[...grid.querySelectorAll(".card")];
    cards.sort((a,b)=>{
      const aI=a.classList.contains("is-inactive")?1:0, bI=b.classList.contains("is-inactive")?1:0;
      if(aI!==bI) return aI-bI;
      if(mode==="price-asc") return parseFloat(a.dataset.price||0)-parseFloat(b.dataset.price||0);
      if(mode==="price-desc") return parseFloat(b.dataset.price||0)-parseFloat(a.dataset.price||0);
      if(mode==="next-desc") return parseInt(b.dataset.next||0)-parseInt(a.dataset.next||0);
      return parseInt(a.dataset.next||0)-parseInt(b.dataset.next||0);
    });
    cards.forEach(c=>grid.appendChild(c));
  }
  sortSel.addEventListener("change", ()=>{ applyFilter(); applySort(); });
  // 强制默认价格降序：覆盖浏览器对 select 的表单状态恢复（否则每次刷新都回到上次选的续费时间）
  sortSel.value = "price-desc";
  applySort();
  window.addEventListener("pageshow", ()=>{ sortSel.value="price-desc"; applySort(); });
  // === 长按多选：上方统计联动 ===
  const statCountEl=document.getElementById("statCount");
  const statMonthlyEl=document.getElementById("statMonthly");
  const statYearlyEl=document.getElementById("statYearly");
  const statDue30El=document.getElementById("statDue30");
  const selectAllBtn=document.getElementById("selectAllBtn");
  const clearBtn=document.getElementById("clearSelectBtn");
  const origCount=parseInt(statCountEl?.dataset.total||"0");
  const origMonthly=parseFloat(statMonthlyEl?.dataset.total||"0");
  const origYearly=parseFloat(statYearlyEl?.dataset.total||"0");
  const origDue30=parseFloat(statDue30El?.dataset.total||"0");
  const statCountLabel=document.getElementById("statCountLabel");
  const statMonthlyLabel=document.getElementById("statMonthlyLabel");
  const statYearlyLabel=document.getElementById("statYearlyLabel");
  const statDue30Label=document.getElementById("statDue30Label");
  const countLabelBase=statCountLabel.textContent;
  const monthlyLabelBase=statMonthlyLabel.textContent;
  const yearlyLabelBase=statYearlyLabel.textContent;
  const due30LabelBase=statDue30Label.textContent;
  const mainSym="¥";
  let selected=new Set();
  function visibleCards(){ return [...grid.querySelectorAll(".card")].filter(c=>c.style.display!=="none"); }
  function updateSelectionUI(){
    const badge=document.getElementById("statInactiveBadge");
    const n=selected.size;
    if(n===0){
      statCountEl.textContent=origCount;
      statMonthlyEl.textContent=mainSym+origMonthly.toFixed(2);
      statYearlyEl.textContent=mainSym+origYearly.toFixed(2);
      statDue30El.textContent=mainSym+origDue30.toFixed(2);
      statCountLabel.textContent=countLabelBase;
      statMonthlyLabel.textContent=monthlyLabelBase;
      statYearlyLabel.textContent=yearlyLabelBase;
      statDue30Label.textContent=due30LabelBase;
      if(badge) badge.style.display="";
      return;
    }
    let sum=0, due30=0;
    selected.forEach(card=>{ sum+=parseFloat(card.dataset.price||0); due30+=parseFloat(card.dataset.due30||0); });
    if(n===origCount){ sum=origMonthly; due30=origDue30; }
    const activeN=[...selected].filter(c=>!c.classList.contains("is-inactive")).length;
    const inactiveN=n-activeN;
    statCountEl.textContent=activeN;
    statMonthlyEl.textContent=mainSym+sum.toFixed(2);
    statYearlyEl.textContent=mainSym+(sum*12).toFixed(2);
    statDue30El.textContent=mainSym+due30.toFixed(2);
    statCountLabel.textContent=countLabelBase+"（选中）";
    statMonthlyLabel.textContent=monthlyLabelBase+"（选中）";
    statYearlyLabel.textContent=yearlyLabelBase+"（选中）";
    statDue30Label.textContent=due30LabelBase+"（选中）";
    if(badge){
      if(inactiveN>0){ badge.textContent="+ "+inactiveN+" 已停用"; badge.style.display=""; }
      else badge.style.display="none";
    }
  }
  function toggleCard(card){ if(selected.has(card)){ selected.delete(card); card.classList.remove("selected"); }else{ selected.add(card); card.classList.add("selected"); } updateSelectionUI(); }
  function clearSelection(){ selected.forEach(c=>c.classList.remove("selected")); selected.clear(); updateSelectionUI(); }
  function selectAll(){
    visibleCards().forEach(c=>{ selected.add(c); c.classList.add("selected"); });
    updateSelectionUI();
  }
  selectAllBtn.addEventListener("click", selectAll);
  clearBtn.addEventListener("click", clearSelection);
  // 长按 450ms 选中 —— 最终硬化版：防捏缩放/幽灵鼠标/抖动/滚动/竞态 全覆盖
  let lpTimer=null, lpX=0, lpY=0, lastTouchAt=0, suppressClickUntil=0, suppressNextClick=false; let opQueue=[];
  function cancelLP(){ clearTimeout(lpTimer); lpTimer=null; }
  function startLP(card, e){
    cancelLP();
    const t=(e.touches && e.touches[0]) ? e.touches[0] : e;
    lpX=t.clientX; lpY=t.clientY;
    if(e.touches && e.touches.length>1) return;
    lpTimer=setTimeout(()=>{
      const sel=window.getSelection(); if(sel && sel.toString().length>0) return;
      suppressNextClick=true;
      opQueue.push(()=>toggleCard(card));
      if(opQueue.length===1){ const fn=opQueue.shift(); try{ fn(); }catch(e){} }
      if(navigator.vibrate) navigator.vibrate(30);
      setTimeout(()=>{ suppressNextClick=false; }, 400);
    }, 450);
  }
  function onTouchMove(e){
    if(!lpTimer) return;
    lastTouchAt=Date.now();
    if(e.touches && e.touches.length>1){ cancelLP(); return; }
    const t=(e.touches && e.touches[0]) ? e.touches[0] : e;
    if(Math.hypot(t.clientX-lpX, t.clientY-lpY)>16) cancelLP();
  }
  // 全局兜底：多指/滚动 均取消
  window.addEventListener("touchstart", e=>{ lastTouchAt=Date.now(); if(e.touches && e.touches.length>1) cancelLP(); }, {passive:true});
  window.addEventListener("touchmove", e=>{ if(e.touches && e.touches.length>1) cancelLP(); }, {passive:true});
  window.addEventListener("touchend", e=>{ lastTouchAt=Date.now(); cancelLP(); }, {passive:true});
  window.addEventListener("touchcancel", cancelLP, {passive:true});
  window.addEventListener("scroll", cancelLP, {passive:true});
  grid.querySelectorAll(".card").forEach(card=>{
    card.addEventListener("touchstart", e=>{ lastTouchAt=Date.now(); startLP(card,e); }, {passive:true});
    card.addEventListener("touchmove", onTouchMove, {passive:true});
    card.addEventListener("touchend", e=>{ lastTouchAt=Date.now(); cancelLP(); }, {passive:false});
    card.addEventListener("touchcancel", cancelLP, {passive:true});
    card.addEventListener("mousedown", e=>{ if(e.button!==0) return; startLP(card,e); });
    card.addEventListener("mouseup", cancelLP);
    card.addEventListener("mouseleave", cancelLP);
    card.addEventListener("click", e=>{
      if(suppressNextClick){ suppressNextClick=false; e.preventDefault(); e.stopPropagation(); return; }
      if(selected.size>0){ opQueue.push(()=>toggleCard(card)); const fn=opQueue.shift(); try{ fn(); }catch(e){} }
    });
    card.addEventListener("contextmenu", e=>{ const sel=window.getSelection(); if(sel && sel.toString().length>0) return; if(selected.size>0 || Date.now()<suppressClickUntil) e.preventDefault(); });
  });
})();
</script>
<script type="application/json" id="chartDataJson">[{"name": "OpenCode", "value": 67.23}, {"name": "OVH", "value": 67.23}, {"name": "Geelinx", "value": 30.46}, {"name": "VMISS", "value": 21.83}, {"name": "RakSmart", "value": 13.38}, {"name": "CloudCone", "value": 13.03}, {"name": "AndNode", "value": 12.8}, {"name": "TierHive", "value": 5.58}, {"name": "喵云-次元喵", "value": 5.08}, {"name": "轻连DERP", "value": 5.0}, {"name": "Cloudnium", "value": 3.63}, {"name": "ulzix", "value": 3.2}, {"name": "阿里云", "value": 3.12}, {"name": "WAWO", "value": 1.44}, {"name": "spaceship", "value": 0.96}, {"name": "IDC.Homes", "value": 0.81}, {"name": "来财云", "value": 0.5}]</script>
<script>
try{
(function(){
  const raw = document.getElementById('chartDataJson')?.textContent || '[]';
  let pieData;
  try{ pieData = JSON.parse(raw); }catch(e){ pieData=[]; }
  if(!pieData.length) return;
  const toggle=document.getElementById('chartToggle');
  const panel=document.getElementById('chartPanel');
  const tooltip=document.getElementById('chartTooltip');
  const canvas=document.getElementById('expensePie');
  if(!toggle||!panel||!canvas||!tooltip) return;
  const ctx=canvas.getContext('2d');
  const dprPie=window.devicePixelRatio||1;
  const rectPie=canvas.getBoundingClientRect();
  const cssPieW=rectPie.width||320, cssPieH=rectPie.height||320;
  const size=Math.min(cssPieW, cssPieH);
  canvas.width=cssPieW*dprPie; canvas.height=cssPieH*dprPie;
  canvas.style.width=cssPieW+'px'; canvas.style.height=cssPieH+'px';
  ctx.setTransform(dprPie,0,0,dprPie,0,0);
  const cx=cssPieW/2, cy=cssPieH/2, r=Math.min(120, size*0.375);
  const total=pieData.reduce((s,it)=>s+it.value,0);
  function genPieColors(n){ const cols=[]; for(let i=0;i<n;i++){ const h=(i*360/n+15)%360; const s=70; const l=document.documentElement.getAttribute('data-theme')==='light'?55:62; cols.push(`hsl(${h} ${s}% ${l}%)`); } return cols; }
  const colors=genPieColors(pieData.length);
  function esc(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');}
  let hoverIdx=-1;
  let hoverBar=-1;
  let bars=[];
  let bw=360, bh=360;
  let barCanvas=null, barTooltip=null, barCtx=null;
  let drawBar=null, showBarTip=null, hideBarTip=null;
  function draw(){
    ctx.clearRect(0,0,cssPieW,cssPieH);
    let ang=-Math.PI/2;
    pieData.forEach((it,i)=>{
      const slice=it.value/total*Math.PI*2;
      const isHover=i===hoverIdx;
      const rr=isHover?r+4:r;
      const angS=ang, angE=ang+slice;
      ctx.beginPath(); ctx.moveTo(cx,cy); ctx.arc(cx,cy,rr,angS,angE); ctx.closePath();
      ctx.fillStyle=colors[i]; ctx.fill();
      if(isHover){ ctx.save(); ctx.shadowColor='rgba(0,0,0,0.28)'; ctx.shadowBlur=14; ctx.fill(); ctx.restore(); }
      ctx.strokeStyle=document.documentElement.getAttribute('data-theme')==='light'?'#fff':'#0b0e11'; ctx.lineWidth=isHover?2:1.5; ctx.stroke();
      ang+=slice;
    });
  }
  draw();
  // theme switch redraw
  const obs=new MutationObserver(draw);
  obs.observe(document.documentElement,{attributes:true,attributeFilter:['data-theme']});
  // slices for hit test
  const slices=[];
  let ang2=-Math.PI/2;
  pieData.forEach((it,i)=>{ const slice=it.value/total*Math.PI*2; slices.push({start:ang2,end:ang2+slice,color:colors[i],data:it}); ang2+=slice; });
  function showTip(idx){ const it=pieData[idx]; const pct=(it.value/total*100).toFixed(1); tooltip.innerHTML=`<span class="tt-dot" style="background:${colors[idx]}"></span><span class="tt-name">${esc(it.name)}</span> <span class="tt-val">月付¥${it.value.toFixed(2)}（占${pct}%）</span>`; const rr=canvas.getBoundingClientRect(); const curW=rr.width||320, curH=rr.height||320; const curSize=Math.min(curW,curH); const curCy=curH/2, curR=Math.min(120, curSize*0.375); tooltip.style.top=(curCy+curR+12)+'px'; tooltip.classList.add('show'); }
  function hideTip(){ tooltip.classList.remove('show'); }
  canvas.addEventListener('mousemove', (e)=>{
      const rect=canvas.getBoundingClientRect(); const scale=size/rect.width;
      const mx=(e.clientX-rect.left)*scale, my=(e.clientY-rect.top)*scale;
      const dx=mx-cx, dy=my-cy; const dist=Math.hypot(dx,dy);
      if(dist>r || dist<r*0.35){ hideTip(); hideBarTip(); if(hoverIdx!==-1){ hoverIdx=-1; draw(); } if(hoverBar!==-1){ hoverBar=-1; drawBar(); } canvas.style.cursor='default'; return; }
      let a=Math.atan2(dy,dx); if(a<-Math.PI/2) a+=Math.PI*2;
      let idx=-1; for(let i=0;i<slices.length;i++){ const s=slices[i]; if(a>=s.start && a<s.end){ idx=i; break; } }
      if(idx>=0){ if(hoverIdx!==idx){ hoverIdx=idx; draw(); } if(hoverBar!==idx){ hoverBar=idx; drawBar(); } canvas.style.cursor='pointer'; showTip(idx); const _b=bars[idx]; if(_b){ const _rectBar=barCanvas.getBoundingClientRect(); const _px=_b.x, _py=_b.y-6; let _cssX=(_px/bw)*_rectBar.width, _cssY=(_py/bh)*_rectBar.height; const _estW=140, _estH=32; if(_cssX+_estW>_rectBar.width) _cssX=_rectBar.width-_estW-4; if(_cssX<0) _cssX=4; showBarTip(idx, _cssX, _cssY); barCanvas.style.cursor='pointer'; } } else { hideTip(); hideBarTip(); if(hoverIdx!==-1){ hoverIdx=-1; draw(); } if(hoverBar!==-1){ hoverBar=-1; drawBar(); } canvas.style.cursor='default'; barCanvas.style.cursor='default'; }
    });
  canvas.addEventListener('mouseleave', ()=>{ hideTip(); hideBarTip(); if(hoverIdx!==-1){ hoverIdx=-1; draw(); } if(hoverBar!==-1){ hoverBar=-1; drawBar(); } canvas.style.cursor='default'; barCanvas.style.cursor='default'; });
  canvas.addEventListener('click', (e)=>{
    const rect=canvas.getBoundingClientRect();
    const bw2=bw, bh2=bh;
    const mx=(e.clientX-rect.left)/rect.width*bw2, my=(e.clientY-rect.top)/rect.height*bh2;
    const dx=mx-cx, dy=my-cy, dist=Math.sqrt(dx*dx+dy*dy);
    if(dist>r || dist<r*0.35){ hideTip(); hideBarTip(); if(hoverIdx!==-1){ hoverIdx=-1; draw(); } if(hoverBar!==-1){ hoverBar=-1; drawBar(); } return; }
    let a=Math.atan2(dy,dx); if(a<0) a+=Math.PI*2; let start=-Math.PI/2, hit=-1;
    for(let i=0;i<slices.length;i++){ const s=slices[i]; if(a>=s.start && a<s.end){ hit=i; break; } }
    if(hit===-1){ hideTip(); hideBarTip(); if(hoverIdx!==-1){ hoverIdx=-1; draw(); } if(hoverBar!==-1){ hoverBar=-1; drawBar(); } }
  });
  canvas.addEventListener('touchstart', (e)=>{
      const t0=e.touches[0]; const rect=canvas.getBoundingClientRect(); const scale=size/rect.width;
      const mx=(t0.clientX-rect.left)*scale, my=(t0.clientY-rect.top)*scale;
      const dx=mx-cx, dy=my-cy; const dist=Math.hypot(dx,dy); if(dist>r || dist<r*0.35) return;
      let a=Math.atan2(dy,dx); if(a<-Math.PI/2) a+=Math.PI*2;
      for(let i=0;i<slices.length;i++){ const s=slices[i]; if(a>=s.start && a<s.end){ if(hoverIdx!==i){ hoverIdx=i; draw(); } if(hoverBar!==i){ hoverBar=i; drawBar(); } showTip(i); const _b2=bars[i]; if(_b2){ const _r2=barCanvas.getBoundingClientRect(); let _cx2=(_b2.x/bw)*_r2.width, _cy2=(_b2.y-6)/bh*_r2.height; if(_cx2+140>_r2.width) _cx2=_r2.width-144; if(_cx2<0) _cx2=4; showBarTip(i,_cx2,_cy2); } break; } }
    }, {passive:true});
  // === 柱状图（悬停显示）===
  barCanvas=document.getElementById('expenseBar');
  barTooltip=document.getElementById('barTooltip');
  barCtx=barCanvas?barCanvas.getContext('2d'):null;
  if(barCanvas && barCtx && barTooltip){
    const dprBar=window.devicePixelRatio||1;
    const rectBar=barCanvas.getBoundingClientRect();
    const cssBarW=rectBar.width||360, cssBarH=rectBar.height||360;
    barCanvas.width=cssBarW*dprBar; barCanvas.height=cssBarH*dprBar;
    barCanvas.style.width=cssBarW+'px'; barCanvas.style.height=cssBarH+'px';
    barCtx.setTransform(dprBar,0,0,dprBar,0,0);
    bw=cssBarW; bh=cssBarH; const padL=32, padR=12, padT=14, padB=90;
    const plotW=bw-padL-padR, plotH=bh-padT-padB;
    const rawMax=Math.max(...pieData.map(d=>d.value), 1);
    // 科学规整刻度：nice numbers 1,2,5 *10^n
    function niceNum(range, round){ let exp=Math.floor(Math.log10(range)); let frac=range/Math.pow(10,exp); let nf; if(round){ if(frac<1.5) nf=1; else if(frac<3) nf=2; else if(frac<7) nf=5; else nf=10; } else { if(frac<=1) nf=1; else if(frac<=2) nf=2; else if(frac<=5) nf=5; else nf=10; } return nf*Math.pow(10,exp); }
    const niceRange=niceNum(rawMax, false);
    const tickSpacing=niceNum(niceRange/4, true);
    const niceMax=Math.ceil(rawMax/tickSpacing)*tickSpacing;
    const tickCount=Math.round(niceMax/tickSpacing);
    hoverBar=-1;
    const n=pieData.length;
    const gap=6;
    const barW=(plotW - gap*(n-1))/n;
    bars.length=0;
    drawBar = function(){
      barCtx.clearRect(0,0,bw,bh);
      // grid - 按规整刻度
      barCtx.strokeStyle=document.documentElement.getAttribute('data-theme')==='light'?'#e5e7eb':'#2b3139';
      barCtx.lineWidth=1;
      barCtx.beginPath();
      for(let g=0; g<=tickCount; g++){ const y=padT+plotH - g*plotH/tickCount; barCtx.moveTo(padL, y); barCtx.lineTo(bw-padR, y); }
      barCtx.stroke();
      // bars - 以 niceMax 为分母
      bars.length=0;
      pieData.forEach((it,i)=>{
        const isHover=i===hoverBar;
        const h=it.value/niceMax*plotH;
        const x=padL + i*(barW+gap);
        const y=padT+plotH - h;
        barCtx.fillStyle=colors[i];
        barCtx.beginPath();
        barCtx.roundRect(x, y, barW, h, [4,4,0,0]);
        barCtx.fill();
        if(isHover){ barCtx.save(); barCtx.shadowColor='rgba(0,0,0,0.28)'; barCtx.shadowBlur=12; barCtx.fill(); barCtx.restore(); barCtx.strokeStyle='rgba(255,255,255,0.9)'; barCtx.lineWidth=1.2; barCtx.stroke(); }
        bars.push({x, y, w:barW, h, idx:i});
        // x轴竖排：文本框整体旋转90度
        barCtx.save();
        barCtx.translate(x+barW/2, padT+plotH+8);
        barCtx.rotate(Math.PI/2);
        barCtx.fillStyle=document.documentElement.getAttribute('data-theme')==='light'?'#6b7280':'#848e9c';
        barCtx.font='10px system-ui';
        barCtx.textAlign='left';
        barCtx.textBaseline='middle';
        barCtx.fillText(it.name, 0, 0);
        barCtx.restore();
        // x label (首字或缩写，旋转不必要，直接截4字)
        barCtx.fillStyle='var(--muted)';
        // 实际填色用 computed
        const isLight=document.documentElement.getAttribute('data-theme')==='light';
        // x轴标签已按需求移除（悬停显示）
      });
      // y axis labels
      barCtx.fillStyle=document.documentElement.getAttribute('data-theme')==='light'?'#6b7280':'#848e9c';
      barCtx.font='9px system-ui';
      barCtx.textAlign='right';
      barCtx.textBaseline='middle';
      for(let g=0; g<=tickCount; g++){ const v=g*tickSpacing; const y=padT+plotH - g*plotH/tickCount; barCtx.fillText(v.toFixed(0), padL-6, y); }
      barCtx.save();
      barCtx.translate(6, padT+plotH/2);
      barCtx.rotate(-Math.PI/2);
      barCtx.fillStyle=document.documentElement.getAttribute('data-theme')==='light'?'#6b7280':'#848e9c';
      barCtx.font='10px system-ui';
      barCtx.textAlign='center';
      barCtx.textBaseline='middle';
      barCtx.fillText('月付/￥', 0, 0);
      barCtx.restore();
    }
    drawBar();
    obs.observe(document.documentElement,{attributes:true,attributeFilter:['data-theme']});
    // 联动主题重绘：复用同一个 observer 已在上方注册，这里再 observe 会重复，改用 MutationObserver 再注册一次无害，drawBar 也在 pie 的 observer 回调里？单独再 observe 一次
    const barObs=new MutationObserver(drawBar);
    barObs.observe(document.documentElement,{attributes:true,attributeFilter:['data-theme']});
    showBarTip = function(idx, cssX, cssY){ const it=pieData[idx]; const pct=(it.value/total*100).toFixed(1); barTooltip.innerHTML=`<span class="tt-dot" style="background:${colors[idx]}"></span><span class="tt-name">${esc(it.name)}</span> <span class="tt-val">月付¥${it.value.toFixed(2)}（占${pct}%）</span>`; barTooltip.style.left=cssX+'px'; barTooltip.style.top=cssY+'px'; barTooltip.classList.add('show'); }
    hideBarTip = function(){ barTooltip.classList.remove('show'); }
    barCanvas.addEventListener('mousemove', (e)=>{
      const rect=barCanvas.getBoundingClientRect(); const scale=bw/rect.width;
      const mx=(e.clientX-rect.left)*scale, my=(e.clientY-rect.top)*scale;
      let idx=-1;
      for(const b of bars){ if(mx>=b.x && mx<=b.x+b.w){ idx=b.idx; break; } }
      if(idx>=0){ if(hoverBar!==idx){ hoverBar=idx; drawBar(); } if(hoverIdx!==idx){ hoverIdx=idx; draw(); } barCanvas.style.cursor='pointer'; canvas.style.cursor='pointer'; const b=bars.find(bb=>bb.idx===idx);
        const px=b.x, py=b.y-6;
        let cssX=(px/bw)*rect.width, cssY=(py/bh)*rect.height;
        const estW=140, estH=32;
        if(cssX+estW>rect.width) cssX=rect.width-estW-4;
        if(cssX<0) cssX=4;
        if(cssY-estH<0) cssY=(b.y+b.h/bh)*rect.height+12;
        showBarTip(idx, cssX, cssY); showTip(idx);
      } else { hideBarTip(); hideTip(); if(hoverBar!==-1){ hoverBar=-1; drawBar(); } if(hoverIdx!==-1){ hoverIdx=-1; draw(); } barCanvas.style.cursor='default'; canvas.style.cursor='default'; }
    });
    barCanvas.addEventListener('mouseleave', ()=>{ hideBarTip(); hideTip(); if(hoverBar!==-1){ hoverBar=-1; drawBar(); } if(hoverIdx!==-1){ hoverIdx=-1; draw(); } barCanvas.style.cursor='default'; canvas.style.cursor='default'; });
    barCanvas.addEventListener('click', (e)=>{
      const rect=barCanvas.getBoundingClientRect();
      const mx=(e.clientX-rect.left)/rect.width*bw, my=(e.clientY-rect.top)/rect.height*bh;
      let hit=-1;
      for(const b of bars){ if(mx>=b.x && mx<=b.x+b.w && my>=b.y && my<=b.y+b.h){ hit=b.idx; break; } }
      if(hit===-1){ hideBarTip(); hideTip(); if(hoverBar!==-1){ hoverBar=-1; drawBar(); } if(hoverIdx!==-1){ hoverIdx=-1; draw(); } }
    });
    barCanvas.addEventListener('touchstart', (e)=>{
      const t0=e.touches[0]; const rect=barCanvas.getBoundingClientRect(); const scale=bw/rect.width;
      const mx=(t0.clientX-rect.left)*scale, my=(t0.clientY-rect.top)*scale;
      for(const b of bars){ if(mx>=b.x && mx<=b.x+b.w && my>=b.y && my<=b.y+b.h){ const bb=bars.find(bb=>bb.idx===b.idx); const px3=bb.x+bb.w/2, py3=bb.y-6; let cssX=(px3/bw)*rect.width, cssY=(py3/bh)*rect.height; const estW=140, estH=32; if(cssX+estW/2>rect.width) cssX=rect.width-estW/2-4; if(cssX-estW/2<0) cssX=estW/2+4; if(cssY-estH<0) cssY=(bb.y+bb.h/bh)*rect.height+12; if(hoverBar!==b.idx){ hoverBar=b.idx; drawBar(); } if(hoverIdx!==b.idx){ hoverIdx=b.idx; draw(); } showBarTip(b.idx, cssX, cssY); showTip(b.idx); break; } }
    }, {passive:true});
  }
  panel.addEventListener('click', (e)=>{ if(e.target===panel){ hideTip(); hideBarTip(); if(hoverIdx!==-1){ hoverIdx=-1; draw(); } if(hoverBar!==-1){ hoverBar=-1; drawBar(); } } });
  document.addEventListener('click', (e)=>{ if(!panel.contains(e.target) && !toggle.contains(e.target)){ hideTip(); hideBarTip(); if(hoverIdx!==-1){ hoverIdx=-1; draw(); } if(hoverBar!==-1){ hoverBar=-1; drawBar(); } } });
  toggle.addEventListener('click', ()=>{
    const open=panel.style.display!=='none';
    if(open){ panel.style.display='none'; toggle.classList.remove('open'); toggle.innerHTML='展开图表 <span class="arrow">▾</span>'; hideTip(); }
    else { panel.style.display='flex'; toggle.classList.add('open'); toggle.innerHTML='收起图表 <span class="arrow">▾</span>'; draw(); }
  });
})();
}catch(e){ console.error('chart error', e); }
</script>
</body>
</html>
