@krtclcdy/tersio 2.7.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.
@@ -8,26 +8,24 @@
8
8
  <link rel="icon" type="image/png" href="brand.webp">
9
9
  <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
10
10
  <script src="https://unpkg.com/lucide@latest"></script>
11
+ <script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>
12
+ <script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/ScrollTrigger.min.js"></script>
13
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/outfit@5/index.css">
11
14
  <style>
12
15
  :root {
13
16
  --bg: #f4f4f5; --panel: #ffffff; --ink: #18181b; --dim: #52525b;
14
17
  --line: rgba(24, 24, 27, .12); --accent: #047857; --accent-soft: rgba(4, 120, 87, .12);
15
18
  --track: rgba(24, 24, 27, .08); --grid: rgba(4, 120, 87, .07);
16
- --cell-0: rgba(24, 24, 27, .06);
17
- --cell-1: rgba(4, 120, 87, .25); --cell-2: rgba(4, 120, 87, .5);
18
- --cell-3: rgba(4, 120, 87, .75); --cell-4: #047857;
19
19
  }
20
20
  @media (prefers-color-scheme: dark) {
21
21
  :root {
22
22
  --bg: #09090b; --panel: #101014; --ink: #f4f4f5; --dim: #a1a1aa;
23
23
  --line: rgba(255, 255, 255, .09); --accent: #34d399; --accent-soft: rgba(52, 211, 153, .1);
24
24
  --track: rgba(255, 255, 255, .07); --grid: rgba(52, 211, 153, .05);
25
- --cell-0: rgba(255, 255, 255, .06);
26
- --cell-1: rgba(52, 211, 153, .25); --cell-2: rgba(52, 211, 153, .5);
27
- --cell-3: rgba(52, 211, 153, .75); --cell-4: #34d399;
28
25
  }
29
26
  }
30
27
  body { background: var(--bg); color: var(--ink); }
28
+ .display { font-family: "Outfit", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
31
29
  .bg-blueprint {
32
30
  background-image: linear-gradient(var(--grid) 1px, transparent 1px),
33
31
  linear-gradient(90deg, var(--grid) 1px, transparent 1px);
@@ -36,27 +34,37 @@
36
34
  }
37
35
  .mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-variant-numeric: tabular-nums; }
38
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); }
39
41
  .bar-fill { background: var(--accent); border-radius: 999px; transform-origin: left; }
40
42
  .bar-track { background: var(--track); border-radius: 999px; }
41
- .bar-dim { background: var(--dim); opacity: .55; border-radius: 999px; transform-origin: bottom; }
42
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; }
43
44
  @keyframes ping { 75%, 100% { box-shadow: 0 0 0 9px transparent; } }
44
45
  .rise { opacity: 0; transform: translateY(26px); }
45
46
  .rise.in { opacity: 1; transform: none; transition: opacity .7s cubic-bezier(.16, 1, .3, 1), transform .7s cubic-bezier(.16, 1, .3, 1); }
46
- .grow { transform: scaleX(0); transition: transform 1.1s cubic-bezier(.16, 1, .3, 1); }
47
- .grow.in { transform: scaleX(1); }
48
- .growY { transform: scaleY(0); transition: transform .9s cubic-bezier(.16, 1, .3, 1); }
49
- .growY.in { transform: scaleY(1); }
50
47
  .tilt { transition: transform .25s cubic-bezier(.16, 1, .3, 1), background .25s; }
51
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; }
52
52
  .btn-push { transition: transform .12s, background .2s; }
53
53
  .btn-push:hover { background: var(--accent-soft); }
54
54
  .btn-push:active { transform: scale(.96); }
55
55
  a.social { color: var(--ink); }
56
56
  a.social:hover { color: var(--accent); }
57
- .cell { width: 11px; height: 11px; border-radius: 2.5px; background: var(--cell-0); }
58
- .cell.l1 { background: var(--cell-1); } .cell.l2 { background: var(--cell-2); }
59
- .cell.l3 { background: var(--cell-3); } .cell.l4 { background: var(--cell-4); }
57
+ .seg button { color: var(--dim); border-radius: 8px; transition: background .2s, color .2s; }
58
+ .seg button.on { background: var(--accent-soft); color: var(--ink); }
59
+ .seg button:not(.on):hover { color: var(--ink); }
60
+ .mbar { display: flex; flex-direction: column-reverse; border-radius: 6px 6px 2px 2px; overflow: hidden; min-height: 3px; background: var(--track); }
61
+ .mbar > span { display: block; width: 100%; }
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; }
60
68
  .dock {
61
69
  position: sticky; top: 12px; z-index: 50;
62
70
  background: color-mix(in srgb, var(--panel) 84%, transparent);
@@ -64,25 +72,29 @@
64
72
  border: 1px solid var(--line); border-radius: 20px;
65
73
  box-shadow: 0 12px 40px rgba(0, 0, 0, .25);
66
74
  }
67
- .dock a.docklink { transition: transform .22s cubic-bezier(.34, 1.56, .64, 1), background .2s; border-radius: 12px; }
68
- .dock a.docklink:hover { transform: scale(1.28) translateY(-3px); background: var(--accent-soft); }
69
- .dock a.docklink:active { transform: scale(1.05); }
70
75
  .dock img.brand { border-radius: 10px; }
71
- @media (prefers-reduced-motion: reduce) {
72
- .dock a.docklink { transition: none; }
73
- .dock a.docklink:hover { transform: none; }
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;
74
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); }
75
87
  @media (prefers-reduced-motion: reduce) {
76
88
  .rise { opacity: 1; transform: none; }
77
- .grow, .growY { transform: none; transition: none; }
78
89
  .live-dot { animation: none; }
79
- .tilt, .btn-push { transition: none; }
90
+ .tilt, .btn-push, .seg button { transition: none; }
80
91
  }
81
92
  </style>
82
93
  </head>
83
94
  <body class="min-h-[100dvh] antialiased">
84
95
  <div class="bg-blueprint pointer-events-none fixed inset-0" aria-hidden="true"></div>
85
96
 
97
+ <main class="overflow-x-hidden w-full max-w-full">
86
98
  <div class="relative mx-auto max-w-7xl px-4 sm:px-6 pb-16">
87
99
 
88
100
  <!-- Floating dock navbar -->
@@ -91,42 +103,82 @@
91
103
  <img src="brand.webp" alt="Tersio" width="32" height="32" class="brand size-8 rounded-full">
92
104
  <span class="leading-tight text-left">
93
105
  <span class="block font-bold tracking-tight text-sm">TERSIO GAIN</span>
94
- <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>
95
107
  </span>
96
108
  </a>
97
109
  <span class="mono hidden shrink-0 sm:flex items-center gap-2 text-xs px-2 ml-auto" style="color: var(--dim)">
98
110
  <span class="live-dot inline-block size-2 rounded-full"></span>
99
111
  <span id="clock">--:--</span>
100
112
  </span>
101
- <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)" aria-label="Reload data">
113
+ <span class="hidden sm:block w-px h-5 shrink-0" style="background: var(--line)" aria-hidden="true"></span>
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">
102
115
  <i data-lucide="refresh-cw" class="size-3.5"></i><span class="hidden md:inline">Reload</span>
103
116
  </button>
104
117
  </header>
105
118
 
106
- <!-- Hero: asymmetric 7/5 -->
107
- <section class="grid grid-cols-1 lg:grid-cols-12 gap-8 pt-10 pb-8 items-end">
108
- <div class="lg:col-span-7 rise">
109
- <p class="mono text-[11px] uppercase tracking-[0.22em] mb-3" style="color: var(--accent)">Live token feed</p>
110
- <p class="mono font-bold tracking-tighter leading-none text-7xl md:text-8xl" id="total">0</p>
111
- <p class="mt-3 text-base max-w-[52ch]" style="color: var(--dim)">session tokens parsed from OMP transcripts. Input, output, cache read, cache write, USD cost, CO2.</p>
119
+ <!-- Hero: centered total -->
120
+ <section class="mx-auto max-w-6xl text-center pt-14 pb-10">
121
+ <p class="hero-in mono text-[11px] uppercase tracking-[0.22em] mb-4" style="color: var(--accent)">Live token feed</p>
122
+ <p class="hero-in mono font-bold tracking-tighter leading-none text-6xl md:text-7xl" id="total">0</p>
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>
124
+ </section>
125
+
126
+ <!-- Savings bento: big USD card + nested right rail -->
127
+ <section class="grid grid-cols-12 grid-flow-dense gap-3" aria-label="Savings">
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>
112
145
  </div>
113
- <div class="lg:col-span-5 rise" style="transition-delay: .12s">
114
- <dl class="mono text-sm">
115
- <div class="flex justify-between gap-4 py-2 rowline"><dt style="color: var(--dim)">usd cost</dt><dd id="usd">-</dd></div>
116
- <div class="flex justify-between gap-4 py-2 rowline"><dt style="color: var(--dim)">co2</dt><dd id="co2">-</dd></div>
117
- <div class="flex justify-between gap-4 py-2"><dt style="color: var(--dim)">assistant msgs</dt><dd id="msgs">0</dd></div>
118
- </dl>
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>
119
171
  </div>
120
172
  </section>
121
173
 
122
- <!-- Token strip: plain stats, dividers, no cards -->
123
- <section class="rise grid grid-cols-2 sm:grid-cols-4 rounded-xl overflow-hidden" style="border: 1px solid var(--line); background: var(--panel)" id="tokens" aria-label="Tokens by bucket"></section>
174
+ <!-- Token strip: nonzero buckets only -->
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>
124
176
 
125
- <!-- Contribution graph, GitHub style -->
177
+ <!-- Activity: contribution heatmap, GitHub style -->
126
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">
127
179
  <div class="flex items-center gap-2 mb-1">
128
180
  <i data-lucide="calendar-days" class="size-4" style="color: var(--accent)"></i>
129
- <h2 class="font-bold tracking-tight text-xl">Activity</h2>
181
+ <h2 class="display font-bold tracking-tight text-xl">Activity</h2>
130
182
  <span class="mono text-xs ml-auto" style="color: var(--dim)" id="graphRange"></span>
131
183
  </div>
132
184
  <p class="mono text-xs mb-4" style="color: var(--dim)">daily session tokens / last 26 weeks / darker means more</p>
@@ -138,86 +190,110 @@
138
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>
139
191
  <span>more</span>
140
192
  </div>
193
+ <div class="heatmonth mono mt-2 flex" style="color: var(--dim)" id="graphMonths"></div>
141
194
  </section>
142
195
 
143
- <!-- Main: 8/4 split -->
144
- <main class="grid grid-cols-1 lg:grid-cols-12 gap-8 mt-8">
196
+ <!-- Top models: 3 cards -->
197
+ <section class="mt-8" aria-label="Top models">
198
+ <div class="flex items-center gap-2 mb-1">
199
+ <i data-lucide="cpu" class="size-4" style="color: var(--accent)"></i>
200
+ <h2 class="display font-bold tracking-tight text-xl">Top models</h2>
201
+ <span class="mono text-xs ml-auto" style="color: var(--dim)">usage across sessions</span>
202
+ </div>
203
+ <p class="mono text-xs mb-4" style="color: var(--dim)">week-over-week vs prior 7 days</p>
204
+ <div class="grid grid-cols-1 md:grid-cols-12 grid-flow-dense gap-3" id="modelCards"></div>
205
+ </section>
145
206
 
146
- <div class="lg:col-span-8 flex flex-col gap-8">
147
- <section id="daily" class="rise" style="scroll-margin-top: 90px" aria-label="Daily chart">
148
- <div class="flex items-center gap-2 mb-1">
149
- <i data-lucide="chart-column" class="size-4" style="color: var(--accent)"></i>
150
- <h2 class="font-bold tracking-tight text-xl">Daily input vs output</h2>
151
- </div>
152
- <p class="mono text-xs mb-4" style="color: var(--dim)">last 14 days / tall bar input, short bar output</p>
153
- <div class="rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel)">
154
- <div class="flex items-end gap-2 h-36" id="chart"></div>
155
- <div class="flex gap-2 mt-2 mono text-[10px]" style="color: var(--dim)" id="chartDays"></div>
156
- </div>
157
- </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>
212
+ </div>
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>
158
216
 
159
- <section id="commands" class="rise" style="scroll-margin-top: 90px" aria-label="Top commands">
160
- <div class="flex items-center gap-2 mb-1">
161
- <i data-lucide="terminal" class="size-4" style="color: var(--accent)"></i>
162
- <h2 class="font-bold tracking-tight text-xl">Top commands</h2>
163
- </div>
164
- <p class="mono text-xs mb-4" style="color: var(--dim)">ranked by ledger count / top 10</p>
165
- <ol id="top" class="rounded-xl overflow-hidden" style="border: 1px solid var(--line); background: var(--panel)"></ol>
166
- <p id="emptyTop" class="mono text-sm py-8 text-center hidden" style="color: var(--dim)">No ledger rows yet. Run tersio commands to record activity.</p>
167
- </section>
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>
168
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>
169
240
 
170
- <div class="lg:col-span-4 flex flex-col gap-8">
171
- <section id="savings" class="rise rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel); transition-delay: .1s; scroll-margin-top: 90px" aria-label="Savings">
172
- <div class="flex items-center gap-2 mb-3">
173
- <i data-lucide="gauge" class="size-4" style="color: var(--accent)"></i>
174
- <h2 class="font-bold tracking-tight text-xl">Savings</h2>
175
- </div>
176
- <ul class="mono text-[13px] leading-relaxed space-y-2">
177
- <li class="flex gap-2"><i data-lucide="circle-dollar-sign" class="size-4 shrink-0 mt-0.5" style="color: var(--accent)"></i><span id="usdLine">USD from per-model price table.</span></li>
178
- <li class="flex gap-2"><i data-lucide="leaf" class="size-4 shrink-0 mt-0.5" style="color: var(--accent)"></i><span id="co2Line">CO2 ~est. from output tokens.</span></li>
179
- <li class="flex gap-2"><i data-lucide="database" class="size-4 shrink-0 mt-0.5" style="color: var(--accent)"></i><span id="cacheLine">Cache share loads with session data.</span></li>
180
- </ul>
181
- </section>
182
-
183
- <section class="rise rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel); transition-delay: .18s" aria-label="Models">
184
- <div class="flex items-center gap-2 mb-3">
185
- <i data-lucide="cpu" class="size-4" style="color: var(--accent)"></i>
186
- <h2 class="font-bold tracking-tight text-xl">Models</h2>
187
- </div>
188
- <ol id="models" class="mono text-[13px] space-y-2" style="scroll-margin-top: 90px"></ol>
189
- </section>
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>
190
251
  </div>
191
- </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>
192
257
 
193
- <footer class="mono text-xs mt-12 pt-4 flex items-center gap-4" style="color: var(--dim); border-top: 1px solid var(--line)">
194
- <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>
195
265
  <span class="ml-auto flex items-center gap-1" style="color: var(--ink)">
196
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>
197
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>
198
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>
199
269
  </span>
200
- <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>
201
272
  </footer>
202
273
  </div>
274
+ </main>
203
275
 
204
276
  <script>
205
277
  (function () {
206
278
  var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
279
+ var hasGsap = !reduce && typeof window.gsap !== 'undefined';
280
+ var hasST = hasGsap && typeof window.ScrollTrigger !== 'undefined';
281
+ if (hasST) gsap.registerPlugin(ScrollTrigger);
207
282
 
208
283
  function fmt(n) { return Number(n).toLocaleString('en-US'); }
284
+ function fmtShort(n) {
285
+ if (n >= 1e12) return (n / 1e12).toFixed(1) + 'T';
286
+ if (n >= 1e9) return (n / 1e9).toFixed(1) + 'B';
287
+ if (n >= 1e6) return (n / 1e6).toFixed(1) + 'M';
288
+ if (n >= 1e3) return (n / 1e3).toFixed(1) + 'K';
289
+ return String(Math.round(n));
290
+ }
209
291
 
210
292
  function countUp(el, target, money) {
211
293
  function text(v) { return money ? '$' + v.toFixed(2) : fmt(Math.round(v)); }
212
- if (reduce || target <= 0) { el.textContent = text(target); return; }
213
- var t0 = null, dur = 900;
214
- function step(t) {
215
- if (!t0) t0 = t;
216
- var p = Math.min((t - t0) / dur, 1), e = 1 - Math.pow(1 - p, 3);
217
- el.textContent = text(target * e);
218
- if (p < 1) requestAnimationFrame(step);
219
- }
220
- requestAnimationFrame(step);
294
+ if (!hasGsap || target <= 0) { el.textContent = text(target); return; }
295
+ var o = { v: 0 };
296
+ gsap.to(o, { v: target, duration: 1.1, ease: 'power3.out', onUpdate: function () { el.textContent = text(o.v); } });
221
297
  }
222
298
 
223
299
  function tick() {
@@ -226,86 +302,315 @@
226
302
  }
227
303
  tick(); setInterval(tick, 1000);
228
304
 
305
+ var VENDORS = [
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'],
319
+ ];
320
+ function vendorOf(model) {
321
+ for (var i = 0; i < VENDORS.length; i++) {
322
+ if (VENDORS[i][0].test(model)) return { name: VENDORS[i][1], mark: VENDORS[i][2], color: VENDORS[i][3], icon: VENDORS[i][4] };
323
+ }
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>';
331
+ }
332
+ var PALETTE = ['#34d399', '#818cf8', '#22d3ee', '#fbbf24', '#f472b6', '#a78bfa', '#fb923c', '#2dd4bf'];
333
+
229
334
  function dayKey(d) {
230
335
  return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
231
336
  }
337
+ function modelTotal(byModel, m) {
338
+ var b = byModel[m] || { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
339
+ return b.input + b.output + b.cacheRead + b.cacheWrite;
340
+ }
341
+
342
+ var DATA = null, RANGE = 'daily', cardsShown = false;
343
+
344
+ function topModels(byModel, n) {
345
+ return Object.keys(byModel).sort(function (a, b) { return modelTotal(byModel, b) - modelTotal(byModel, a); }).slice(0, n);
346
+ }
347
+
348
+ function allDays() {
349
+ var set = {};
350
+ Object.keys(DATA.byDay || {}).forEach(function (d) { set[d] = 1; });
351
+ Object.keys(DATA.byDayModel || {}).forEach(function (d) { set[d] = 1; });
352
+ return Object.keys(set).sort();
353
+ }
232
354
 
233
355
  function renderGraph(byDay) {
234
356
  var g = document.getElementById('graph');
235
357
  g.innerHTML = '';
236
358
  var today = new Date(); today.setHours(0, 0, 0, 0);
237
- var start = new Date(today); start.setDate(start.getDate() - (26 * 7 - 1) - today.getDay());
359
+ var start = new Date(today); start.setDate(start.getDate() - (25 * 7) - today.getDay());
238
360
  var max = 1, sums = {};
239
361
  Object.keys(byDay).forEach(function (day) {
240
362
  var b = byDay[day];
241
363
  sums[day] = b.input + b.output + b.cacheRead + b.cacheWrite;
242
364
  if (sums[day] > max) max = sums[day];
243
365
  });
244
- var weeks = 0;
366
+ var months = document.getElementById('graphMonths');
367
+ months.innerHTML = '';
368
+ var lastMonth = '';
245
369
  for (var w = 0; w < 26; w++) {
246
370
  var col = document.createElement('div');
247
- col.className = 'flex flex-col gap-[3px]';
371
+ col.style.cssText = 'display:flex;flex-direction:column;gap:3px;';
372
+ var ml = document.createElement('span');
373
+ ml.className = 'heatmonth w-full truncate';
248
374
  for (var i = 0; i < 7; i++) {
249
- var dt = new Date(start); dt.setDate(dt.getDate() + weeks * 7 + i);
375
+ var dt = new Date(start); dt.setDate(dt.getDate() + w * 7 + i);
250
376
  var key = dayKey(dt), v = sums[key] || 0, lvl = 0;
251
377
  if (v > 0) lvl = Math.min(4, 1 + Math.floor((v / max) * 3.99));
252
378
  var cell = document.createElement('span');
253
379
  cell.className = 'cell' + (lvl ? ' l' + lvl : '');
254
380
  cell.title = key + ': ' + fmt(v) + ' tokens';
255
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
+ }
256
387
  }
257
- weeks++;
258
388
  g.appendChild(col);
389
+ months.appendChild(ml);
259
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'; });
260
395
  document.getElementById('graphRange').textContent =
261
396
  dayKey(start) + ' to ' + dayKey(today);
262
397
  }
263
398
 
264
- function renderChart(byDay) {
265
- var chart = document.getElementById('chart'), days = document.getElementById('chartDays');
266
- chart.innerHTML = ''; days.innerHTML = '';
267
- var list = [], today = new Date(); today.setHours(0, 0, 0, 0);
268
- for (var i = 13; i >= 0; i--) {
269
- var dt = new Date(today); dt.setDate(dt.getDate() - i);
270
- list.push({ key: dayKey(dt), label: String(dt.getDate()), b: byDay[dayKey(dt)] || { input: 0, output: 0 } });
399
+ // Buckets: [{ key, label, total, segs: [[model, v]] }]
400
+ function buckets() {
401
+ var days = allDays(), tops = topModels(DATA.byModel || {}, 8), out = [];
402
+ if (RANGE === 'daily') {
403
+ var last = days.slice(-60);
404
+ last.forEach(function (d) {
405
+ var per = (DATA.byDayModel || {})[d] || {}, segs = [], total = 0, other = 0;
406
+ tops.forEach(function (m) { var v = per[m] || 0; if (v) { segs.push([m, v]); total += v; } });
407
+ Object.keys(per).forEach(function (m) { if (tops.indexOf(m) < 0) other += per[m]; });
408
+ if (other) { segs.push(['other', other]); total += other; }
409
+ var dt = new Date(d + 'T12:00:00');
410
+ out.push({ key: d, label: isNaN(dt) ? '' : String(dt.getDate()), total: total, segs: segs });
411
+ });
412
+ document.getElementById('rangeNote').textContent = 'stacked by model / last 60 days';
413
+ } else if (RANGE === 'weekly') {
414
+ var weeks = {}, order = [];
415
+ days.forEach(function (d) {
416
+ var dt = new Date(d + 'T12:00:00'); if (isNaN(dt)) return;
417
+ var monday = new Date(dt); monday.setDate(dt.getDate() - ((dt.getDay() + 6) % 7));
418
+ var k = dayKey(monday);
419
+ if (!weeks[k]) { weeks[k] = {}; order.push(k); }
420
+ var per = (DATA.byDayModel || {})[d] || {};
421
+ Object.keys(per).forEach(function (m) { weeks[k][m] = (weeks[k][m] || 0) + per[m]; });
422
+ });
423
+ order.slice(-26).forEach(function (k) {
424
+ var per = weeks[k], segs = [], total = 0, other = 0;
425
+ tops.forEach(function (m) { var v = per[m] || 0; if (v) { segs.push([m, v]); total += v; } });
426
+ Object.keys(per).forEach(function (m) { if (tops.indexOf(m) < 0) other += per[m]; });
427
+ if (other) { segs.push(['other', other]); total += other; }
428
+ var dt = new Date(k + 'T12:00:00');
429
+ out.push({ key: k, label: isNaN(dt) ? '' : (dt.toLocaleString('en-US', { month: 'short' }) + ' ' + dt.getDate()), total: total, segs: segs });
430
+ });
431
+ document.getElementById('rangeNote').textContent = 'stacked by model / last 26 weeks';
432
+ } else {
433
+ var run = {}, cum = 0;
434
+ days.forEach(function (d) {
435
+ var per = (DATA.byDayModel || {})[d] || {}, segs = [], other = 0;
436
+ tops.forEach(function (m) {
437
+ var v = per[m] || 0;
438
+ run[m] = (run[m] || 0) + v;
439
+ if (run[m]) segs.push([m, run[m]]);
440
+ });
441
+ Object.keys(per).forEach(function (m) { if (tops.indexOf(m) < 0) { run[m] = (run[m] || 0) + per[m]; } });
442
+ other = Object.keys(run).reduce(function (a, m) { return tops.indexOf(m) < 0 ? a + run[m] : a; }, 0);
443
+ var tot = segs.reduce(function (a, s) { return a + s[1]; }, 0) + other;
444
+ if (other) segs.push(['other', other]);
445
+ cum = tot;
446
+ var dt = new Date(d + 'T12:00:00');
447
+ out.push({ key: d, label: isNaN(dt) ? '' : String(dt.getDate()), total: tot, segs: segs });
448
+ });
449
+ out = out.slice(-60);
450
+ document.getElementById('rangeNote').textContent = 'running total by model / all time';
271
451
  }
452
+ return { list: out, tops: tops };
453
+ }
454
+
455
+ function renderBars() {
456
+ var b = buckets(), wrap = document.getElementById('bars'), axis = document.getElementById('barDays');
457
+ wrap.innerHTML = ''; axis.innerHTML = '';
272
458
  var max = 1;
273
- list.forEach(function (d) { max = Math.max(max, d.b.input, d.b.output); });
274
- list.forEach(function (d, i) {
275
- var wrap = document.createElement('div');
276
- wrap.className = 'flex-1 flex items-end justify-center gap-1 h-full';
277
- wrap.title = d.key + ': in ' + fmt(d.b.input) + ' / out ' + fmt(d.b.output);
278
- var bi = document.createElement('div');
279
- bi.className = 'bar-fill growY w-3';
280
- bi.style.height = Math.max(2, Math.round(d.b.input / max * 100)) + '%';
281
- bi.style.transitionDelay = reduce ? '' : (i * 40) + 'ms';
282
- var bo = document.createElement('div');
283
- bo.className = 'bar-dim growY w-3';
284
- bo.style.height = Math.max(2, Math.round(d.b.output / max * 100)) + '%';
285
- bo.style.transitionDelay = reduce ? '' : (i * 40 + 60) + 'ms';
286
- wrap.appendChild(bi); wrap.appendChild(bo); chart.appendChild(wrap);
459
+ b.list.forEach(function (r) { max = Math.max(max, r.total); });
460
+ b.list.forEach(function (r) {
461
+ var col = document.createElement('div');
462
+ col.className = 'flex-1 flex items-end h-full';
463
+ col.title = r.key + ': ' + fmt(r.total) + ' tokens' +
464
+ (r.segs.length ? '\n' + r.segs.map(function (s) { return s[0] + ' ' + fmt(s[1]); }).join('\n') : '');
465
+ var bar = document.createElement('div');
466
+ bar.className = 'mbar w-full';
467
+ bar.style.height = Math.max(2, Math.round(r.total / max * 100)) + '%';
468
+ var order = r.segs.slice().sort(function (x, y) { return y[1] - x[1]; });
469
+ order.forEach(function (s) {
470
+ var seg = document.createElement('span');
471
+ var ci = s[0] === 'other' ? -1 : b.tops.indexOf(s[0]);
472
+ seg.style.background = ci < 0 ? 'var(--dim)' : PALETTE[ci % PALETTE.length];
473
+ seg.style.height = Math.max(1, Math.round(s[1] / r.total * 100)) + '%';
474
+ bar.appendChild(seg);
475
+ });
476
+ col.appendChild(bar); wrap.appendChild(col);
287
477
  var lb = document.createElement('span');
288
- lb.className = 'flex-1 text-center'; lb.textContent = d.label;
289
- days.appendChild(lb);
478
+ lb.className = 'flex-1 text-center truncate'; lb.textContent = r.label;
479
+ axis.appendChild(lb);
290
480
  });
481
+ var legend = document.getElementById('legend');
482
+ legend.innerHTML = '';
483
+ b.tops.slice(0, 8).forEach(function (m, i) {
484
+ var s = document.createElement('span');
485
+ s.className = 'flex items-center gap-1.5';
486
+ var dot = document.createElement('span');
487
+ dot.style.cssText = 'width:9px;height:9px;border-radius:3px;background:' + PALETTE[i % PALETTE.length] + ';display:inline-block';
488
+ s.appendChild(dot);
489
+ s.appendChild(document.createTextNode(m));
490
+ legend.appendChild(s);
491
+ });
492
+ if (hasGsap) {
493
+ gsap.from('#bars > div', { scaleY: 0.2, opacity: 0.2, transformOrigin: 'bottom', duration: 0.5, ease: 'power2.out', stagger: 0.008, overwrite: true });
494
+ if (hasST && !cardsShown) {
495
+ cardsShown = true;
496
+ gsap.from('#modelCards > div', {
497
+ scale: 0.92, opacity: 0.2, duration: 0.6, ease: 'power2.out', stagger: 0.08, overwrite: true,
498
+ scrollTrigger: { trigger: '#modelCards', start: 'top 88%' }
499
+ });
500
+ }
501
+ }
291
502
  }
292
503
 
293
- function render(d) {
294
- var t = d.tokens || { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
295
- var total = t.input + t.output + t.cacheRead + t.cacheWrite;
296
- document.getElementById('meta').textContent =
297
- fmt(total) + ' session tokens / ' + d.total + ' ledger rows';
298
- countUp(document.getElementById('total'), total);
299
- countUp(document.getElementById('usd'), d.usd || 0, true);
300
- document.getElementById('co2').textContent = '~' + (d.co2g || 0).toFixed(1) + 'g (est.)';
301
- document.getElementById('msgs').textContent = fmt(d.messages || 0);
504
+ function wow(model) {
505
+ var days = allDays().slice(-14), cur = 0, prev = 0;
506
+ days.forEach(function (d, i) {
507
+ var v = ((DATA.byDayModel || {})[d] || {})[model] || 0;
508
+ if (i < days.length - 7) prev += v; else cur += v;
509
+ });
510
+ if (!prev) return cur ? 'new' : '—';
511
+ var p = Math.round((cur - prev) / prev * 100);
512
+ return (p >= 0 ? '+' : '') + p + '%';
513
+ }
302
514
 
303
- var buckets = [['input', t.input, 'arrow-down-to-line'], ['output', t.output, 'arrow-up-from-line'], ['cache read', t.cacheRead, 'hard-drive-download'], ['cache write', t.cacheWrite, 'hard-drive-upload']];
515
+ function renderModels() {
516
+ var byModel = DATA.byModel || {}, tops = topModels(byModel, 8);
517
+ var cards = document.getElementById('modelCards');
518
+ cards.innerHTML = '';
519
+ tops.slice(0, 3).forEach(function (m, i) {
520
+ var v = vendorOf(m), d = wow(m);
521
+ var card = document.createElement('div');
522
+ card.className = 'modelcard md:col-span-4 rounded-xl p-5 overflow-hidden relative';
523
+ card.style.cssText = 'border: 1px solid var(--line); background: var(--panel)';
524
+ var ghost = document.createElement('span');
525
+ ghost.className = 'display pointer-events-none select-none absolute -right-2 -bottom-7 font-bold';
526
+ ghost.style.cssText = 'font-size: 120px; line-height: 1; opacity: .07';
527
+ ghost.textContent = v.mark;
528
+ var head = document.createElement('p');
529
+ head.className = 'mono text-xs mb-3'; head.style.color = 'var(--dim)';
530
+ head.textContent = '0' + (i + 1);
531
+ var row = document.createElement('div');
532
+ row.className = 'flex items-center gap-3';
533
+ var badgeWrap = document.createElement('span');
534
+ badgeWrap.innerHTML = badgeHTML(v, false);
535
+ var badge = badgeWrap.firstChild;
536
+ var mid = document.createElement('div'); mid.className = 'min-w-0 flex-1';
537
+ var nm = document.createElement('p'); nm.className = 'mono text-sm font-bold truncate'; nm.textContent = m;
538
+ var vn = document.createElement('p'); vn.className = 'mono text-xs truncate'; vn.style.color = 'var(--dim)'; vn.textContent = v.name;
539
+ mid.appendChild(nm); mid.appendChild(vn);
540
+ var tot = document.createElement('p'); tot.className = 'mono font-bold text-lg shrink-0'; tot.textContent = fmtShort(modelTotal(byModel, m));
541
+ row.appendChild(badge); row.appendChild(mid); row.appendChild(tot);
542
+ var delta = document.createElement('p');
543
+ delta.className = 'mono text-xs mt-3';
544
+ delta.style.color = d.charAt(0) === '-' ? '#f87171' : 'var(--accent)';
545
+ delta.textContent = d;
546
+ card.appendChild(ghost); card.appendChild(head); card.appendChild(row); card.appendChild(delta);
547
+ cards.appendChild(card);
548
+ });
549
+ var ol = document.getElementById('models');
550
+ ol.innerHTML = '';
551
+ var max = 1;
552
+ tops.forEach(function (m) { max = Math.max(max, modelTotal(byModel, m)); });
553
+ tops.forEach(function (m, i) {
554
+ var v = vendorOf(m);
555
+ var li = document.createElement('li');
556
+ li.className = 'tilt flex items-center gap-3 px-4 py-3' + (i < tops.length - 1 ? ' rowline' : '');
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;';
561
+ var mid = document.createElement('div'); mid.className = 'min-w-0 flex-1';
562
+ var nm = document.createElement('p'); nm.className = 'mono text-sm truncate'; nm.textContent = m;
563
+ var track = document.createElement('div'); track.className = 'bar-track mt-1.5 h-1.5 overflow-hidden';
564
+ var fill = document.createElement('div'); fill.className = 'bar-fill h-full';
565
+ fill.style.width = Math.max(2, Math.round(modelTotal(byModel, m) / max * 100)) + '%';
566
+ track.appendChild(fill); mid.appendChild(nm); mid.appendChild(track);
567
+ var n = document.createElement('span'); n.className = 'mono font-bold shrink-0'; n.textContent = fmt(modelTotal(byModel, m));
568
+ li.appendChild(badge); li.appendChild(mid); li.appendChild(n);
569
+ ol.appendChild(li);
570
+ });
571
+ if (!tops.length) ol.innerHTML = '<li class="mono text-sm px-4 py-6" style="color: var(--dim)">no session tokens yet</li>';
572
+ }
573
+
574
+ function renderTools() {
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);
596
+ });
597
+ document.getElementById('topTable').style.display = tools.length ? '' : 'none';
598
+ document.getElementById('emptyTop').classList.toggle('hidden', tools.length > 0);
599
+ }
600
+
601
+ function renderStrip(t) {
602
+ var defs = [
603
+ ['input', t.input, 'arrow-down-to-line'],
604
+ ['output', t.output, 'arrow-up-from-line'],
605
+ ['cache read', t.cacheRead, 'hard-drive-download'],
606
+ ['cache write', t.cacheWrite, 'hard-drive-upload']
607
+ ].filter(function (b) { return b[1] > 0; });
608
+ if (!defs.length) defs = [['input', 0, 'arrow-down-to-line'], ['output', 0, 'arrow-up-from-line']];
304
609
  var strip = document.getElementById('tokens');
305
610
  strip.innerHTML = '';
306
- buckets.forEach(function (b, i) {
611
+ defs.forEach(function (b, i) {
307
612
  var cell = document.createElement('div');
308
- cell.className = 'tilt px-4 py-4' + (i > 0 ? ' sm:border-l' : '');
613
+ cell.className = 'stripcell flex-1 px-4 py-4' + (i > 0 ? ' sm:border-l' : '');
309
614
  cell.style.borderColor = 'var(--line)';
310
615
  cell.innerHTML = '<div class="flex items-center gap-2 mono text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">' +
311
616
  '<i data-lucide="' + b[2] + '" class="size-3.5"></i><span></span></div>' +
@@ -314,63 +619,126 @@
314
619
  cell.querySelector('p').textContent = fmt(b[1]);
315
620
  strip.appendChild(cell);
316
621
  });
622
+ }
317
623
 
318
- renderGraph(d.byDay || {});
319
- renderChart(d.byDay || {});
320
-
321
- var cacheShare = total ? Math.round((t.cacheRead + t.cacheWrite) / total * 100) : 0;
322
- document.getElementById('usdLine').textContent =
323
- '$' + (d.usd || 0).toFixed(2) + (d.priced ? ' measured (price table).' : ' incl. default pricing.');
324
- document.getElementById('co2Line').textContent =
325
- '~' + (d.co2g || 0).toFixed(1) + 'g CO2 est. from output tokens.';
326
- document.getElementById('cacheLine').textContent =
327
- cacheShare + '% of tokens served from cache.';
328
-
329
- var models = document.getElementById('models');
330
- models.innerHTML = '';
331
- Object.entries(d.byModel || {}).sort(function (a, b) {
332
- return (b[1].input + b[1].output) - (a[1].input + a[1].output);
333
- }).slice(0, 5).forEach(function (row) {
334
- var li = document.createElement('li');
335
- li.className = 'flex justify-between gap-2';
336
- var name = document.createElement('span'); name.className = 'truncate'; name.textContent = row[0];
337
- var n = document.createElement('span'); n.textContent = fmt(row[1].input + row[1].output);
338
- li.appendChild(name); li.appendChild(n); models.appendChild(li);
624
+ function money(v) { return '$' + v.toFixed(2); }
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);
339
639
  });
340
- if (!models.children.length) {
341
- models.innerHTML = '<li style="color: var(--dim)">no session tokens yet</li>';
342
- }
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
+ }
343
653
 
344
- var ol = document.getElementById('top');
345
- ol.innerHTML = '';
346
- var max = 1;
347
- (d.byDetail || []).forEach(function (row) { if (row[1] > max) max = row[1]; });
348
- (d.byDetail || []).forEach(function (row, i) {
349
- var li = document.createElement('li');
350
- li.className = 'tilt flex items-center gap-3 px-4 py-3' + (i < d.byDetail.length - 1 ? ' rowline' : '');
351
- var rank = document.createElement('span');
352
- rank.className = 'mono text-xs w-6 shrink-0'; rank.style.color = 'var(--dim)';
353
- rank.textContent = String(i + 1).padStart(2, '0');
354
- var mid = document.createElement('div'); mid.className = 'min-w-0 flex-1';
355
- var name = document.createElement('p'); name.className = 'mono text-sm truncate'; name.textContent = row[0];
356
- var track = document.createElement('div'); track.className = 'bar-track mt-1.5 h-1.5 overflow-hidden';
357
- var fill = document.createElement('div'); fill.className = 'bar-fill grow h-full';
358
- fill.style.width = Math.max(2, Math.round(row[1] / max * 100)) + '%';
359
- track.appendChild(fill); mid.appendChild(name); mid.appendChild(track);
360
- var n = document.createElement('span'); n.className = 'mono font-bold text-lg shrink-0'; n.textContent = String(row[1]);
361
- li.appendChild(rank); li.appendChild(mid); li.appendChild(n);
362
- ol.appendChild(li);
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
+
677
+ function render(d) {
678
+ DATA = d;
679
+ var t = d.tokens || { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
680
+ var total = t.input + t.output + t.cacheRead + t.cacheWrite;
681
+ var sub = document.getElementById('herosub');
682
+ sub.innerHTML = '';
683
+ ('input ' + fmt(t.input) + ' · output ' + fmt(t.output) + ' · cache read ' + fmt(t.cacheRead) +
684
+ (t.cacheWrite ? ' · cache write ' + fmt(t.cacheWrite) : '') + ' · ' + money(d.usd || 0)).split(' ').forEach(function (w) {
685
+ var s = document.createElement('span'); s.textContent = w + ' ';
686
+ sub.appendChild(s);
363
687
  });
364
- document.getElementById('emptyTop').classList.toggle('hidden', (d.total || 0) > 0);
365
- ol.classList.toggle('hidden', (d.total || 0) === 0);
688
+ var totalEl = document.getElementById('total');
689
+ if (hasGsap) {
690
+ var o = { v: 0 };
691
+ gsap.to(o, { v: total, duration: 1.2, ease: 'power3.out', onUpdate: function () { totalEl.textContent = fmt(o.v); } });
692
+ } else {
693
+ totalEl.textContent = fmt(total);
694
+ }
695
+ countUp(document.getElementById('usd'), d.usd || 0, true);
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);
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);
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);
708
+ var share = total ? Math.round((t.cacheRead + t.cacheWrite) / total * 100) : 0;
709
+ document.getElementById('cacheShare').textContent = share + '%';
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'));
718
+
719
+ renderStrip(t);
720
+ renderGraph(d.byDay || {});
721
+ renderBars();
722
+ renderModels();
723
+ renderTools();
366
724
 
367
725
  if (window.lucide) lucide.createIcons();
726
+ if (hasGsap) {
727
+ gsap.from('.hero-in', { y: 26, opacity: 0, duration: 0.8, ease: 'power3.out', stagger: 0.08 });
728
+ // Scrub: hero sub words fade in sequence on scroll.
729
+ if (hasST) {
730
+ gsap.fromTo('#herosub span', { opacity: 0.15 }, {
731
+ opacity: 1, ease: 'none', stagger: 0.05,
732
+ scrollTrigger: { trigger: '#herosub', start: 'top 92%', end: 'top 55%', scrub: true }
733
+ });
734
+ }
735
+ }
368
736
  observe();
369
737
  }
370
738
 
371
739
  var seen = new WeakSet();
372
740
  function observe() {
373
- var els = document.querySelectorAll('.rise, .grow, .growY');
741
+ var els = document.querySelectorAll('.rise');
374
742
  if (!('IntersectionObserver' in window) || reduce) {
375
743
  els.forEach(function (el) { el.classList.add('in'); });
376
744
  return;
@@ -385,11 +753,19 @@
385
753
  els.forEach(function (el) { if (!seen.has(el)) io.observe(el); });
386
754
  }
387
755
 
756
+ document.querySelectorAll('.seg button').forEach(function (btn) {
757
+ btn.addEventListener('click', function () {
758
+ document.querySelectorAll('.seg button').forEach(function (b) { b.classList.remove('on'); });
759
+ btn.classList.add('on');
760
+ RANGE = btn.getAttribute('data-range');
761
+ if (DATA) renderBars();
762
+ });
763
+ });
764
+
388
765
  function load() {
389
- document.getElementById('meta').textContent = 'connecting to ledger';
390
766
  fetch('data.json').then(function (r) { return r.json(); }).then(render).catch(function () {
391
- document.getElementById('meta').textContent = 'No live data. Serve with: tersio dashboard';
392
767
  document.getElementById('rawlink').style.display = 'none';
768
+ document.getElementById('topTable').style.display = 'none';
393
769
  document.getElementById('emptyTop').classList.remove('hidden');
394
770
  if (window.lucide) lucide.createIcons();
395
771
  observe();