@krtclcdy/tersio 2.8.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -34,6 +34,10 @@
34
34
  }
35
35
  .mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-variant-numeric: tabular-nums; }
36
36
  .rowline { border-bottom: 1px solid var(--line); }
37
+ .pill { display: inline-flex; align-items: center; gap: 4px; border-radius: 999px; padding: 3px 10px; font-size: 11px; white-space: nowrap; }
38
+ .pill.good { background: var(--accent-soft); color: var(--accent); }
39
+ .pill.bad { background: rgba(248, 113, 113, .12); color: #f87171; }
40
+ .pill.flat { background: var(--track); color: var(--dim); }
37
41
  .bar-fill { background: var(--accent); border-radius: 999px; transform-origin: left; }
38
42
  .bar-track { background: var(--track); border-radius: 999px; }
39
43
  .live-dot { background: var(--accent); box-shadow: 0 0 0 0 var(--accent-soft); animation: ping 2s cubic-bezier(0, 0, .2, 1) infinite; }
@@ -42,6 +46,9 @@
42
46
  .rise.in { opacity: 1; transform: none; transition: opacity .7s cubic-bezier(.16, 1, .3, 1), transform .7s cubic-bezier(.16, 1, .3, 1); }
43
47
  .tilt { transition: transform .25s cubic-bezier(.16, 1, .3, 1), background .25s; }
44
48
  .tilt:hover { transform: translateX(6px); background: var(--accent-soft); }
49
+ .stripcell { transition: background .25s; }
50
+ .stripcell:hover { background: var(--accent-soft); }
51
+ button, .seg button, .tilt, .modelcard, .mbar, .cell, .stripcell { cursor: pointer; }
45
52
  .btn-push { transition: transform .12s, background .2s; }
46
53
  .btn-push:hover { background: var(--accent-soft); }
47
54
  .btn-push:active { transform: scale(.96); }
@@ -53,6 +60,11 @@
53
60
  .mbar { display: flex; flex-direction: column-reverse; border-radius: 6px 6px 2px 2px; overflow: hidden; min-height: 3px; background: var(--track); }
54
61
  .mbar > span { display: block; width: 100%; }
55
62
  .monogram { width: 34px; height: 34px; border-radius: 10px; display: grid; place-items: center; font-weight: 700; font-size: 13px; color: #fff; flex-shrink: 0; }
63
+ .monogram svg { width: 18px; height: 18px; fill: #fff; }
64
+ .monogram.sm svg { width: 14px; height: 14px; }
65
+ .modelcard { transition: transform .25s cubic-bezier(.16, 1, .3, 1), border-color .25s; }
66
+ .modelcard:hover { transform: translateY(-4px); border-color: var(--accent); }
67
+ .heatmonth { font-size: 10px; }
56
68
  .dock {
57
69
  position: sticky; top: 12px; z-index: 50;
58
70
  background: color-mix(in srgb, var(--panel) 84%, transparent);
@@ -61,6 +73,17 @@
61
73
  box-shadow: 0 12px 40px rgba(0, 0, 0, .25);
62
74
  }
63
75
  .dock img.brand { border-radius: 10px; }
76
+ .dock {
77
+ background:
78
+ linear-gradient(var(--panel), var(--panel)) padding-box,
79
+ linear-gradient(120deg, var(--accent-soft), var(--line) 30%, var(--line) 70%, var(--accent-soft)) border-box;
80
+ border: 1px solid transparent;
81
+ }
82
+ .dock .brand { box-shadow: 0 0 12px var(--accent-soft); }
83
+ #reload:hover { box-shadow: 0 0 14px var(--accent-soft); border-color: var(--accent); }
84
+ .spark { display: flex; align-items: flex-end; gap: 3px; }
85
+ .spark > span { flex: 1; min-width: 3px; border-radius: 2px 2px 0 0; background: var(--accent); opacity: .45; }
86
+ .spark > span.top { opacity: 1; box-shadow: 0 0 8px var(--accent-soft); }
64
87
  @media (prefers-reduced-motion: reduce) {
65
88
  .rise { opacity: 1; transform: none; }
66
89
  .live-dot { animation: none; }
@@ -80,13 +103,14 @@
80
103
  <img src="brand.webp" alt="Tersio" width="32" height="32" class="brand size-8 rounded-full">
81
104
  <span class="leading-tight text-left">
82
105
  <span class="block font-bold tracking-tight text-sm">TERSIO GAIN</span>
83
- <span class="mono block text-[11px]" style="color: var(--dim)" id="meta">connecting to ledger</span>
106
+ <span class="mono block text-[11px]" style="color: var(--dim)">live ledger</span>
84
107
  </span>
85
108
  </a>
86
109
  <span class="mono hidden shrink-0 sm:flex items-center gap-2 text-xs px-2 ml-auto" style="color: var(--dim)">
87
110
  <span class="live-dot inline-block size-2 rounded-full"></span>
88
111
  <span id="clock">--:--</span>
89
112
  </span>
113
+ <span class="hidden sm:block w-px h-5 shrink-0" style="background: var(--line)" aria-hidden="true"></span>
90
114
  <button id="reload" class="btn-push mono flex shrink-0 items-center gap-2 text-xs pl-2.5 pr-3 py-2 rounded-xl" style="border: 1px solid var(--line); color: var(--ink)" aria-label="Reload data">
91
115
  <i data-lucide="refresh-cw" class="size-3.5"></i><span class="hidden md:inline">Reload</span>
92
116
  </button>
@@ -99,53 +123,74 @@
99
123
  <p class="hero-in mono mt-4 text-sm max-w-[62ch] mx-auto" style="color: var(--dim)" id="herosub">session tokens parsed from OMP transcripts. Input, output, cache read, cache write, USD cost, CO2.</p>
100
124
  </section>
101
125
 
102
- <!-- Savings bento: 3+3+2+2+2 -->
126
+ <!-- Savings bento: big USD card + nested right rail -->
103
127
  <section class="grid grid-cols-12 grid-flow-dense gap-3" aria-label="Savings">
104
- <div class="rise col-span-6 md:col-span-3 rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel)">
105
- <p class="mono text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">usd cost</p>
106
- <p class="mono font-bold text-3xl mt-1" id="usd">-</p>
107
- <p class="mono text-[11px] mt-1" style="color: var(--dim)" id="usdNote">per-model price table</p>
108
- </div>
109
- <div class="rise col-span-6 md:col-span-3 rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel)">
110
- <p class="mono text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">saved by cache</p>
111
- <p class="mono font-bold text-3xl mt-1" style="color: var(--accent)" id="saved">-</p>
112
- <p class="mono text-[11px] mt-1" style="color: var(--dim)">est. vs full input price</p>
113
- </div>
114
- <div class="rise col-span-4 md:col-span-2 rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel)">
115
- <p class="mono text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">co2</p>
116
- <p class="mono font-bold text-3xl mt-1" id="co2">-</p>
117
- <p class="mono text-[11px] mt-1" style="color: var(--dim)">est. from output</p>
118
- </div>
119
- <div class="rise col-span-4 md:col-span-2 rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel)">
120
- <p class="mono text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">cache share</p>
121
- <p class="mono font-bold text-3xl mt-1" id="cacheShare">-</p>
122
- <p class="mono text-[11px] mt-1" style="color: var(--dim)">of tokens cached</p>
128
+ <div class="rise col-span-12 lg:col-span-7 lg:row-span-2 rounded-xl p-6 flex flex-col justify-between" style="border: 1px solid var(--line); background: var(--panel)">
129
+ <div>
130
+ <div class="flex items-center justify-between gap-2">
131
+ <p class="mono text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">usd cost</p>
132
+ <span class="pill good mono" id="usdDelta">—</span>
133
+ </div>
134
+ <p class="mono font-bold tracking-tighter leading-none mt-2 text-6xl lg:text-7xl" id="usd">-</p>
135
+ </div>
136
+ <div class="mt-4">
137
+ <div class="spark h-16" id="usdSpark"></div>
138
+ <div class="flex gap-4 mt-3 mono text-xs" style="color: var(--dim)">
139
+ <span>~ <span id="usdPerDay" style="color: var(--ink)">-</span>/day</span>
140
+ <span>top <span id="usdTopDay" style="color: var(--ink)">-</span></span>
141
+ <span>x<span id="usdLeverage" style="color: var(--accent)">-</span> cache leverage</span>
142
+ </div>
143
+ </div>
144
+ <p class="mono text-[11px] mt-3" style="color: var(--dim)" id="usdNote">per-model price table</p>
123
145
  </div>
124
- <div class="rise col-span-4 md:col-span-2 rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel)">
125
- <p class="mono text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">assistant msgs</p>
126
- <p class="mono font-bold text-3xl mt-1" id="msgs">0</p>
127
- <p class="mono text-[11px] mt-1" style="color: var(--dim)" id="msgNote">sessions parsed</p>
146
+ <div class="col-span-12 lg:col-span-5 grid grid-cols-2 grid-flow-dense gap-3">
147
+ <div class="rise col-span-2 rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel)">
148
+ <div class="flex items-center justify-between gap-2">
149
+ <div><p class="mono text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">saved by cache</p>
150
+ <p class="mono font-bold text-3xl mt-1" style="color: var(--accent)" id="saved">-</p></div>
151
+ <span class="pill good mono" id="savedDelta">—</span>
152
+ </div>
153
+ <p class="mono text-[11px] mt-2" style="color: var(--dim)">est. vs full input price</p>
154
+ </div>
155
+ <div class="rise col-span-1 rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel)">
156
+ <div class="flex items-center justify-between gap-2">
157
+ <div><p class="mono text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">co2</p>
158
+ <p class="mono font-bold text-3xl mt-1" id="co2">-</p>
159
+ <p class="mt-2"><span class="pill good mono" id="co2Delta">—</span></p></div>
160
+ </div>
161
+ <p class="mono text-[11px] mt-2" style="color: var(--dim)">est. from output</p>
162
+ </div>
163
+ <div class="rise col-span-1 rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel)">
164
+ <div class="flex items-center justify-between gap-2">
165
+ <div><p class="mono text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">cache share</p>
166
+ <p class="mono font-bold text-3xl mt-1" id="cacheShare">-</p>
167
+ <p class="mt-2"><span class="pill good mono" id="cacheDelta">—</span></p></div>
168
+ </div>
169
+ <p class="mono text-[11px] mt-2" style="color: var(--dim)">of tokens cached</p>
170
+ </div>
128
171
  </div>
129
172
  </section>
130
173
 
131
174
  <!-- Token strip: nonzero buckets only -->
132
175
  <section class="rise mt-3 flex rounded-xl overflow-hidden" style="border: 1px solid var(--line); background: var(--panel)" id="tokens" aria-label="Tokens by bucket"></section>
133
176
 
134
- <!-- Token activity: stacked per-model bars + range toggle -->
135
- <section id="activity" class="rise mt-8 rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel); scroll-margin-top: 90px" aria-label="Token activity">
136
- <div class="flex flex-wrap items-center gap-3 mb-1">
137
- <i data-lucide="chart-column" class="size-4" style="color: var(--accent)"></i>
138
- <h2 class="display font-bold tracking-tight text-xl">Token activity</h2>
139
- <div class="seg mono text-xs flex items-center gap-1 ml-auto p-1 rounded-[10px]" style="border: 1px solid var(--line)" role="tablist" aria-label="Range">
140
- <button data-range="daily" class="on px-3 py-1.5" role="tab">Daily</button>
141
- <button data-range="weekly" class="px-3 py-1.5" role="tab">Weekly</button>
142
- <button data-range="cumulative" class="px-3 py-1.5" role="tab">Cumulative</button>
143
- </div>
177
+ <!-- Activity: contribution heatmap, GitHub style -->
178
+ <section id="activity" class="rise mt-8 rounded-xl p-5 overflow-hidden" style="border: 1px solid var(--line); background: var(--panel); scroll-margin-top: 90px" aria-label="Activity graph">
179
+ <div class="flex items-center gap-2 mb-1">
180
+ <i data-lucide="calendar-days" class="size-4" style="color: var(--accent)"></i>
181
+ <h2 class="display font-bold tracking-tight text-xl">Activity</h2>
182
+ <span class="mono text-xs ml-auto" style="color: var(--dim)" id="graphRange"></span>
144
183
  </div>
145
- <p class="mono text-xs mb-4" style="color: var(--dim)" id="rangeNote">stacked by model / last 60 days</p>
146
- <div class="flex items-end gap-[3px] h-56" id="bars"></div>
147
- <div class="flex gap-[3px] mt-2 mono text-[10px]" style="color: var(--dim)" id="barDays"></div>
148
- <div class="flex flex-wrap gap-x-4 gap-y-1.5 mt-4 mono text-[11px]" style="color: var(--dim)" id="legend"></div>
184
+ <p class="mono text-xs mb-4" style="color: var(--dim)">daily session tokens / last 26 weeks / darker means more</p>
185
+ <div class="overflow-x-auto pb-1">
186
+ <div class="flex gap-[3px] w-max" id="graph"></div>
187
+ </div>
188
+ <div class="mono text-[11px] mt-3 flex items-center gap-1.5" style="color: var(--dim)">
189
+ <span>less</span>
190
+ <span class="cell"></span><span class="cell l1"></span><span class="cell l2"></span><span class="cell l3"></span><span class="cell l4"></span>
191
+ <span>more</span>
192
+ </div>
193
+ <div class="heatmonth mono mt-2 flex" style="color: var(--dim)" id="graphMonths"></div>
149
194
  </section>
150
195
 
151
196
  <!-- Top models: 3 cards -->
@@ -159,39 +204,71 @@
159
204
  <div class="grid grid-cols-1 md:grid-cols-12 grid-flow-dense gap-3" id="modelCards"></div>
160
205
  </section>
161
206
 
162
- <!-- Models list + top tools: 8 + 4 -->
163
- <main class="grid grid-cols-1 lg:grid-cols-12 grid-flow-dense gap-8 mt-8">
164
- <div class="lg:col-span-8">
165
- <section class="rise" aria-label="All models">
166
- <div class="flex items-center gap-2 mb-1">
167
- <i data-lucide="layers" class="size-4" style="color: var(--accent)"></i>
168
- <h2 class="display font-bold tracking-tight text-xl">Models</h2>
169
- </div>
170
- <p class="mono text-xs mb-4" style="color: var(--dim)">ranked by tokens / top 8</p>
171
- <ol id="models" class="rounded-xl overflow-hidden" style="border: 1px solid var(--line); background: var(--panel)"></ol>
172
- </section>
207
+ <!-- Models list: full width -->
208
+ <section class="rise mt-8" aria-label="All models">
209
+ <div class="flex items-center gap-2 mb-1">
210
+ <i data-lucide="layers" class="size-4" style="color: var(--accent)"></i>
211
+ <h2 class="display font-bold tracking-tight text-xl">Models</h2>
173
212
  </div>
174
- <div class="lg:col-span-4">
175
- <section id="tools" class="rise rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel); scroll-margin-top: 90px" aria-label="Top tools">
176
- <div class="flex items-center gap-2 mb-1">
177
- <i data-lucide="terminal" class="size-4" style="color: var(--accent)"></i>
178
- <h2 class="display font-bold tracking-tight text-xl">Top tools</h2>
179
- </div>
180
- <p class="mono text-xs mb-4" style="color: var(--dim)">tool calls in sessions</p>
181
- <ol id="top" class="mono text-[13px] space-y-3"></ol>
182
- <p id="emptyTop" class="mono text-sm py-6 text-center hidden" style="color: var(--dim)">No tool calls yet.</p>
183
- </section>
213
+ <p class="mono text-xs mb-4" style="color: var(--dim)">ranked by tokens / top 8</p>
214
+ <ol id="models" class="rounded-xl overflow-hidden" style="border: 1px solid var(--line); background: var(--panel)"></ol>
215
+ </section>
216
+
217
+ <!-- Top tools: full-width impact table -->
218
+ <section id="tools" class="rise mt-8 rounded-xl p-5 overflow-hidden" style="border: 1px solid var(--line); background: var(--panel); scroll-margin-top: 90px" aria-label="Top tools">
219
+ <div class="flex items-center gap-2 mb-1">
220
+ <i data-lucide="terminal" class="size-4" style="color: var(--accent)"></i>
221
+ <h2 class="display font-bold tracking-tight text-xl">Top tools</h2>
222
+ <span class="mono text-xs ml-auto" style="color: var(--dim)">tool calls in sessions</span>
223
+ </div>
224
+ <div class="overflow-x-auto">
225
+ <table class="w-full mono text-[13px]" id="topTable">
226
+ <thead>
227
+ <tr class="text-left text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">
228
+ <th class="font-normal text-right pr-3 py-2 w-10">#</th>
229
+ <th class="font-normal py-2 pr-3">Tool</th>
230
+ <th class="font-normal text-right py-2 pr-3">Calls</th>
231
+ <th class="font-normal text-right py-2 pr-3">Share</th>
232
+ <th class="font-normal py-2 min-w-32">Impact</th>
233
+ </tr>
234
+ </thead>
235
+ <tbody id="top"></tbody>
236
+ </table>
237
+ </div>
238
+ <p id="emptyTop" class="mono text-sm py-6 text-center hidden" style="color: var(--dim)">No tool calls yet.</p>
239
+ </section>
240
+
241
+ <!-- Token activity: stacked per-model bars -->
242
+ <section id="modelactivity" class="rise mt-8 rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel); scroll-margin-top: 90px" aria-label="Token activity">
243
+ <div class="flex flex-wrap items-center gap-3 mb-1">
244
+ <i data-lucide="chart-column" class="size-4" style="color: var(--accent)"></i>
245
+ <h2 class="display font-bold tracking-tight text-xl">Token activity</h2>
246
+ <div class="seg mono text-xs flex items-center gap-1 ml-auto p-1 rounded-[10px]" style="border: 1px solid var(--line)" role="tablist" aria-label="Range">
247
+ <button data-range="daily" class="on px-3 py-1.5" role="tab">Daily</button>
248
+ <button data-range="weekly" class="px-3 py-1.5" role="tab">Weekly</button>
249
+ <button data-range="cumulative" class="px-3 py-1.5" role="tab">Cumulative</button>
250
+ </div>
184
251
  </div>
185
- </main>
252
+ <p class="mono text-xs mb-4" style="color: var(--dim)" id="rangeNote">stacked by model / last 60 days</p>
253
+ <div class="flex items-end gap-[3px] h-56" id="bars"></div>
254
+ <div class="flex gap-[3px] mt-2 mono text-[10px]" style="color: var(--dim)" id="barDays"></div>
255
+ <div class="flex flex-wrap gap-x-4 gap-y-1.5 mt-4 mono text-[11px]" style="color: var(--dim)" id="legend"></div>
256
+ </section>
186
257
 
187
- <footer class="mono text-xs mt-12 pt-4 flex items-center gap-4" style="color: var(--dim); border-top: 1px solid var(--line)">
188
- <span>tersio gain dashboard / single file</span>
258
+ <footer class="mono text-xs mt-12 pt-4" style="color: var(--dim); border-top: 1px solid var(--line)">
259
+ <div class="flex items-center gap-3">
260
+ <span>tersio gain dashboard / single file</span>
261
+ <span class="pill flat" id="verChip">tersio</span>
262
+ </div>
263
+ <div class="flex items-center gap-4 mt-3">
264
+ <span>session tokens stay local — parsed on this machine, never uploaded</span>
189
265
  <span class="ml-auto flex items-center gap-1" style="color: var(--ink)">
190
266
  <a href="https://github.com/KurutoDenzeru" target="_blank" rel="noopener" aria-label="GitHub" class="btn-push social grid size-8 place-items-center rounded-lg"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></a>
191
267
  <a href="https://linkedin.com/in/kurtcalacday/" target="_blank" rel="noopener" aria-label="LinkedIn" class="btn-push social grid size-8 place-items-center rounded-lg"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg></a>
192
268
  <a href="https://instagram.com/krtclcdy/" target="_blank" rel="noopener" aria-label="Instagram" class="btn-push social grid size-8 place-items-center rounded-lg"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077"/></svg></a>
193
269
  </span>
194
- <a id="rawlink" href="data.json" class="underline underline-offset-2">raw data.json</a>
270
+ <a id="rawlink" href="data.json" class="underline underline-offset-2">raw data.json</a>
271
+ </div>
195
272
  </footer>
196
273
  </div>
197
274
  </main>
@@ -226,24 +303,31 @@
226
303
  tick(); setInterval(tick, 1000);
227
304
 
228
305
  var VENDORS = [
229
- [/glm|z-ai|zhipu/i, 'Zhipu', 'Z', '#3b82f6'],
230
- [/muse/i, 'Meta', 'M', '#0082fb'],
231
- [/qwen|qwq/i, 'Alibaba', 'Q', '#7c3aed'],
232
- [/deepseek/i, 'DeepSeek', 'D', '#4d6bfe'],
233
- [/mimo/i, 'Xiaomi', 'Mi', '#ff6900'],
234
- [/kimi|moonshot/i, 'Moonshot', 'K', '#a855f7'],
235
- [/minimax/i, 'MiniMax', 'M', '#e11d48'],
236
- [/nemotron|nvidia/i, 'NVIDIA', 'N', '#76b900'],
237
- [/gpt|openai|codex/i, 'OpenAI', 'O', '#10a37f'],
238
- [/claude|anthropic/i, 'Anthropic', 'A', '#d97757'],
239
- [/gemini|google/i, 'Google', 'G', '#4285f4'],
240
- [/grok|xai/i, 'xAI', 'X', '#71717a']
306
+ [/glm|z-ai|zhipu/i, 'Zhipu', 'Z', '#3b82f6', ''],
307
+ [/muse/i, 'Meta', 'M', '#0082fb', 'M6.915 4.03c-1.968 0-3.683 1.28-4.871 3.113C.704 9.208 0 11.883 0 14.449c0 .706.07 1.369.21 1.973a6.624 6.624 0 0 0 .265.86 5.297 5.297 0 0 0 .371.761c.696 1.159 1.818 1.927 3.593 1.927 1.497 0 2.633-.671 3.965-2.444.76-1.012 1.144-1.626 2.663-4.32l.756-1.339.186-.325c.061.1.121.196.183.3l2.152 3.595c.724 1.21 1.665 2.556 2.47 3.314 1.046.987 1.992 1.22 3.06 1.22 1.075 0 1.876-.355 2.455-.843a3.743 3.743 0 0 0 .81-.973c.542-.939.861-2.127.861-3.745 0-2.72-.681-5.357-2.084-7.45-1.282-1.912-2.957-2.93-4.716-2.93-1.047 0-2.088.467-3.053 1.308-.652.57-1.257 1.29-1.82 2.05-.69-.875-1.335-1.547-1.958-2.056-1.182-.966-2.315-1.303-3.454-1.303zm10.16 2.053c1.147 0 2.188.758 2.992 1.999 1.132 1.748 1.647 4.195 1.647 6.4 0 1.548-.368 2.9-1.839 2.9-.58 0-1.027-.23-1.664-1.004-.496-.601-1.343-1.878-2.832-4.358l-.617-1.028a44.908 44.908 0 0 0-1.255-1.98c.07-.109.141-.224.211-.327 1.12-1.667 2.118-2.602 3.358-2.602zm-10.201.553c1.265 0 2.058.791 2.675 1.446.307.327.737.871 1.234 1.579l-1.02 1.566c-.757 1.163-1.882 3.017-2.837 4.338-1.191 1.649-1.81 1.817-2.486 1.817-.524 0-1.038-.237-1.383-.794-.263-.426-.464-1.13-.464-2.046 0-2.221.63-4.535 1.66-6.088.454-.687.964-1.226 1.533-1.533a2.264 2.264 0 0 1 1.088-.285z'],
308
+ [/qwen|qwq/i, 'Alibaba', 'Q', '#7c3aed', 'M23.919 14.545 20.817 9.17l1.47-2.544a.56.56 0 0 0 0-.566l-1.633-2.83a.57.57 0 0 0-.49-.283h-6.207L12.487.402a.57.57 0 0 0-.49-.284H8.732a.56.56 0 0 0-.49.284L5.139 5.775h-2.94a.56.56 0 0 0-.49.284L.077 8.887a.56.56 0 0 0 0 .567L3.18 14.83l-1.47 2.545a.56.56 0 0 0 0 .566l1.634 2.83a.57.57 0 0 0 .49.283h6.205l1.47 2.545a.57.57 0 0 0 .49.284h3.266a.57.57 0 0 0 .49-.284l3.104-5.375h2.94a.57.57 0 0 0 .49-.283l1.634-2.828a.55.55 0 0 0-.004-.568M8.733.686l1.634 2.828-1.634 2.828H21.8L20.164 9.17H7.425L5.63 6.06Zm1.306 19.801-6.205-.002 1.634-2.83h3.265L2.201 6.344h3.267q3.182 5.517 6.367 11.032zm10.124-5.66L18.53 12l-6.532 11.315-1.634-2.83c2.129-3.673 4.25-7.351 6.373-11.028h3.592l3.102 5.374z'],
309
+ [/deepseek/i, 'DeepSeek', 'D', '#4d6bfe', 'M23.748 4.651c-.254-.124-.364.113-.512.233-.051.04-.094.09-.137.137-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.155-.708-.311-.955-.65-.172-.24-.219-.509-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.094.172.187.129.323-.082.28-.18.553-.266.833-.055.179-.137.218-.328.14a5.5 5.5 0 0 1-1.737-1.179c-.857-.828-1.631-1.743-2.597-2.46a12 12 0 0 0-.689-.47c-.985-.957.13-1.743.387-1.836.27-.098.094-.433-.778-.428-.872.003-1.67.295-2.687.685a3 3 0 0 1-.465.136 9.6 9.6 0 0 0-2.883-.101c-1.885.21-3.39 1.1-4.497 2.622C.082 8.776-.231 10.854.152 13.02c.403 2.284 1.568 4.175 3.36 5.653 1.857 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.132-.284 4.994-1.86.47.234.962.328 1.78.398.629.058 1.235-.031 1.705-.129.735-.155.684-.836.418-.961-2.155-1.004-1.682-.595-2.112-.926 1.095-1.295 2.768-3.598 3.284-6.733.05-.346.115-.834.108-1.114-.004-.171.035-.238.23-.257a4.2 4.2 0 0 0 1.545-.475c1.397-.763 1.96-2.016 2.093-3.517.02-.23-.004-.467-.247-.588M11.58 18.168c-2.088-1.642-3.101-2.183-3.52-2.16-.39.024-.32.472-.234.763.09.288.207.487.371.74.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.168-1.361-.801-2.5-1.86-3.301-3.306-.775-1.393-1.225-2.888-1.299-4.482-.02-.385.094-.522.477-.592a4.7 4.7 0 0 1 1.53-.038c2.131.311 3.946 1.264 5.467 2.774.868.86 1.525 1.887 2.202 2.89.72 1.066 1.494 2.082 2.48 2.915.348.291.626.513.892.677-.802.09-2.14.109-3.055-.615zm1.001-6.44a.306.306 0 0 1 .415-.287.3.3 0 0 1 .113.074.3.3 0 0 1 .086.214c0 .17-.136.307-.308.307a.303.303 0 0 1-.306-.307m3.11 1.596c-.2.081-.4.151-.591.16a1.25 1.25 0 0 1-.798-.254c-.274-.23-.47-.358-.551-.758a1.7 1.7 0 0 1 .015-.588c.07-.327-.007-.537-.238-.727-.188-.156-.426-.199-.689-.199a.6.6 0 0 1-.254-.078.253.253 0 0 1-.114-.358 1 1 0 0 1 .192-.21c.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.392.451.462.576.685.915.176.264.336.536.446.848.066.194-.02.353-.25.45'],
310
+ [/mimo/i, 'Xiaomi', 'Mi', '#ff6900', 'M12 0C8.016 0 4.756.255 2.493 2.516.23 4.776 0 8.033 0 12.012c0 3.98.23 7.235 2.494 9.497C4.757 23.77 8.017 24 12 24c3.983 0 7.243-.23 9.506-2.491C23.77 19.247 24 15.99 24 12.012c0-3.984-.233-7.243-2.502-9.504C19.234.252 15.978 0 12 0zM4.906 7.405h5.624c1.47 0 3.007.068 3.764.827.746.746.827 2.233.83 3.676v4.54a.15.15 0 0 1-.152.147h-1.947a.15.15 0 0 1-.152-.148V11.83c-.002-.806-.048-1.634-.464-2.051-.358-.36-1.026-.441-1.72-.458H7.158a.15.15 0 0 0-.151.147v6.98a.15.15 0 0 1-.152.148H4.906a.15.15 0 0 1-.15-.148V7.554a.15.15 0 0 1 .15-.149zm12.131 0h1.949a.15.15 0 0 1 .15.15v8.892a.15.15 0 0 1-.15.148h-1.949a.15.15 0 0 1-.151-.148V7.554a.15.15 0 0 1 .151-.149zM8.92 10.948h2.046c.083 0 .15.066.15.147v5.352a.15.15 0 0 1-.15.148H8.92a.15.15 0 0 1-.152-.148v-5.352a.15.15 0 0 1 .152-.147Z'],
311
+ [/kimi|moonshot/i, 'Moonshot', 'K', '#a855f7', ''],
312
+ [/minimax/i, 'MiniMax', 'M', '#e11d48', 'M11.43 3.92a.86.86 0 1 0-1.718 0v14.236a1.999 1.999 0 0 1-3.997 0V9.022a.86.86 0 1 0-1.718 0v3.87a1.999 1.999 0 0 1-3.997 0V11.49a.57.57 0 0 1 1.139 0v1.404a.86.86 0 0 0 1.719 0V9.022a1.999 1.999 0 0 1 3.997 0v9.134a.86.86 0 0 0 1.719 0V3.92a1.998 1.998 0 1 1 3.996 0v11.788a.57.57 0 1 1-1.139 0zm10.572 3.105a2 2 0 0 0-1.999 1.997v7.63a.86.86 0 0 1-1.718 0V3.923a1.999 1.999 0 0 0-3.997 0v16.16a.86.86 0 0 1-1.719 0V18.08a.57.57 0 1 0-1.138 0v2a1.998 1.998 0 0 0 3.996 0V3.92a.86.86 0 0 1 1.719 0v12.73a1.999 1.999 0 0 0 3.996 0V9.023a.86.86 0 1 1 1.72 0v6.686a.57.57 0 0 0 1.138 0V9.022a2 2 0 0 0-1.998-1.997'],
313
+ [/nemotron|nvidia/i, 'NVIDIA', 'N', '#76b900', 'M8.948 8.798v-1.43a6.7 6.7 0 0 1 .424-.018c3.922-.124 6.493 3.374 6.493 3.374s-2.774 3.851-5.75 3.851c-.398 0-.787-.062-1.158-.185v-4.346c1.528.185 1.837.857 2.747 2.385l2.04-1.714s-1.492-1.952-4-1.952a6.016 6.016 0 0 0-.796.035m0-4.735v2.138l.424-.027c5.45-.185 9.01 4.47 9.01 4.47s-4.08 4.964-8.33 4.964c-.37 0-.733-.035-1.095-.097v1.325c.3.035.61.062.91.062 3.957 0 6.82-2.023 9.593-4.408.459.371 2.34 1.263 2.73 1.652-2.633 2.208-8.772 3.984-12.253 3.984-.335 0-.653-.018-.971-.053v1.864H24V4.063zm0 10.326v1.131c-3.657-.654-4.673-4.46-4.673-4.46s1.758-1.944 4.673-2.262v1.237H8.94c-1.528-.186-2.73 1.245-2.73 1.245s.68 2.412 2.739 3.11M2.456 10.9s2.164-3.197 6.5-3.533V6.201C4.153 6.59 0 10.653 0 10.653s2.35 6.802 8.948 7.42v-1.237c-4.84-.6-6.492-5.936-6.492-5.936z'],
314
+ [/gpt|openai|codex/i, 'OpenAI', 'O', '#10a37f', 'M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z'],
315
+ [/claude|anthropic/i, 'Anthropic', 'A', '#d97757', 'M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z'],
316
+ [/gemini/i, 'Google', 'G', '#4285f4', 'M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81'],
317
+ [/google/i, 'Google', 'G', '#4285f4', 'M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z'],
318
+ [/grok|xai/i, 'xAI', 'X', '#71717a', 'M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z'],
241
319
  ];
242
320
  function vendorOf(model) {
243
321
  for (var i = 0; i < VENDORS.length; i++) {
244
- if (VENDORS[i][0].test(model)) return { name: VENDORS[i][1], mark: VENDORS[i][2], color: VENDORS[i][3] };
322
+ if (VENDORS[i][0].test(model)) return { name: VENDORS[i][1], mark: VENDORS[i][2], color: VENDORS[i][3], icon: VENDORS[i][4] };
245
323
  }
246
- return { name: 'Other', mark: (model || '?').charAt(0).toUpperCase(), color: '#71717a' };
324
+ return { name: 'Other', mark: (model || '?').charAt(0).toUpperCase(), color: '#71717a', icon: '' };
325
+ }
326
+ function badgeHTML(v, small) {
327
+ var inner = v.icon
328
+ ? '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="' + v.icon + '"/></svg>'
329
+ : '<span>' + v.mark + '</span>';
330
+ return '<span class="monogram' + (small ? ' sm' : '') + '" style="background:' + v.color + '" title="' + v.name + '">' + inner + '</span>';
247
331
  }
248
332
  var PALETTE = ['#34d399', '#818cf8', '#22d3ee', '#fbbf24', '#f472b6', '#a78bfa', '#fb923c', '#2dd4bf'];
249
333
 
@@ -255,7 +339,7 @@
255
339
  return b.input + b.output + b.cacheRead + b.cacheWrite;
256
340
  }
257
341
 
258
- var DATA = null, RANGE = 'daily';
342
+ var DATA = null, RANGE = 'daily', cardsShown = false;
259
343
 
260
344
  function topModels(byModel, n) {
261
345
  return Object.keys(byModel).sort(function (a, b) { return modelTotal(byModel, b) - modelTotal(byModel, a); }).slice(0, n);
@@ -268,6 +352,50 @@
268
352
  return Object.keys(set).sort();
269
353
  }
270
354
 
355
+ function renderGraph(byDay) {
356
+ var g = document.getElementById('graph');
357
+ g.innerHTML = '';
358
+ var today = new Date(); today.setHours(0, 0, 0, 0);
359
+ var start = new Date(today); start.setDate(start.getDate() - (25 * 7) - today.getDay());
360
+ var max = 1, sums = {};
361
+ Object.keys(byDay).forEach(function (day) {
362
+ var b = byDay[day];
363
+ sums[day] = b.input + b.output + b.cacheRead + b.cacheWrite;
364
+ if (sums[day] > max) max = sums[day];
365
+ });
366
+ var months = document.getElementById('graphMonths');
367
+ months.innerHTML = '';
368
+ var lastMonth = '';
369
+ for (var w = 0; w < 26; w++) {
370
+ var col = document.createElement('div');
371
+ col.style.cssText = 'display:flex;flex-direction:column;gap:3px;';
372
+ var ml = document.createElement('span');
373
+ ml.className = 'heatmonth w-full truncate';
374
+ for (var i = 0; i < 7; i++) {
375
+ var dt = new Date(start); dt.setDate(dt.getDate() + w * 7 + i);
376
+ var key = dayKey(dt), v = sums[key] || 0, lvl = 0;
377
+ if (v > 0) lvl = Math.min(4, 1 + Math.floor((v / max) * 3.99));
378
+ var cell = document.createElement('span');
379
+ cell.className = 'cell' + (lvl ? ' l' + lvl : '');
380
+ cell.title = key + ': ' + fmt(v) + ' tokens';
381
+ col.appendChild(cell);
382
+ if (i === 0) {
383
+ var mm = dt.toLocaleString('en-US', { month: 'short' });
384
+ ml.textContent = mm !== lastMonth ? mm : '';
385
+ lastMonth = mm;
386
+ }
387
+ }
388
+ g.appendChild(col);
389
+ months.appendChild(ml);
390
+ }
391
+ // Month labels share the column grid.
392
+ months.className = 'heatmonth mono mt-2 flex';
393
+ months.style.cssText = 'display:flex;gap:3px;';
394
+ Array.prototype.forEach.call(months.children, function (m) { m.className += ' flex-1'; });
395
+ document.getElementById('graphRange').textContent =
396
+ dayKey(start) + ' to ' + dayKey(today);
397
+ }
398
+
271
399
  // Buckets: [{ key, label, total, segs: [[model, v]] }]
272
400
  function buckets() {
273
401
  var days = allDays(), tops = topModels(DATA.byModel || {}, 8), out = [];
@@ -363,7 +491,8 @@
363
491
  });
364
492
  if (hasGsap) {
365
493
  gsap.from('#bars > div', { scaleY: 0.2, opacity: 0.2, transformOrigin: 'bottom', duration: 0.5, ease: 'power2.out', stagger: 0.008, overwrite: true });
366
- if (hasST) {
494
+ if (hasST && !cardsShown) {
495
+ cardsShown = true;
367
496
  gsap.from('#modelCards > div', {
368
497
  scale: 0.92, opacity: 0.2, duration: 0.6, ease: 'power2.out', stagger: 0.08, overwrite: true,
369
498
  scrollTrigger: { trigger: '#modelCards', start: 'top 88%' }
@@ -390,7 +519,7 @@
390
519
  tops.slice(0, 3).forEach(function (m, i) {
391
520
  var v = vendorOf(m), d = wow(m);
392
521
  var card = document.createElement('div');
393
- card.className = 'rise md:col-span-4 rounded-xl p-5 overflow-hidden relative';
522
+ card.className = 'modelcard md:col-span-4 rounded-xl p-5 overflow-hidden relative';
394
523
  card.style.cssText = 'border: 1px solid var(--line); background: var(--panel)';
395
524
  var ghost = document.createElement('span');
396
525
  ghost.className = 'display pointer-events-none select-none absolute -right-2 -bottom-7 font-bold';
@@ -401,8 +530,9 @@
401
530
  head.textContent = '0' + (i + 1);
402
531
  var row = document.createElement('div');
403
532
  row.className = 'flex items-center gap-3';
404
- var badge = document.createElement('span');
405
- badge.className = 'monogram'; badge.style.background = v.color; badge.textContent = v.mark;
533
+ var badgeWrap = document.createElement('span');
534
+ badgeWrap.innerHTML = badgeHTML(v, false);
535
+ var badge = badgeWrap.firstChild;
406
536
  var mid = document.createElement('div'); mid.className = 'min-w-0 flex-1';
407
537
  var nm = document.createElement('p'); nm.className = 'mono text-sm font-bold truncate'; nm.textContent = m;
408
538
  var vn = document.createElement('p'); vn.className = 'mono text-xs truncate'; vn.style.color = 'var(--dim)'; vn.textContent = v.name;
@@ -424,10 +554,10 @@
424
554
  var v = vendorOf(m);
425
555
  var li = document.createElement('li');
426
556
  li.className = 'tilt flex items-center gap-3 px-4 py-3' + (i < tops.length - 1 ? ' rowline' : '');
427
- var badge = document.createElement('span');
428
- badge.className = 'monogram'; badge.style.cssText = 'width:28px;height:28px;font-size:11px;border-radius:8px;background:' + v.color;
429
- badge.textContent = v.mark;
430
- badge.title = v.name;
557
+ var badgeWrap = document.createElement('span');
558
+ badgeWrap.innerHTML = badgeHTML(v, true);
559
+ var badge = badgeWrap.firstChild;
560
+ badge.style.cssText += 'width:28px;height:28px;font-size:11px;border-radius:8px;';
431
561
  var mid = document.createElement('div'); mid.className = 'min-w-0 flex-1';
432
562
  var nm = document.createElement('p'); nm.className = 'mono text-sm truncate'; nm.textContent = m;
433
563
  var track = document.createElement('div'); track.className = 'bar-track mt-1.5 h-1.5 overflow-hidden';
@@ -442,24 +572,29 @@
442
572
  }
443
573
 
444
574
  function renderTools() {
445
- var ol = document.getElementById('top');
446
- ol.innerHTML = '';
447
- var tools = DATA.byTool || [], max = 1;
448
- tools.forEach(function (r) { max = Math.max(max, r[1]); });
449
- tools.forEach(function (r) {
450
- var li = document.createElement('li');
451
- var top = document.createElement('div');
452
- top.className = 'flex justify-between gap-2';
453
- var nm = document.createElement('span'); nm.className = 'truncate'; nm.textContent = r[0];
454
- var n = document.createElement('span'); n.className = 'font-bold'; n.textContent = fmt(r[1]);
455
- top.appendChild(nm); top.appendChild(n);
456
- var track = document.createElement('div'); track.className = 'bar-track mt-1 h-1 overflow-hidden';
457
- var fill = document.createElement('div'); fill.className = 'bar-fill h-full';
458
- fill.style.width = Math.max(2, Math.round(r[1] / max * 100)) + '%';
459
- track.appendChild(fill);
460
- li.appendChild(top); li.appendChild(track);
461
- ol.appendChild(li);
575
+ var body = document.getElementById('top');
576
+ body.innerHTML = '';
577
+ var tools = DATA.byTool || [], max = 1, sum = 0;
578
+ tools.forEach(function (r) { max = Math.max(max, r[1]); sum += r[1]; });
579
+ tools.forEach(function (r, i) {
580
+ var tr = document.createElement('tr');
581
+ tr.className = 'tilt' + (i < tools.length - 1 ? ' rowline' : '');
582
+ var share = sum ? Math.round(r[1] / sum * 100) : 0;
583
+ tr.innerHTML = '<td class="text-right pr-3 py-2.5 mono text-xs w-10" style="color: var(--dim)"></td>' +
584
+ '<td class="py-2.5 pr-3 truncate" style="max-width: 280px"></td>' +
585
+ '<td class="text-right py-2.5 pr-3 font-bold"></td>' +
586
+ '<td class="text-right py-2.5 pr-3" style="color: var(--dim)"></td>' +
587
+ '<td class="py-2.5 min-w-32"><div class="bar-track h-1.5 overflow-hidden"><div class="bar-fill h-full"></div></div></td>';
588
+ var tds = tr.children;
589
+ tds[0].textContent = String(i + 1).padStart(2, '0');
590
+ tds[1].textContent = r[0];
591
+ tds[1].title = r[0];
592
+ tds[2].textContent = fmt(r[1]);
593
+ tds[3].textContent = share + '%';
594
+ tds[4].firstChild.firstChild.style.width = Math.max(2, Math.round(r[1] / max * 100)) + '%';
595
+ body.appendChild(tr);
462
596
  });
597
+ document.getElementById('topTable').style.display = tools.length ? '' : 'none';
463
598
  document.getElementById('emptyTop').classList.toggle('hidden', tools.length > 0);
464
599
  }
465
600
 
@@ -475,7 +610,7 @@
475
610
  strip.innerHTML = '';
476
611
  defs.forEach(function (b, i) {
477
612
  var cell = document.createElement('div');
478
- cell.className = 'tilt flex-1 px-4 py-4' + (i > 0 ? ' sm:border-l' : '');
613
+ cell.className = 'stripcell flex-1 px-4 py-4' + (i > 0 ? ' sm:border-l' : '');
479
614
  cell.style.borderColor = 'var(--line)';
480
615
  cell.innerHTML = '<div class="flex items-center gap-2 mono text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">' +
481
616
  '<i data-lucide="' + b[2] + '" class="size-3.5"></i><span></span></div>' +
@@ -488,11 +623,61 @@
488
623
 
489
624
  function money(v) { return '$' + v.toFixed(2); }
490
625
 
626
+ // 14-day volume sparkline + blended-rate daily stats for the USD card.
627
+ // Daily cost is a blended estimate (total USD / total tokens x day
628
+ // tokens), always rendered with ~ and never mixed with measured totals.
629
+ function renderSpark(byDay, usd, savedUsd, total) {
630
+ var el = document.getElementById('usdSpark');
631
+ el.innerHTML = '';
632
+ var keys = Object.keys(byDay).sort().slice(-14);
633
+ var rate = total ? usd / total : 0;
634
+ var max = 1, vols = [];
635
+ keys.forEach(function (k) {
636
+ var v = dayTotal(byDay[k] || {});
637
+ vols.push({ k: k, v: v });
638
+ max = Math.max(max, v);
639
+ });
640
+ var top = vols.slice().sort(function (a, b) { return b.v - a.v; })[0] || { k: '', v: 0 };
641
+ vols.forEach(function (r) {
642
+ var s = document.createElement('span');
643
+ if (r.k === top.k && r.v > 0) s.className = 'top';
644
+ s.style.height = Math.max(4, Math.round(r.v / max * 100)) + '%';
645
+ s.title = r.k + ': ' + fmt(r.v) + ' tokens (~$' + (rate * r.v).toFixed(2) + ')';
646
+ el.appendChild(s);
647
+ });
648
+ var days = keys.length || 1;
649
+ document.getElementById('usdPerDay').textContent = '$' + (usd / days).toFixed(2);
650
+ document.getElementById('usdTopDay').textContent = top.k ? top.k.slice(5) + ' ~$' + (rate * top.v).toFixed(2) : '-';
651
+ document.getElementById('usdLeverage').textContent = usd ? (savedUsd / usd).toFixed(1) : '-';
652
+ }
653
+
654
+ // Week-over-week sums from byDay: { cur, prev } for a token selector.
655
+ function weekSplit(days, pick) {
656
+ var keys = Object.keys(days).sort(), cur = 0, prev = 0;
657
+ var last = keys.slice(-14);
658
+ last.forEach(function (k, i) {
659
+ var v = pick(days[k] || {});
660
+ if (i < last.length - 7) prev += v; else cur += v;
661
+ });
662
+ return { cur: cur, prev: prev };
663
+ }
664
+ function pillHTML(cur, prev, goodWhenUp) {
665
+ if (!prev) return { text: cur ? 'new' : '—', cls: 'flat' };
666
+ var p = (cur - prev) / prev * 100, a = Math.abs(p);
667
+ var cls = a < 0.5 ? 'flat' : ((p > 0) === goodWhenUp ? 'good' : 'bad');
668
+ return { text: (p >= 0 ? '▲ +' : '▼ ') + a.toFixed(1) + '% vs prior 7d', cls: cls };
669
+ }
670
+ function setPill(id, cur, prev, goodWhenUp) {
671
+ var el = document.getElementById(id), r = pillHTML(cur, prev, goodWhenUp);
672
+ el.textContent = r.text;
673
+ el.className = 'pill mono ' + r.cls;
674
+ }
675
+ function dayTotal(b) { return (b.input || 0) + (b.output || 0) + (b.cacheRead || 0) + (b.cacheWrite || 0); }
676
+
491
677
  function render(d) {
492
678
  DATA = d;
493
679
  var t = d.tokens || { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
494
680
  var total = t.input + t.output + t.cacheRead + t.cacheWrite;
495
- document.getElementById('meta').textContent = fmt(total) + ' session tokens / ' + d.total + ' ledger rows';
496
681
  var sub = document.getElementById('herosub');
497
682
  sub.innerHTML = '';
498
683
  ('input ' + fmt(t.input) + ' · output ' + fmt(t.output) + ' · cache read ' + fmt(t.cacheRead) +
@@ -509,13 +694,30 @@
509
694
  }
510
695
  countUp(document.getElementById('usd'), d.usd || 0, true);
511
696
  document.getElementById('usdNote').textContent = d.priced ? 'per-model price table' : 'incl. default pricing';
697
+ var byDay = d.byDay || {};
698
+ renderSpark(byDay, d.usd || 0, d.savedUsd || 0, total);
699
+ document.getElementById('verChip').textContent = 'tersio v' + (d.version || '?');
700
+ var wAll = weekSplit(byDay, dayTotal);
701
+ setPill('usdDelta', wAll.cur, wAll.prev, false);
512
702
  countUp(document.getElementById('saved'), d.savedUsd || 0, true);
703
+ var wCache = weekSplit(byDay, function (b) { return b.cacheRead || 0; });
704
+ setPill('savedDelta', wCache.cur, wCache.prev, true);
513
705
  document.getElementById('co2').textContent = '~' + (d.co2g || 0).toFixed(1) + 'g';
706
+ var wOut = weekSplit(byDay, function (b) { return b.output || 0; });
707
+ setPill('co2Delta', wOut.cur, wOut.prev, false);
514
708
  var share = total ? Math.round((t.cacheRead + t.cacheWrite) / total * 100) : 0;
515
709
  document.getElementById('cacheShare').textContent = share + '%';
516
- document.getElementById('msgs').textContent = fmt(d.messages || 0);
710
+ var shareOf = function (b) { var s = dayTotal(b); return s ? ((b.cacheRead || 0) + (b.cacheWrite || 0)) / s * 100 : 0; };
711
+ var keys = Object.keys(byDay).sort(), cPct = 0, pPct = 0, cn = 0, pn = 0;
712
+ keys.slice(-7).forEach(function (k) { cPct += shareOf(byDay[k]); cn++; });
713
+ keys.slice(-14, -7).forEach(function (k) { pPct += shareOf(byDay[k]); pn++; });
714
+ var pp = (cn ? cPct / cn : 0) - (pn ? pPct / pn : 0);
715
+ var cd = document.getElementById('cacheDelta');
716
+ cd.textContent = (pp >= 0 ? '▲ +' : '▼ ') + Math.abs(pp).toFixed(1) + 'pp vs prior 7d';
717
+ cd.className = 'pill mono ' + (Math.abs(pp) < 0.05 ? 'flat' : (pp > 0 ? 'good' : 'bad'));
517
718
 
518
719
  renderStrip(t);
720
+ renderGraph(d.byDay || {});
519
721
  renderBars();
520
722
  renderModels();
521
723
  renderTools();
@@ -530,9 +732,8 @@
530
732
  scrollTrigger: { trigger: '#herosub', start: 'top 92%', end: 'top 55%', scrub: true }
531
733
  });
532
734
  }
533
- } else {
534
- observe();
535
735
  }
736
+ observe();
536
737
  }
537
738
 
538
739
  var seen = new WeakSet();
@@ -562,10 +763,9 @@
562
763
  });
563
764
 
564
765
  function load() {
565
- document.getElementById('meta').textContent = 'connecting to ledger';
566
766
  fetch('data.json').then(function (r) { return r.json(); }).then(render).catch(function () {
567
- document.getElementById('meta').textContent = 'No live data. Open with: tersio gain --open';
568
767
  document.getElementById('rawlink').style.display = 'none';
768
+ document.getElementById('topTable').style.display = 'none';
569
769
  document.getElementById('emptyTop').classList.remove('hidden');
570
770
  if (window.lucide) lucide.createIcons();
571
771
  observe();
package/cli/dashboard.js CHANGED
@@ -31,7 +31,7 @@ async function runDashboard(options) {
31
31
  if (options.exportFile) {
32
32
  const html = await templateHtml();
33
33
  const inline = html
34
- .replace("fetch('data.json')", `Promise.resolve({ json: function () { return ${dataJson()}; } }).then(function (r) { return r.json(); }).then`)
34
+ .replace("fetch('data.json')", `Promise.resolve({ json: function () { return ${dataJson()}; } })`)
35
35
  .replace('href="brand.webp"', `href="${await faviconDataUri()}"`)
36
36
  .replace('src="brand.webp"', `src="${await faviconDataUri()}"`);
37
37
  await fs.writeFile(options.exportFile, inline, 'utf8');
package/cli/doctor.js CHANGED
@@ -101,8 +101,10 @@ async function runDoctor() {
101
101
  section('Environment');
102
102
  check('Node', true, process.version);
103
103
  check('OMP CLI', ompVersion !== null, ompVersion ?? '');
104
- if (updateVersion)
104
+ if (typeof updateVersion === 'string')
105
105
  warnLine('Tersio CLI', `${updateVersion} available — run tersio update`);
106
+ else if (updateVersion === 'unknown')
107
+ warnLine('Tersio CLI', `${PACKAGE_VERSION} (version check unreachable)`);
106
108
  else
107
109
  check('Tersio CLI', true, PACKAGE_VERSION);
108
110
  console.log(` Home: ${HOME}`);
package/cli/install.js CHANGED
@@ -550,7 +550,7 @@ let updatePromptDone = false;
550
550
  // Scripts, pipes, --yes, and --dry-run keep the old straight-to-install path.
551
551
  async function runCommandMenu() {
552
552
  const newer = await checkForUpdate();
553
- if (newer && !dryRun) {
553
+ if (typeof newer === 'string' && !dryRun) {
554
554
  const answer = await askInteractiveConfirm(`tersio ${newer} is available (installed ${PACKAGE_VERSION}). Install it now?`);
555
555
  if (answer.status === 'confirmed' && answer.value) {
556
556
  await runLatestUpdate();
@@ -582,8 +582,13 @@ async function runCommandMenu() {
582
582
  await runInstall();
583
583
  break;
584
584
  case 'check': {
585
- const latest = await checkForUpdate();
586
- console.log(latest ? ` [update] tersio ${latest} available (installed ${PACKAGE_VERSION})` : ` [ok] tersio ${PACKAGE_VERSION} is the latest`);
585
+ const latest = await checkForUpdate(true);
586
+ if (typeof latest === 'string')
587
+ console.log(` [update] tersio ${latest} available (installed ${PACKAGE_VERSION})`);
588
+ else if (latest === 'unknown')
589
+ console.log(` [warn] could not reach the npm registry — run \`tersio update\` to retry`);
590
+ else
591
+ console.log(` [ok] tersio ${PACKAGE_VERSION} is the latest`);
587
592
  closeRL();
588
593
  break;
589
594
  }
@@ -627,7 +632,7 @@ async function runInstall() {
627
632
  // the apply-update payload, which is itself an update run.
628
633
  if (tty() && !applyUpdate && command !== 'uninstall') {
629
634
  const newer = await checkForUpdate();
630
- if (newer) {
635
+ if (typeof newer === 'string') {
631
636
  // Bare `tersio` with an update pending: offer it now (Y/n) instead of
632
637
  // burying the banner above the install prompts. Yes runs the full
633
638
  // update and stops here — the fresh binary owns what follows.
package/cli/update.js CHANGED
@@ -33,13 +33,15 @@ async function latestPublishedVersion() {
33
33
  return null;
34
34
  }
35
35
  }
36
- // Returns a newer published version, or null. Reads the registry at most once
37
- // per TTL (cached under OMP_PLUGINS_DIR) and stays silent on failure.
38
- async function checkForUpdate() {
36
+ // Returns a newer published version, null when up to date, or 'unknown' when
37
+ // the registry cannot be reached. Reads the registry at most once per TTL
38
+ // (cached under OMP_PLUGINS_DIR) unless force skips the cache — an explicit
39
+ // "check for updates" must never trust a stale cache.
40
+ async function checkForUpdate(force = false) {
39
41
  const cachePath = path.join(OMP_PLUGINS_DIR, 'tersio-update-check.json');
40
42
  const cached = parseJsonObject(await readTextIfExists(cachePath));
41
43
  const cacheFresh = typeof cached?.lastCheck === 'number' && Date.now() - cached.lastCheck < UPDATE_CHECK_TTL_MS;
42
- if (cacheFresh && cached?.latest)
44
+ if (!force && cacheFresh && cached?.latest)
43
45
  return newerThan(cached.latest, PACKAGE_VERSION) ? cached.latest : null;
44
46
  const latest = await latestPublishedVersion();
45
47
  if (latest) {
@@ -47,8 +49,9 @@ async function checkForUpdate() {
47
49
  await fs.writeFile(cachePath, JSON.stringify({ latest, lastCheck: Date.now() }) + '\n', 'utf8').catch(() => { });
48
50
  return newerThan(latest, PACKAGE_VERSION) ? latest : null;
49
51
  }
50
- // Registry unreachable: fall back to a stale cache rather than staying silent.
51
- return cached?.latest && newerThan(cached.latest, PACKAGE_VERSION) ? cached.latest : null;
52
+ // Registry unreachable: a stale cache naming a newer release is still
53
+ // actionable; otherwise report unknown so callers never claim "latest".
54
+ return cached?.latest && newerThan(cached.latest, PACKAGE_VERSION) ? cached.latest : 'unknown';
52
55
  }
53
56
  // Race a probe against a timeout; slow or failing probes resolve null so the
54
57
  // update plan stays best-effort and never blocks the update itself.
package/cli/usage.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // cli/usage.ts — ledger + session-token usage report, tokscale-style.
2
2
  import { co2Grams, importSessionTokens, priceFor, readUsage, usdCost, } from '../extensions/shared/usage-ledger.js';
3
3
  import { withInteractiveSpinner } from './interactive.js';
4
+ import { PACKAGE_VERSION } from './common.js';
4
5
  export function summarizeUsage(rows) {
5
6
  const byKind = {};
6
7
  const detailCounts = {};
@@ -38,6 +39,7 @@ export function summarizeUsage(rows) {
38
39
  savedUsd,
39
40
  costMeasured: session.costMeasured,
40
41
  co2g: co2Grams(session.totals.output),
42
+ version: PACKAGE_VERSION,
41
43
  };
42
44
  }
43
45
  function fmt(n) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krtclcdy/tersio",
3
- "version": "2.8.0",
3
+ "version": "2.9.0",
4
4
  "description": "✂️ Token-saving OMP add-ons: terse Caveman replies, compact RTK shell output, lean Ponytail code calls — one-command combo presets.",
5
5
  "keywords": [
6
6
  "omp",