@krtclcdy/tersio 2.9.0 → 2.11.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.
@@ -0,0 +1,922 @@
1
+ (function() {
2
+ var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
3
+ var hasGsap = !reduce && typeof window.gsap !== 'undefined';
4
+ var hasST = hasGsap && typeof window.ScrollTrigger !== 'undefined';
5
+ if (hasST) gsap.registerPlugin(ScrollTrigger);
6
+
7
+ function fmt(n) { return Number(n).toLocaleString('en-US'); }
8
+ function fmtShort(n) {
9
+ if (n >= 1e12) return (n / 1e12).toFixed(1) + 'T';
10
+ if (n >= 1e9) return (n / 1e9).toFixed(1) + 'B';
11
+ if (n >= 1e6) return (n / 1e6).toFixed(1) + 'M';
12
+ if (n >= 1e3) return (n / 1e3).toFixed(1) + 'K';
13
+ return String(Math.round(n));
14
+ }
15
+
16
+ var CURS = {
17
+ USD: { s: '$', d: 2 }, PHP: { s: '₱', d: 2 }, EUR: { s: '€', d: 2 }, GBP: { s: '£', d: 2 },
18
+ JPY: { s: '¥', d: 0 }, KRW: { s: '₩', d: 0 }, SGD: { s: 'S$', d: 2 }, AUD: { s: 'A$', d: 2 },
19
+ CAD: { s: 'C$', d: 2 }, INR: { s: '₹', d: 2 }
20
+ };
21
+ var FLAGS = { USD: '🇺🇸', PHP: '🇵🇭', EUR: '🇪🇺', GBP: '🇬🇧', JPY: '🇯🇵', KRW: '🇰🇷', SGD: '🇸🇬', AUD: '🇦🇺', CAD: '🇨🇦', INR: '🇮🇳' };
22
+ // ponytail: static snapshot; live rates from frankfurter replace it when reachable
23
+ var fx = { cur: 'USD', rates: { USD: 1, PHP: 58.7, EUR: 0.92, GBP: 0.79, JPY: 149.8, KRW: 1385, SGD: 1.34, AUD: 1.52, CAD: 1.37, INR: 88.2 }, live: false };
24
+ try {
25
+ var savedCur = localStorage.getItem('tersio-fx-cur');
26
+ if (savedCur && CURS[savedCur]) fx.cur = savedCur;
27
+ var savedFx = JSON.parse(localStorage.getItem('tersio-fx') || 'null');
28
+ if (savedFx && savedFx.rates && savedFx.rates.USD === 1) fx.rates = savedFx.rates;
29
+ } catch (e) { }
30
+ function fxMoney(v) {
31
+ var c = CURS[fx.cur] || CURS.USD, r = fx.rates[fx.cur] || 1;
32
+ return c.s + (v * r).toLocaleString('en-US', { minimumFractionDigits: c.d, maximumFractionDigits: c.d });
33
+ }
34
+
35
+ function countUp(el, target, money) {
36
+ function text(v) { return money ? fxMoney(v) : fmt(Math.round(v)); }
37
+ if (!hasGsap || target <= 0) { el.textContent = text(target); return; }
38
+ var o = { v: 0 };
39
+ gsap.to(o, { v: target, duration: 1.1, ease: 'power3.out', onUpdate: function() { el.textContent = text(o.v); } });
40
+ }
41
+
42
+ function tick() {
43
+ var c = document.getElementById('clock');
44
+ if (c) c.textContent = new Date().toLocaleString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' });
45
+ }
46
+ tick(); setInterval(tick, 1000);
47
+ function currentTheme() {
48
+ try { return localStorage.getItem('tersio-theme') || 'system'; } catch (e) { return 'system'; }
49
+ }
50
+ function paintThemeTabs() {
51
+ var v = currentTheme();
52
+ Array.prototype.forEach.call(document.querySelectorAll('[data-theme-val]'), function(b) {
53
+ b.classList.toggle('on', b.getAttribute('data-theme-val') === v);
54
+ });
55
+ }
56
+ function applyTheme(v) {
57
+ if (v === 'system') document.documentElement.removeAttribute('data-theme');
58
+ else document.documentElement.setAttribute('data-theme', v);
59
+ try { localStorage.setItem('tersio-theme', v); } catch (e) { }
60
+ paintThemeTabs();
61
+ }
62
+ applyTheme(currentTheme());
63
+ (function initRail() {
64
+ var rail = document.getElementById('rail');
65
+ if (!rail || initRail.done) return;
66
+ initRail.done = true;
67
+ var N = 24, ticks = [];
68
+ for (var i = 0; i < N; i++) {
69
+ (function(i) {
70
+ var b = document.createElement('button');
71
+ b.type = 'button'; b.tabIndex = -1; b.setAttribute('aria-hidden', 'true');
72
+ b.addEventListener('click', function() {
73
+ var max = document.documentElement.scrollHeight - window.innerHeight;
74
+ window.scrollTo({ top: max * (i / (N - 1)), behavior: 'smooth' });
75
+ });
76
+ rail.appendChild(b); ticks.push(b);
77
+ })(i);
78
+ }
79
+ var queued = false;
80
+ function paint() {
81
+ queued = false;
82
+ var max = document.documentElement.scrollHeight - window.innerHeight;
83
+ var f = max > 0 ? window.scrollY / max : 0;
84
+ ticks.forEach(function(b, j) { b.classList.toggle('lit', j / (N - 1) <= f); });
85
+ }
86
+ window.addEventListener('scroll', function() {
87
+ if (!queued) { queued = true; requestAnimationFrame(paint); }
88
+ }, { passive: true });
89
+ paint();
90
+ })();
91
+ (function initFx() {
92
+ var btn = document.getElementById('fxBtn'), panel = document.getElementById('fxPanel'), cur = document.getElementById('fxCur');
93
+ var keys = Object.keys(CURS), active = -1;
94
+ function paint() {
95
+ cur.textContent = FLAGS[fx.cur] + ' ' + fx.cur;
96
+ Array.prototype.forEach.call(panel.children, function(o) {
97
+ o.setAttribute('aria-selected', o.dataset.cur === fx.cur ? 'true' : 'false');
98
+ });
99
+ }
100
+ function setCur(k) {
101
+ fx.cur = k; active = keys.indexOf(k);
102
+ try { localStorage.setItem('tersio-fx-cur', fx.cur); } catch (e) { }
103
+ paint();
104
+ if (DATA) render(DATA);
105
+ }
106
+ function open(show) {
107
+ var willOpen = show === undefined ? panel.classList.contains('hidden') : show;
108
+ panel.classList.toggle('hidden', !willOpen);
109
+ btn.setAttribute('aria-expanded', willOpen ? 'true' : 'false');
110
+ if (willOpen) mark(keys.indexOf(fx.cur));
111
+ }
112
+ function mark(i) {
113
+ active = (i + keys.length) % keys.length;
114
+ Array.prototype.forEach.call(panel.children, function(o, j) {
115
+ o.classList.toggle('active', j === active);
116
+ });
117
+ var el = panel.children[active];
118
+ if (el) el.focus();
119
+ }
120
+ keys.forEach(function(k) {
121
+ var o = document.createElement('button');
122
+ o.type = 'button'; o.className = 'fxopt mono'; o.dataset.cur = k;
123
+ o.setAttribute('role', 'option');
124
+ o.innerHTML = '<span>' + FLAGS[k] + ' ' + k + '</span><svg class="tick" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>';
125
+ o.addEventListener('click', function() { setCur(k); open(false); btn.focus(); });
126
+ o.addEventListener('mousemove', function() { mark(keys.indexOf(k)); });
127
+ panel.appendChild(o);
128
+ });
129
+ paint();
130
+ btn.addEventListener('click', function() { open(); });
131
+ btn.addEventListener('keydown', function(ev) {
132
+ if (ev.key === 'ArrowDown' || ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); open(true); }
133
+ });
134
+ panel.addEventListener('keydown', function(ev) {
135
+ if (ev.key === 'Escape') { open(false); btn.focus(); }
136
+ else if (ev.key === 'ArrowDown') { ev.preventDefault(); mark(active + 1); }
137
+ else if (ev.key === 'ArrowUp') { ev.preventDefault(); mark(active - 1); }
138
+ else if (ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); setCur(keys[active]); open(false); btn.focus(); }
139
+ else if (ev.key === 'Tab') open(false);
140
+ });
141
+ document.addEventListener('click', function(ev) {
142
+ if (!panel.classList.contains('hidden') && !btn.contains(ev.target) && !panel.contains(ev.target)) open(false);
143
+ });
144
+ if (typeof fetch !== 'function' || typeof AbortController === 'undefined') return;
145
+ var ctl = new AbortController();
146
+ var to = setTimeout(function() { ctl.abort(); }, 4000);
147
+ fetch('https://api.frankfurter.app/latest?from=USD', { signal: ctl.signal }).then(function(r) { return r.json(); }).then(function(j) {
148
+ clearTimeout(to);
149
+ if (!j || !j.rates) return;
150
+ var ok = Object.keys(CURS).every(function(k) { return k === 'USD' || typeof j.rates[k] === 'number'; });
151
+ if (!ok) return;
152
+ fx.rates = Object.assign({ USD: 1 }, j.rates); fx.live = true;
153
+ try { localStorage.setItem('tersio-fx', JSON.stringify({ rates: fx.rates, ts: Date.now() })); } catch (e) { }
154
+ if (DATA) render(DATA);
155
+ }).catch(function() { clearTimeout(to); });
156
+ })();
157
+ Array.prototype.forEach.call(document.querySelectorAll('[data-theme-val]'), function(b) {
158
+ b.addEventListener('click', function() { applyTheme(b.getAttribute('data-theme-val')); });
159
+ });
160
+ (function() {
161
+ var sysMedia = window.matchMedia('(prefers-color-scheme: dark)');
162
+ if (sysMedia && typeof sysMedia.addEventListener === 'function') sysMedia.addEventListener('change', function() {
163
+ if (currentTheme() === 'system') applyTheme('system');
164
+ });
165
+ })();
166
+
167
+ var PROVIDERS = [
168
+ [/muse/i, 'Meta', 'meta', '#0082fb'],
169
+ [/deepseek/i, 'DeepSeek', 'deepseek', '#4d6bfe'],
170
+ [/qwen|qwq/i, 'Qwen', 'qwen', '#6950EF'],
171
+ [/glm|z-ai|zhipu/i, 'Z.ai', 'zdotai', '#2D2D2D'],
172
+ [/mimo/i, 'Xiaomi', 'xiaomi', '#ff6900'],
173
+ [/kimi|moonshot/i, 'Moonshot', 'kimi', '#a855f7'],
174
+ [/minimax/i, 'MiniMax', 'minimax', '#e11d48'],
175
+ [/nemotron|nvidia/i, 'NVIDIA', 'nvidia', '#76b900'],
176
+ [/mistral/i, 'Mistral', 'mistralai', '#ff7000'],
177
+ [/claude|anthropic/i, 'Anthropic', 'anthropic', '#d97757'],
178
+ [/gemini|google/i, 'Google', 'googlegemini', '#4285f4'],
179
+ ];
180
+ function vendorOf(model) {
181
+ for (var i = 0; i < PROVIDERS.length; i++) {
182
+ if (PROVIDERS[i][0].test(model)) return { name: PROVIDERS[i][1], slug: PROVIDERS[i][2], color: PROVIDERS[i][3] };
183
+ }
184
+ return { name: 'Other', slug: '', color: '#71717a' };
185
+ }
186
+ // Brand glyph from the Simple Icons CDN; a missing slug (or offline export)
187
+ // falls back to a neutral bot glyph. Never a bare initial.
188
+ function brandHTML(v, small) {
189
+ var icon = v.slug
190
+ ? '<img src="https://cdn.simpleicons.org/' + v.slug + '/white" alt="" loading="lazy" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'grid\';">'
191
+ : '';
192
+ var fb = '<i data-lucide="bot" class="fb"' + (v.slug ? '' : ' style="display:grid"') + '></i>';
193
+ return '<span class="brandmark' + (small ? ' sm' : '') + '" style="background:' + v.color + '" title="' + v.name + '">' + icon + fb + '</span>';
194
+ }
195
+ var PALETTE = ['#34d399', '#818cf8', '#22d3ee', '#fbbf24', '#f472b6', '#a78bfa', '#fb923c', '#2dd4bf'];
196
+
197
+ function dayKey(d) {
198
+ return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
199
+ }
200
+ function relTime(ts) {
201
+ var s = Math.max(0, Math.floor((Date.now() - ts) / 1000));
202
+ if (s < 60) return 'just now';
203
+ if (s < 3600) return Math.floor(s / 60) + 'm ago';
204
+ if (s < 86400) return Math.floor(s / 3600) + 'h ago';
205
+ return Math.floor(s / 86400) + 'd ago';
206
+ }
207
+ function modelTotal(byModel, m) {
208
+ var b = byModel[m] || { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
209
+ return b.input + b.output + b.cacheRead + b.cacheWrite;
210
+ }
211
+
212
+ var DATA = null, RANGE = 'daily', cardsShown = false;
213
+
214
+ function topModels(byModel, n) {
215
+ return Object.keys(byModel).sort(function(a, b) { return modelTotal(byModel, b) - modelTotal(byModel, a); }).slice(0, n);
216
+ }
217
+
218
+ function allDays() {
219
+ var set = {};
220
+ Object.keys(DATA.byDay || {}).forEach(function(d) { set[d] = 1; });
221
+ Object.keys(DATA.byDayModel || {}).forEach(function(d) { set[d] = 1; });
222
+ return Object.keys(set).sort();
223
+ }
224
+
225
+ var tipEl = null;
226
+ function tip() {
227
+ if (!tipEl) {
228
+ tipEl = document.createElement('div');
229
+ tipEl.id = 'tip'; tipEl.className = 'mono'; tipEl.setAttribute('role', 'tooltip');
230
+ document.body.appendChild(tipEl);
231
+ }
232
+ return tipEl;
233
+ }
234
+ function showTip(html, x, y) {
235
+ var t = tip();
236
+ t.innerHTML = html;
237
+ t.classList.add('show');
238
+ moveTip(x, y);
239
+ }
240
+ function moveTip(x, y) {
241
+ if (!tipEl) return;
242
+ var w = 300, h = tipEl.offsetHeight || 220;
243
+ var lx = x + 16 > window.innerWidth - w ? x - w - 12 : x + 16;
244
+ var ly = y + 16 > window.innerHeight - h ? y - h - 12 : y + 16;
245
+ tipEl.style.left = Math.max(8, lx) + 'px';
246
+ tipEl.style.top = Math.max(8, ly) + 'px';
247
+ }
248
+ function hideTip() { if (tipEl) tipEl.classList.remove('show'); }
249
+ function dateHead(key) {
250
+ var dt = new Date(key + 'T12:00:00');
251
+ return isNaN(dt) ? key : dt.toLocaleString('en-US', { month: 'short', day: 'numeric' }).toUpperCase();
252
+ }
253
+ function shortName(m) { return m.length > 22 ? m.slice(0, 21) + '...' : m; }
254
+ function tipDayHTML(key, total, rows) {
255
+ var h = '<div class="tt">' + dateHead(key) + '</div><div class="tv">' + fmtShort(total) + ' total</div>';
256
+ rows.forEach(function(r) {
257
+ h += '<div class="tr"><span class="sw" style="background:' + r[2] + '"></span>' +
258
+ '<span class="tn">' + shortName(r[0]) + '</span><span class="tvr">' + fmtShort(r[1]) + '</span></div>';
259
+ });
260
+ return h;
261
+ }
262
+ function tipModelHTML(m) {
263
+ var b = (DATA.byModel || {})[m] || { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
264
+ var req = ((DATA.byModelMessages || {})[m]) || 0;
265
+ var denom = b.input + b.cacheRead;
266
+ var hit = denom ? (b.cacheRead / denom * 100).toFixed(1) + '%' : '-';
267
+ var total = b.input + b.output + b.cacheRead + b.cacheWrite;
268
+ var h = '<div class="tt">' + shortName(m) + '</div><div class="tv">' + fmtShort(total) + ' total</div>';
269
+ h += '<div class="tr"><span class="sw" style="background:var(--accent)"></span><span class="tn">input</span><span class="tvr">' + fmt(b.input) + '</span></div>';
270
+ h += '<div class="tr"><span class="sw" style="background:var(--dim)"></span><span class="tn">output</span><span class="tvr">' + fmt(b.output) + '</span></div>';
271
+ h += '<div class="tr"><span class="sw" style="background:var(--dim)"></span><span class="tn">requests</span><span class="tvr">' + fmt(req) + '</span></div>';
272
+ h += '<div class="tr"><span class="sw" style="background:var(--dim)"></span><span class="tn">cache hit</span><span class="tvr">' + hit + '</span></div>';
273
+ return h;
274
+ }
275
+ function tipCo2HTML() {
276
+ var h = '<div class="tt">CO2</div><div class="tv">~' + (DATA.co2g || 0).toFixed(1) + 'g est.</div>';
277
+ h += '<div class="tr"><span class="sw" style="background:var(--accent)"></span><span class="tn">energy</span><span class="tvr">~' + (DATA.energyWh || 0).toFixed(1) + ' Wh</span></div>';
278
+ h += '<div class="tr"><span class="sw" style="background:var(--dim)"></span><span class="tn">method</span><span class="tvr">EcoLogits 0.8.2 port</span></div>';
279
+ h += '<div class="tr"><span class="sw" style="background:var(--dim)"></span><span class="tn">served</span><span class="tvr">ceiling / 32</span></div>';
280
+ h += '<div class="tr"><span class="sw" style="background:var(--dim)"></span><span class="tn">grid</span><span class="tvr">per provider</span></div>';
281
+ return h;
282
+ }
283
+ function hoverModel(el, m) {
284
+ el.addEventListener('mouseenter', function(ev) { showTip(tipModelHTML(m), ev.clientX, ev.clientY); });
285
+ el.addEventListener('mousemove', function(ev) { moveTip(ev.clientX, ev.clientY); });
286
+ el.addEventListener('mouseleave', hideTip);
287
+ }
288
+ function dayModelRows(key) {
289
+ var per = (DATA.byDayModel || {})[key] || {};
290
+ var tops = topModels(DATA.byModel || {}, 8), rows = [], other = 0;
291
+ tops.forEach(function(m, i) {
292
+ var v = per[m] || 0;
293
+ if (v) rows.push([m, v, PALETTE[i % PALETTE.length]]);
294
+ });
295
+ Object.keys(per).forEach(function(m) { if (tops.indexOf(m) < 0) other += per[m]; });
296
+ if (other) rows.push(['Other', other, 'var(--dim)']);
297
+ return rows;
298
+ }
299
+
300
+ var GMODE = 'daily';
301
+ function mondayOf(d) {
302
+ var m = new Date(d);
303
+ m.setDate(d.getDate() - ((d.getDay() + 6) % 7));
304
+ m.setHours(0, 0, 0, 0);
305
+ return m;
306
+ }
307
+ function weekTipHTML(monKey, weekTotal, weekPer) {
308
+ var mon = new Date(monKey + 'T12:00:00'), sun = new Date(mon);
309
+ sun.setDate(mon.getDate() + 6);
310
+ var head = isNaN(mon) ? monKey : mon.toLocaleString('en-US', { month: 'short', day: 'numeric' }).toUpperCase() +
311
+ ' - ' + (isNaN(sun) ? '' : sun.toLocaleString('en-US', { month: 'short', day: 'numeric' }).toUpperCase());
312
+ var tops = topModels(DATA.byModel || {}, 8), rows = [], other = 0;
313
+ tops.forEach(function(m, i) {
314
+ var v = weekPer[m] || 0;
315
+ if (v) rows.push([m, v, PALETTE[i % PALETTE.length]]);
316
+ });
317
+ Object.keys(weekPer).forEach(function(m) { if (tops.indexOf(m) < 0) other += weekPer[m]; });
318
+ if (other) rows.push(['Other', other, 'var(--dim)']);
319
+ return tipDayHTML(head, weekTotal, rows.length ? rows : [['no activity', 0, 'var(--dim)']]);
320
+ }
321
+ // Shared empty state (shadcn pattern): icon chip + title + hint. Callers
322
+ // inject the HTML; render()'s trailing lucide.createIcons() paints icons.
323
+ function emptyState(icon, title, desc) {
324
+ return '<span class="empty-icon"><i data-lucide="' + icon + '" class="size-5"></i></span>' +
325
+ '<p class="empty-title">' + title + '</p>' +
326
+ '<p class="empty-desc">' + desc + '</p>';
327
+ }
328
+ function renderGraph(byDay) {
329
+ var g = document.getElementById('graph');
330
+ g.innerHTML = '';
331
+ var today = new Date(); today.setHours(0, 0, 0, 0);
332
+ var start = new Date(today); start.setDate(start.getDate() - (52 * 7) - today.getDay());
333
+ var sums = {};
334
+ Object.keys(byDay).forEach(function(day) {
335
+ var b = byDay[day];
336
+ sums[day] = b.input + b.output + b.cacheRead + b.cacheWrite;
337
+ });
338
+ var hasData = Object.keys(sums).some(function(k) { return sums[k] > 0; });
339
+ document.getElementById('emptyGraph').classList.toggle('hidden', hasData);
340
+ document.getElementById('graph').style.display = hasData ? '' : 'none';
341
+ document.getElementById('graphMonths').style.display = hasData ? '' : 'none';
342
+ if (!hasData) {
343
+ document.getElementById('graphRange').textContent = '';
344
+ document.getElementById('graphCap').textContent = 'no data in trailing 12 months';
345
+ return;
346
+ }
347
+ // per-day values under the active mode
348
+ var dayKeys = [], wk;
349
+ for (wk = 0; wk < 53; wk++) for (var di = 0; di < 7; di++) {
350
+ var dd = new Date(start); dd.setDate(dd.getDate() + wk * 7 + di);
351
+ dayKeys.push(dayKey(dd));
352
+ }
353
+ var weekPer = {}, weekTotal = {}, run = 0, cellVal = {};
354
+ dayKeys.forEach(function(k) {
355
+ var dt = new Date(k + 'T12:00:00');
356
+ if (GMODE === 'weekly' && !isNaN(dt)) {
357
+ var mk = dayKey(mondayOf(dt));
358
+ weekTotal[mk] = (weekTotal[mk] || 0) + (sums[k] || 0);
359
+ var per = (DATA.byDayModel || {})[k] || {};
360
+ if (!weekPer[mk]) weekPer[mk] = {};
361
+ Object.keys(per).forEach(function(m) { weekPer[mk][m] = (weekPer[mk][m] || 0) + per[m]; });
362
+ }
363
+ if (GMODE === 'cumulative') { run += sums[k] || 0; cellVal[k] = run; }
364
+ });
365
+ var max = 1;
366
+ if (GMODE === 'weekly') {
367
+ Object.keys(weekTotal).forEach(function(k) { max = Math.max(max, weekTotal[k]); });
368
+ } else if (GMODE === 'cumulative') {
369
+ max = Math.max(1, run);
370
+ } else {
371
+ Object.keys(sums).forEach(function(k) { max = Math.max(max, sums[k]); });
372
+ }
373
+ var months = document.getElementById('graphMonths');
374
+ months.innerHTML = '';
375
+ var lastMonth = '';
376
+ for (var w = 0; w < 53; w++) {
377
+ var col = document.createElement('div');
378
+ col.style.cssText = 'display:flex;flex-direction:column;gap:3px;min-width:0;';
379
+ var ml = document.createElement('span');
380
+ ml.className = 'heatmonth w-full';
381
+ for (var i = 0; i < 7; i++) {
382
+ var cdt = new Date(start); cdt.setDate(cdt.getDate() + w * 7 + i);
383
+ var key = dayKey(cdt), v, tipFn;
384
+ if (GMODE === 'weekly') {
385
+ var mk2 = dayKey(mondayOf(cdt));
386
+ v = weekTotal[mk2] || 0;
387
+ tipFn = (function(mk3, vv) {
388
+ return function() { return weekTipHTML(mk3, vv, weekPer[mk3] || {}); };
389
+ })(mk2, v);
390
+ } else if (GMODE === 'cumulative') {
391
+ v = cellVal[key] || 0;
392
+ tipFn = (function(kk, vv) {
393
+ return function() {
394
+ var rows = dayModelRows(kk);
395
+ return tipDayHTML('THROUGH ' + dateHead(kk), vv, rows.length ? rows : [['no activity', 0, 'var(--dim)']]);
396
+ };
397
+ })(key, v);
398
+ } else {
399
+ v = sums[key] || 0;
400
+ tipFn = (function(kk, vv) {
401
+ return function() {
402
+ var rows = dayModelRows(kk);
403
+ return tipDayHTML(kk, vv, rows.length ? rows : [['no activity', 0, 'var(--dim)']]);
404
+ };
405
+ })(key, v);
406
+ }
407
+ var lvl = 0;
408
+ if (v > 0) lvl = Math.min(4, 1 + Math.floor((v / max) * 3.99));
409
+ var cell = document.createElement('span');
410
+ cell.className = 'cell' + (lvl ? ' l' + lvl : '');
411
+ (function(fn) {
412
+ cell.addEventListener('mouseenter', function(ev) { showTip(fn(), ev.clientX, ev.clientY); });
413
+ cell.addEventListener('mousemove', function(ev) { moveTip(ev.clientX, ev.clientY); });
414
+ cell.addEventListener('mouseleave', hideTip);
415
+ })(tipFn);
416
+ col.appendChild(cell);
417
+ if (i === 0) {
418
+ var mm = cdt.toLocaleString('en-US', { month: 'short' });
419
+ ml.textContent = mm !== lastMonth ? mm : '';
420
+ lastMonth = mm;
421
+ }
422
+ }
423
+ g.appendChild(col);
424
+ months.appendChild(ml);
425
+ }
426
+ Array.prototype.forEach.call(months.children, function(m) { m.style.minWidth = '0'; });
427
+ document.getElementById('graphRange').textContent =
428
+ dayKey(start) + ' to ' + dayKey(today);
429
+ document.getElementById('graphCap').textContent =
430
+ GMODE === 'weekly' ? 'weekly totals / trailing 12 months' :
431
+ GMODE === 'cumulative' ? 'running total / trailing 12 months' :
432
+ 'daily values / trailing 12 months';
433
+ }
434
+ function bindGraphTabs() {
435
+ if (bindGraphTabs.done) return;
436
+ bindGraphTabs.done = true;
437
+ Array.prototype.forEach.call(document.querySelectorAll('#activity [data-gmode]'), function(btn) {
438
+ btn.addEventListener('click', function() {
439
+ Array.prototype.forEach.call(document.querySelectorAll('#activity [data-gmode]'), function(b) { b.classList.remove('on'); });
440
+ btn.classList.add('on');
441
+ GMODE = btn.getAttribute('data-gmode');
442
+ if (DATA) renderGraph(DATA.byDay || {});
443
+ });
444
+ });
445
+ }
446
+
447
+ // 30-day token line: daily totals, smoothed path, hover dot + per-model tip.
448
+ function wow(model) {
449
+ var days = allDays().slice(-14), cur = 0, prev = 0;
450
+ days.forEach(function(d, i) {
451
+ var v = ((DATA.byDayModel || {})[d] || {})[model] || 0;
452
+ if (i < days.length - 7) prev += v; else cur += v;
453
+ });
454
+ if (!prev) return cur ? 'new' : '-';
455
+ var p = Math.round((cur - prev) / prev * 100);
456
+ return (p >= 0 ? '+' : '') + p + '%';
457
+ }
458
+ function renderModels() {
459
+ var byModel = DATA.byModel || {};
460
+ var tops = topModels(byModel, 8).filter(function(m) { return modelTotal(byModel, m) > 0; });
461
+ var cards = document.getElementById('modelCards');
462
+ cards.innerHTML = '';
463
+ if (!tops.length) cards.innerHTML = '<div class="empty md:col-span-3">' + emptyState('boxes', 'No models yet', 'Model token totals will appear here once sessions report tokens.') + '</div>';
464
+ tops.slice(0, 3).forEach(function(m, i) {
465
+ var v = vendorOf(m), d = wow(m);
466
+ var card = document.createElement('div');
467
+ card.className = 'modelcard relative p-4 rounded-xl overflow-hidden';
468
+ if (v.slug) {
469
+ var ghost = document.createElement('img');
470
+ ghost.className = 'ghostimg'; ghost.alt = '';
471
+ ghost.src = 'https://cdn.simpleicons.org/' + v.slug + '/white';
472
+ ghost.setAttribute('aria-hidden', 'true');
473
+ card.appendChild(ghost);
474
+ }
475
+ card.style.cssText = 'border: 1px solid var(--line); background: var(--panel)';
476
+ var head = document.createElement('p');
477
+ head.className = 'mono text-xs mb-3'; head.style.color = 'var(--dim)';
478
+ head.textContent = '0' + (i + 1);
479
+ var row = document.createElement('div');
480
+ row.className = 'flex items-center gap-3';
481
+ var badgeWrap = document.createElement('span');
482
+ badgeWrap.innerHTML = brandHTML(v, false);
483
+ var badge = badgeWrap.firstChild;
484
+ var mid = document.createElement('div'); mid.className = 'min-w-0 flex-1';
485
+ var nm = document.createElement('p'); nm.className = 'mono text-sm font-bold truncate'; nm.textContent = m;
486
+ var vn = document.createElement('p'); vn.className = 'mono text-xs truncate'; vn.style.color = 'var(--dim)'; vn.textContent = v.name;
487
+ mid.appendChild(nm); mid.appendChild(vn);
488
+ var tot = document.createElement('p'); tot.className = 'mono font-bold text-lg shrink-0'; tot.textContent = fmtShort(modelTotal(byModel, m));
489
+ row.appendChild(badge); row.appendChild(mid); row.appendChild(tot);
490
+ card.appendChild(head); card.appendChild(row);
491
+ if (d !== 'new') {
492
+ var delta = document.createElement('p');
493
+ delta.className = 'mono text-xs mt-3';
494
+ delta.style.color = d.charAt(0) === '-' ? '#f87171' : 'var(--accent)';
495
+ delta.textContent = d;
496
+ card.appendChild(delta);
497
+ }
498
+ hoverModel(card, m);
499
+ cards.appendChild(card);
500
+ });
501
+ var ol = document.getElementById('models');
502
+ ol.innerHTML = '';
503
+ var max = 1;
504
+ tops.forEach(function(m) { max = Math.max(max, modelTotal(byModel, m)); });
505
+ tops.forEach(function(m, i) {
506
+ var v = vendorOf(m);
507
+ var li = document.createElement('li');
508
+ li.className = 'mrow flex items-center gap-3 px-4 py-3' + (i < tops.length - 1 ? ' rowline' : '');
509
+ var badgeWrap = document.createElement('span');
510
+ badgeWrap.innerHTML = brandHTML(v, true);
511
+ var badge = badgeWrap.firstChild;
512
+ var mid = document.createElement('div'); mid.className = 'min-w-0 flex-1';
513
+ var nm = document.createElement('p'); nm.className = 'mono text-sm truncate'; nm.textContent = m;
514
+ var track = document.createElement('div'); track.className = 'bar-track mt-1.5 h-1.5 overflow-hidden';
515
+ var fill = document.createElement('div'); fill.className = 'bar-fill h-full';
516
+ var mv = modelTotal(byModel, m);
517
+ fill.dataset.w = Math.round(mv / max * 100) + '%';
518
+ fill.style.width = '0';
519
+ track.appendChild(fill); mid.appendChild(nm); mid.appendChild(track);
520
+ var right = document.createElement('div'); right.className = 'shrink-0 text-right';
521
+ var n = document.createElement('p'); n.className = 'mono font-bold'; n.textContent = fmt(modelTotal(byModel, m));
522
+ var cost = document.createElement('p'); cost.className = 'mono text-xs'; cost.style.color = 'var(--dim)';
523
+ cost.textContent = fxMoney((DATA.byModelUsd || {})[m] || 0);
524
+ right.appendChild(n); right.appendChild(cost);
525
+ li.appendChild(badge); li.appendChild(mid); li.appendChild(right);
526
+ hoverModel(li, m);
527
+ ol.appendChild(li);
528
+ });
529
+ document.getElementById('modelsCount').textContent = tops.length ? tops.length + ' models' : '';
530
+ ol.style.display = tops.length ? '' : 'none';
531
+ document.getElementById('emptyModels').classList.toggle('hidden', tops.length > 0);
532
+ if (tops.length && 'IntersectionObserver' in window && !reduce) {
533
+ var fio = new IntersectionObserver(function(entries) {
534
+ entries.forEach(function(e) {
535
+ if (e.isIntersecting) {
536
+ e.target.querySelectorAll('.bar-fill').forEach(function(f) { f.style.width = f.dataset.w || '0'; });
537
+ fio.disconnect();
538
+ }
539
+ });
540
+ }, { threshold: 0.2 });
541
+ fio.observe(ol);
542
+ } else if (tops.length) {
543
+ ol.querySelectorAll('.bar-fill').forEach(function(f) { f.style.width = f.dataset.w || '0'; });
544
+ }
545
+ if (hasGsap && hasST && !cardsShown && tops.length) {
546
+ cardsShown = true;
547
+ gsap.from('#modelCards > div', {
548
+ scale: 0.92, opacity: 0.2, duration: 0.6, ease: 'power2.out', stagger: 0.08, overwrite: true,
549
+ scrollTrigger: { trigger: '#modelCards', start: 'top 88%' }
550
+ });
551
+ }
552
+ }
553
+
554
+ // Union of session tool calls + RTK-metered commands, sortable + paged.
555
+ // Session tools were never metered per command: saved / avg / time show – and sort last.
556
+ var cmdSort = { key: 'count', dir: -1 }, cmdPage = 1, cmdPer = 15, cmdRows = [];
557
+ function cmdVal(r, key) { return key === 'name' ? r.name.toLowerCase() : r[key]; }
558
+ function sortCmd() {
559
+ var k = cmdSort.key, d = cmdSort.dir;
560
+ cmdRows.sort(function(a, b) {
561
+ var av = cmdVal(a, k), bv = cmdVal(b, k);
562
+ if (av === null || av === undefined) return 1;
563
+ if (bv === null || bv === undefined) return -1;
564
+ if (av < bv) return -d;
565
+ if (av > bv) return d;
566
+ return b.count - a.count;
567
+ });
568
+ Array.prototype.forEach.call(document.querySelectorAll('#tools .thsort'), function(btn) {
569
+ var on = btn.dataset.sort === k;
570
+ btn.querySelector('.arr').textContent = on ? (d === 1 ? '↑' : '↓') : '⇅';
571
+ btn.closest('th').setAttribute('aria-sort', on ? (d === 1 ? 'ascending' : 'descending') : 'none');
572
+ });
573
+ }
574
+ function renderCmd() {
575
+ var g = (DATA.rtkGain || {});
576
+ cmdRows = (DATA.byTool || []).map(function(r) {
577
+ return { name: r[0], count: r[1], saved: null, avgPct: null, avgMs: null };
578
+ });
579
+ (g.byCommand || []).forEach(function(r) {
580
+ cmdRows.push({ name: r.command, count: r.count, saved: r.saved, avgPct: r.avgPct, avgMs: r.avgMs });
581
+ });
582
+ sortCmd();
583
+ var pages = Math.max(1, Math.ceil(cmdRows.length / cmdPer));
584
+ if (cmdPage > pages) cmdPage = pages;
585
+ var body = document.getElementById('cmdBody');
586
+ body.innerHTML = '';
587
+ var max = 1;
588
+ cmdRows.forEach(function(r) { max = Math.max(max, r.count); });
589
+ var start = (cmdPage - 1) * cmdPer;
590
+ cmdRows.slice(start, start + cmdPer).forEach(function(r, i) {
591
+ var n = start + i;
592
+ var tr = document.createElement('tr');
593
+ tr.className = 'mrow' + (i < Math.min(cmdRows.length - start, cmdPer) - 1 ? ' rowline' : '');
594
+ tr.innerHTML = '<td class="text-right pr-3 py-2.5 mono text-xs w-10" style="color: var(--dim)"></td>' +
595
+ '<td class="py-2.5 pr-3 truncate" style="max-width: 280px"></td>' +
596
+ '<td class="text-right py-2.5 pr-3 font-bold"></td>' +
597
+ '<td class="text-right py-2.5 pr-3 font-bold"></td>' +
598
+ '<td class="text-right py-2.5 pr-3" style="color: var(--accent)"></td>' +
599
+ '<td class="text-right py-2.5 pr-3" style="color: var(--dim)"></td>' +
600
+ '<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>';
601
+ var tds = tr.children;
602
+ tds[0].textContent = String(n + 1).padStart(2, '0');
603
+ tds[1].textContent = r.name;
604
+ tds[1].title = r.name;
605
+ tds[2].textContent = fmt(r.count);
606
+ tds[3].textContent = r.saved === null ? '–' : fmtShort(r.saved);
607
+ tds[4].textContent = r.avgPct === null ? '–' : r.avgPct.toFixed(1) + '%';
608
+ tds[5].textContent = r.avgMs === null || r.avgMs === undefined ? '–' : fmtMs(r.avgMs);
609
+ tds[6].firstChild.firstChild.style.width = r.count ? Math.round(r.count / max * 100) + '%' : '0';
610
+ body.appendChild(tr);
611
+ });
612
+ document.getElementById('cmdTable').style.display = cmdRows.length ? '' : 'none';
613
+ document.getElementById('emptyCmd').classList.toggle('hidden', cmdRows.length > 0);
614
+ var tools = (DATA.byTool || []).length;
615
+ document.getElementById('toolsScope').textContent =
616
+ g.commands ? fmt(tools) + ' tools · ' + fmt(g.commands) + ' commands · ' + fmtShort(g.saved) + ' saved' : 'tool calls in sessions';
617
+ paintCmdPager(pages);
618
+ }
619
+ function paintCmdPager(pages) {
620
+ var total = cmdRows.length, start = total ? (cmdPage - 1) * cmdPer + 1 : 0;
621
+ document.getElementById('cmdRange').textContent =
622
+ 'Showing ' + start + '–' + Math.min(total, cmdPage * cmdPer) + ' of ' + total;
623
+ var wrap = document.getElementById('cmdPages');
624
+ wrap.innerHTML = '';
625
+ function btn(label, page, opts) {
626
+ var b = document.createElement('button');
627
+ b.type = 'button'; b.className = 'pgbtn mono' + (opts && opts.on ? ' on' : '');
628
+ b.textContent = label;
629
+ if (opts && opts.dim) b.setAttribute('aria-label', opts.dim);
630
+ if (opts && opts.off) b.disabled = true;
631
+ else b.addEventListener('click', function() { cmdPage = page; renderCmd(); });
632
+ wrap.appendChild(b);
633
+ return b;
634
+ }
635
+ btn('‹', cmdPage - 1, { dim: 'Previous page', off: cmdPage <= 1 });
636
+ var nums = [];
637
+ for (var p = 1; p <= pages; p++) {
638
+ if (p === 1 || p === pages || Math.abs(p - cmdPage) <= 1) nums.push(p);
639
+ else if (nums[nums.length - 1] !== '…') nums.push('…');
640
+ }
641
+ nums.forEach(function(p) {
642
+ if (p === '…') {
643
+ var s = document.createElement('span');
644
+ s.textContent = '…'; s.style.padding = '0 2px';
645
+ wrap.appendChild(s);
646
+ } else btn(String(p), p, { on: p === cmdPage });
647
+ });
648
+ btn('›', cmdPage + 1, { dim: 'Next page', off: cmdPage >= pages });
649
+ }
650
+ function bindCmdTable() {
651
+ if (bindCmdTable.done) return;
652
+ bindCmdTable.done = true;
653
+ Array.prototype.forEach.call(document.querySelectorAll('#tools .thsort'), function(btn) {
654
+ btn.addEventListener('click', function() {
655
+ var k = btn.dataset.sort;
656
+ if (cmdSort.key === k) cmdSort.dir = -cmdSort.dir;
657
+ else { cmdSort.key = k; cmdSort.dir = k === 'name' ? 1 : -1; }
658
+ cmdPage = 1;
659
+ renderCmd();
660
+ });
661
+ });
662
+ Array.prototype.forEach.call(document.querySelectorAll('#cmdPager [data-pp]'), function(btn) {
663
+ btn.addEventListener('click', function() {
664
+ Array.prototype.forEach.call(document.querySelectorAll('#cmdPager [data-pp]'), function(b) { b.classList.remove('on'); });
665
+ btn.classList.add('on');
666
+ cmdPer = Number(btn.dataset.pp);
667
+ cmdPage = 1;
668
+ renderCmd();
669
+ });
670
+ });
671
+ }
672
+
673
+ function fmtMs(ms) {
674
+ if (ms < 1000) return Math.round(ms) + 'ms';
675
+ return (ms / 1000).toFixed(1) + 's';
676
+ }
677
+
678
+ function renderStrip(t) {
679
+ var defs = [
680
+ ['input', t.input, 'arrow-down-to-line'],
681
+ ['output', t.output, 'arrow-up-from-line'],
682
+ ['cache read', t.cacheRead, 'hard-drive-download'],
683
+ ['cache write', t.cacheWrite, 'hard-drive-upload']
684
+ ].filter(function(b) { return b[1] > 0; });
685
+ var strip = document.getElementById('tokens');
686
+ strip.innerHTML = '';
687
+ defs.forEach(function(b, i) {
688
+ var cell = document.createElement('div');
689
+ cell.className = 'stripcell flex-1 px-4 py-4' + (i > 0 ? ' sm:border-l' : '');
690
+ cell.style.borderColor = 'var(--line)';
691
+ cell.innerHTML = '<div class="flex items-center gap-2 mono text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">' +
692
+ '<i data-lucide="' + b[2] + '" class="size-3.5"></i><span></span></div>' +
693
+ '<p class="mono font-bold text-3xl mt-1"></p>';
694
+ cell.querySelector('span').textContent = b[0];
695
+ cell.querySelector('p').textContent = fmt(b[1]);
696
+ strip.appendChild(cell);
697
+ });
698
+ }
699
+
700
+ // 30-day volume sparkline + blended-rate daily stats for the USD card.
701
+ // Daily cost is a blended estimate (total USD / total tokens x day
702
+ // tokens), always rendered with ~ and never mixed with measured totals.
703
+ function renderSpark(byDay, usd, savedUsd, total) {
704
+ var el = document.getElementById('usdSpark');
705
+ el.innerHTML = '';
706
+ var keys = Object.keys(byDay).sort().slice(-30);
707
+ // Zero state: the spark strip is a fixed h-16, so a placeholder inside it
708
+ // overflows onto the big number. Hide the number, strip, and stats, and
709
+ // mount one placeholder in the card body instead. Restored below on data.
710
+ var mid = el.parentElement || null, card = mid && mid.parentElement ? mid.parentElement : null;
711
+ var usdEl = document.getElementById('usd'), noteEl = document.getElementById('usdNote');
712
+ var ex = document.getElementById('emptyUsd');
713
+ if (!keys.length && card && mid) {
714
+ if (!ex) {
715
+ ex = document.createElement('div');
716
+ ex.id = 'emptyUsd'; ex.className = 'empty'; ex.style.marginTop = '16px';
717
+ ex.innerHTML = emptyState('trending-up', 'No cost data yet', 'Daily spend will spark here once sessions report tokens.');
718
+ card.insertBefore(ex, mid);
719
+ }
720
+ el.style.display = 'none'; mid.style.display = 'none';
721
+ if (usdEl) usdEl.style.display = 'none';
722
+ if (noteEl) noteEl.style.display = 'none';
723
+ } else {
724
+ if (ex && ex.parentElement) ex.parentElement.removeChild(ex);
725
+ el.style.display = '';
726
+ if (mid) mid.style.display = '';
727
+ if (usdEl) usdEl.style.display = '';
728
+ if (noteEl) noteEl.style.display = '';
729
+ }
730
+ var rate = total ? usd / total : 0;
731
+ var max = 1, vols = [];
732
+ keys.forEach(function(k) {
733
+ var v = dayTotal(byDay[k] || {});
734
+ vols.push({ k: k, v: v });
735
+ max = Math.max(max, v);
736
+ });
737
+ var top = vols.slice().sort(function(a, b) { return b.v - a.v; })[0] || { k: '', v: 0 };
738
+ vols.forEach(function(r) {
739
+ var s = document.createElement('span');
740
+ if (r.k === top.k && r.v > 0) s.className = 'top';
741
+ s.style.height = r.v ? Math.max(4, Math.round(r.v / max * 100)) + '%' : '0';
742
+ s.dataset.usd = (rate * r.v).toFixed(2);
743
+ s.dataset.tok = r.v;
744
+ s.dataset.day = r.k;
745
+ s.addEventListener('mouseenter', function(ev) {
746
+ showTip('<div class="tt">' + dateHead(s.dataset.day) + '</div><div class="tv">~' + fxMoney(Number(s.dataset.usd)) + '</div>' +
747
+ '<div class="tr"><span class="sw" style="background:var(--accent)"></span>' +
748
+ '<span class="tn">tokens</span><span class="tvr">' + fmt(Number(s.dataset.tok)) + '</span></div>',
749
+ ev.clientX, ev.clientY);
750
+ });
751
+ s.addEventListener('mousemove', function(ev) { moveTip(ev.clientX, ev.clientY); });
752
+ s.addEventListener('mouseleave', hideTip);
753
+ el.appendChild(s);
754
+ });
755
+ var days = keys.length || 1;
756
+ document.getElementById('usdPerDay').textContent = fxMoney(usd / days);
757
+ document.getElementById('usdTopDay').textContent = top.k ? top.k.slice(5) + ' ~' + fxMoney(rate * top.v) : '-';
758
+ document.getElementById('usdLeverage').textContent = usd ? (savedUsd / usd).toFixed(1) : '-';
759
+ }
760
+
761
+ function dayTotal(b) { return (b.input || 0) + (b.output || 0) + (b.cacheRead || 0) + (b.cacheWrite || 0); }
762
+
763
+ function render(d) {
764
+ DATA = d;
765
+ var t = d.tokens || { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
766
+ var total = t.input + t.output + t.cacheRead + t.cacheWrite;
767
+ var totalEl = document.getElementById('total');
768
+ if (hasGsap) {
769
+ var o = { v: 0 };
770
+ gsap.to(o, { v: total, duration: 1.2, ease: 'power3.out', onUpdate: function() { totalEl.textContent = fmt(o.v); } });
771
+ } else {
772
+ totalEl.textContent = fmt(total);
773
+ }
774
+ document.getElementById('costLabel').textContent = fx.cur.toLowerCase() + ' cost';
775
+ countUp(document.getElementById('usd'), d.usd || 0, true);
776
+ document.getElementById('usdNote').textContent = (d.priced ? 'per-model price table' : 'incl. default pricing') + (fx.live ? ' · fx live' : ' · fx snapshot');
777
+ var byDay = d.byDay || {};
778
+ renderSpark(byDay, d.usd || 0, d.savedUsd || 0, total);
779
+ document.getElementById('verChip').textContent = 'tersio v' + (d.version || '?');
780
+ var dp = d.paths || {};
781
+ [['pathLedger', dp.ledger], ['pathSessions', dp.sessions], ['pathRtk', dp.rtk]].forEach(function(pair) {
782
+ var pel = document.getElementById(pair[0]);
783
+ if (pel && pair[1]) { pel.textContent = pair[1]; pel.title = pair[1]; }
784
+ });
785
+ countUp(document.getElementById('saved'), d.savedUsd || 0, true);
786
+ document.getElementById('co2').textContent = '~' + (d.co2g || 0).toFixed(1) + 'g';
787
+ (function() {
788
+ var el = document.getElementById('co2');
789
+ var card = el ? el.closest('div.rounded-xl') : null;
790
+ if (!card || card.dataset.co2tip) return;
791
+ card.dataset.co2tip = '1';
792
+ card.addEventListener('mouseenter', function(ev) { showTip(tipCo2HTML(), ev.clientX, ev.clientY); });
793
+ card.addEventListener('mousemove', function(ev) { moveTip(ev.clientX, ev.clientY); });
794
+ card.addEventListener('mouseleave', hideTip);
795
+ })();
796
+ var share = total ? Math.round((t.cacheRead + t.cacheWrite) / total * 100) : 0;
797
+ document.getElementById('cacheShare').textContent = share + '%';
798
+ var shareOf = function(b) { var s = dayTotal(b); return s ? ((b.cacheRead || 0) + (b.cacheWrite || 0)) / s * 100 : 0; };
799
+ var keys = Object.keys(byDay).sort(), cPct = 0, pPct = 0, cn = 0, pn = 0;
800
+ keys.slice(-7).forEach(function(k) { cPct += shareOf(byDay[k]); cn++; });
801
+ keys.slice(-14, -7).forEach(function(k) { pPct += shareOf(byDay[k]); pn++; });
802
+ var pp = (cn ? cPct / cn : 0) - (pn ? pPct / pn : 0);
803
+ var cd = document.getElementById('cacheDelta');
804
+ cd.textContent = (pp >= 0 ? '▲ +' : '▼ ') + Math.abs(pp).toFixed(1) + 'pp vs prior 7d';
805
+ cd.className = 'pill mono ' + (Math.abs(pp) < 0.05 ? 'flat' : (pp > 0 ? 'good' : 'bad'));
806
+
807
+ (function() {
808
+ var el = document.getElementById('ticker');
809
+ if (!el) return;
810
+ var tops = topModels(DATA.byModel || {}, 5);
811
+ var parts = tops.map(function(m) {
812
+ var b = (DATA.byModel || {})[m];
813
+ var v = b.input + b.output + b.cacheRead + b.cacheWrite;
814
+ var nm = m.toUpperCase().replace(/-(FREE|CONTRIBUTOR.*|NEXT|LATEST)$/, '').replace(/[-.]?\d[\d.]*/, '').replace(/-V(?=-|$)/, '');
815
+ return nm + ' ' + fmtShort(v);
816
+ });
817
+ if (!parts.length) return;
818
+ var half = parts.join(' \u25c6 ');
819
+ el.textContent = half + ' \u25c6 ' + half;
820
+ })();
821
+ renderStrip(t);
822
+ bindGraphTabs();
823
+ bindCmdTable();
824
+ renderGraph(d.byDay || {});
825
+ renderModels();
826
+ renderRecent();
827
+ renderCmd();
828
+
829
+ if (window.lucide) lucide.createIcons();
830
+ if (hasGsap && !render.introDone) {
831
+ render.introDone = true;
832
+ gsap.from('.hero-in', { y: 26, opacity: 0, duration: 0.8, ease: 'power3.out', stagger: 0.08 });
833
+ }
834
+ observe();
835
+ }
836
+
837
+ function renderRecent() {
838
+ var body = document.getElementById('recent');
839
+ body.innerHTML = '';
840
+ var rows = (DATA.recent || []).slice(0, 13);
841
+ rows.forEach(function(r, i) {
842
+ var v = vendorOf(r.m);
843
+ var tr = document.createElement('tr');
844
+ tr.className = 'rrow' + (i < rows.length - 1 ? ' rowline' : '');
845
+ var dot = '<span style="display:inline-block;width:8px;height:8px;border-radius:99px;background:' + v.color + ';margin-right:8px"></span>';
846
+ tr.innerHTML = '<td class="py-2.5 pr-3 truncate" style="max-width: 180px"></td>' +
847
+ '<td class="text-right py-2.5 pr-3 whitespace-nowrap"></td>' +
848
+ '<td class="text-right py-2.5 whitespace-nowrap" style="color: var(--dim)"></td>';
849
+ var tds = tr.children;
850
+ tds[0].innerHTML = dot + '<span></span>';
851
+ tds[0].querySelector('span:last-child').textContent = r.m;
852
+ tds[0].title = r.m;
853
+ tds[1].innerHTML = '<span style="color:#fb923c"></span> <span style="color:var(--accent)"></span>';
854
+ tds[1].children[0].textContent = fmt(r.i) + '\u2191';
855
+ tds[1].children[1].textContent = fmt(r.o) + '\u2193';
856
+ tds[1].title = fmt(r.i) + ' in / ' + fmt(r.o) + ' out';
857
+ tds[2].textContent = relTime(r.t);
858
+ body.appendChild(tr);
859
+ });
860
+ document.getElementById('recentCount').textContent = rows.length ? rows.length + ' requests' : '';
861
+ document.getElementById('recentTable').style.display = rows.length ? '' : 'none';
862
+ document.getElementById('emptyRecent').classList.toggle('hidden', rows.length > 0);
863
+ }
864
+
865
+ var seen = new WeakSet();
866
+ function observe() {
867
+ var els = document.querySelectorAll('.rise');
868
+ if (!('IntersectionObserver' in window) || reduce) {
869
+ els.forEach(function(el) { el.classList.add('in'); });
870
+ return;
871
+ }
872
+ var io = new IntersectionObserver(function(entries) {
873
+ entries.forEach(function(e) {
874
+ if (e.isIntersecting && !seen.has(e.target)) {
875
+ seen.add(e.target); e.target.classList.add('in'); io.unobserve(e.target);
876
+ }
877
+ });
878
+ }, { threshold: 0.12 });
879
+ els.forEach(function(el) { if (!seen.has(el)) io.observe(el); });
880
+ }
881
+
882
+ function load() {
883
+ fetch('data.json').then(function(r) { return r.json(); }).then(render).catch(function() {
884
+ var cards = document.getElementById('modelCards');
885
+ if (cards && !cards.children.length) cards.innerHTML = '<div class="empty md:col-span-3">' + emptyState('cloud-off', 'Could not load data', 'Serve with tersio gain instead of opening this file directly.') + '</div>';
886
+ if (window.lucide) lucide.createIcons();
887
+ observe();
888
+ });
889
+ }
890
+ document.getElementById('reload').addEventListener('click', load);
891
+ (function() {
892
+ var dlg = document.getElementById('settings');
893
+ if (!dlg || typeof dlg.showModal !== 'function') return;
894
+ document.getElementById('settingsBtn').addEventListener('click', function() {
895
+ if (typeof dlg.showModal === 'function') dlg.showModal();
896
+ });
897
+ document.getElementById('settingsClose').addEventListener('click', function() { dlg.close(); });
898
+ dlg.addEventListener('click', function(ev) { if (ev.target === dlg) dlg.close(); });
899
+ })();
900
+ (function() {
901
+ var resetBtn = document.getElementById('reset');
902
+ if (!resetBtn) return;
903
+ if (window.location.protocol === 'file:') { resetBtn.style.display = 'none'; return; }
904
+ var label = document.getElementById('resetLabel');
905
+ resetBtn.addEventListener('click', function() {
906
+ if (resetBtn.dataset.armed) {
907
+ delete resetBtn.dataset.armed;
908
+ label.textContent = 'Clearing…';
909
+ fetch('reset', { method: 'POST' }).then(function(r) { return r.json(); }).then(function() {
910
+ label.textContent = 'Reset';
911
+ load();
912
+ }).catch(function() { label.textContent = 'Reset'; });
913
+ } else {
914
+ resetBtn.dataset.armed = '1';
915
+ label.textContent = 'Sure?';
916
+ setTimeout(function() { delete resetBtn.dataset.armed; if (label.textContent === 'Sure?') label.textContent = 'Reset'; }, 3000);
917
+ }
918
+ });
919
+ })();
920
+ load();
921
+ observe();
922
+ })();