@krtclcdy/tersio 2.6.0 → 2.8.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.
- package/README.md +12 -35
- package/cli/common.js +4 -4
- package/cli/dashboard.html +387 -211
- package/cli/dashboard.js +3 -3
- package/cli/doctor.js +5 -1
- package/cli/install.js +110 -12
- package/cli/interactive.js +13 -2
- package/cli/uninstall.js +1 -0
- package/cli/usage.js +15 -3
- package/extensions/shared/usage-ledger.js +35 -1
- package/extensions/tersio-commands/index.js +25 -2
- package/package.json +1 -1
- package/tersio.js +6 -6
package/cli/dashboard.html
CHANGED
|
@@ -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);
|
|
@@ -38,15 +36,10 @@
|
|
|
38
36
|
.rowline { border-bottom: 1px solid var(--line); }
|
|
39
37
|
.bar-fill { background: var(--accent); border-radius: 999px; transform-origin: left; }
|
|
40
38
|
.bar-track { background: var(--track); border-radius: 999px; }
|
|
41
|
-
.bar-dim { background: var(--dim); opacity: .55; border-radius: 999px; transform-origin: bottom; }
|
|
42
39
|
.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
40
|
@keyframes ping { 75%, 100% { box-shadow: 0 0 0 9px transparent; } }
|
|
44
41
|
.rise { opacity: 0; transform: translateY(26px); }
|
|
45
42
|
.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
43
|
.tilt { transition: transform .25s cubic-bezier(.16, 1, .3, 1), background .25s; }
|
|
51
44
|
.tilt:hover { transform: translateX(6px); background: var(--accent-soft); }
|
|
52
45
|
.btn-push { transition: transform .12s, background .2s; }
|
|
@@ -54,9 +47,12 @@
|
|
|
54
47
|
.btn-push:active { transform: scale(.96); }
|
|
55
48
|
a.social { color: var(--ink); }
|
|
56
49
|
a.social:hover { color: var(--accent); }
|
|
57
|
-
.
|
|
58
|
-
.
|
|
59
|
-
.
|
|
50
|
+
.seg button { color: var(--dim); border-radius: 8px; transition: background .2s, color .2s; }
|
|
51
|
+
.seg button.on { background: var(--accent-soft); color: var(--ink); }
|
|
52
|
+
.seg button:not(.on):hover { color: var(--ink); }
|
|
53
|
+
.mbar { display: flex; flex-direction: column-reverse; border-radius: 6px 6px 2px 2px; overflow: hidden; min-height: 3px; background: var(--track); }
|
|
54
|
+
.mbar > span { display: block; width: 100%; }
|
|
55
|
+
.monogram { width: 34px; height: 34px; border-radius: 10px; display: grid; place-items: center; font-weight: 700; font-size: 13px; color: #fff; flex-shrink: 0; }
|
|
60
56
|
.dock {
|
|
61
57
|
position: sticky; top: 12px; z-index: 50;
|
|
62
58
|
background: color-mix(in srgb, var(--panel) 84%, transparent);
|
|
@@ -64,25 +60,18 @@
|
|
|
64
60
|
border: 1px solid var(--line); border-radius: 20px;
|
|
65
61
|
box-shadow: 0 12px 40px rgba(0, 0, 0, .25);
|
|
66
62
|
}
|
|
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
63
|
.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; }
|
|
74
|
-
}
|
|
75
64
|
@media (prefers-reduced-motion: reduce) {
|
|
76
65
|
.rise { opacity: 1; transform: none; }
|
|
77
|
-
.grow, .growY { transform: none; transition: none; }
|
|
78
66
|
.live-dot { animation: none; }
|
|
79
|
-
.tilt, .btn-push { transition: none; }
|
|
67
|
+
.tilt, .btn-push, .seg button { transition: none; }
|
|
80
68
|
}
|
|
81
69
|
</style>
|
|
82
70
|
</head>
|
|
83
71
|
<body class="min-h-[100dvh] antialiased">
|
|
84
72
|
<div class="bg-blueprint pointer-events-none fixed inset-0" aria-hidden="true"></div>
|
|
85
73
|
|
|
74
|
+
<main class="overflow-x-hidden w-full max-w-full">
|
|
86
75
|
<div class="relative mx-auto max-w-7xl px-4 sm:px-6 pb-16">
|
|
87
76
|
|
|
88
77
|
<!-- Floating dock navbar -->
|
|
@@ -98,94 +87,99 @@
|
|
|
98
87
|
<span class="live-dot inline-block size-2 rounded-full"></span>
|
|
99
88
|
<span id="clock">--:--</span>
|
|
100
89
|
</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">
|
|
90
|
+
<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
91
|
<i data-lucide="refresh-cw" class="size-3.5"></i><span class="hidden md:inline">Reload</span>
|
|
103
92
|
</button>
|
|
104
93
|
</header>
|
|
105
94
|
|
|
106
|
-
<!-- Hero:
|
|
107
|
-
<section class="
|
|
108
|
-
<
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
95
|
+
<!-- Hero: centered total -->
|
|
96
|
+
<section class="mx-auto max-w-6xl text-center pt-14 pb-10">
|
|
97
|
+
<p class="hero-in mono text-[11px] uppercase tracking-[0.22em] mb-4" style="color: var(--accent)">Live token feed</p>
|
|
98
|
+
<p class="hero-in mono font-bold tracking-tighter leading-none text-6xl md:text-7xl" id="total">0</p>
|
|
99
|
+
<p class="hero-in mono mt-4 text-sm max-w-[62ch] mx-auto" style="color: var(--dim)" id="herosub">session tokens parsed from OMP transcripts. Input, output, cache read, cache write, USD cost, CO2.</p>
|
|
100
|
+
</section>
|
|
101
|
+
|
|
102
|
+
<!-- Savings bento: 3+3+2+2+2 -->
|
|
103
|
+
<section class="grid grid-cols-12 grid-flow-dense gap-3" aria-label="Savings">
|
|
104
|
+
<div class="rise col-span-6 md:col-span-3 rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel)">
|
|
105
|
+
<p class="mono text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">usd cost</p>
|
|
106
|
+
<p class="mono font-bold text-3xl mt-1" id="usd">-</p>
|
|
107
|
+
<p class="mono text-[11px] mt-1" style="color: var(--dim)" id="usdNote">per-model price table</p>
|
|
108
|
+
</div>
|
|
109
|
+
<div class="rise col-span-6 md:col-span-3 rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel)">
|
|
110
|
+
<p class="mono text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">saved by cache</p>
|
|
111
|
+
<p class="mono font-bold text-3xl mt-1" style="color: var(--accent)" id="saved">-</p>
|
|
112
|
+
<p class="mono text-[11px] mt-1" style="color: var(--dim)">est. vs full input price</p>
|
|
113
|
+
</div>
|
|
114
|
+
<div class="rise col-span-4 md:col-span-2 rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel)">
|
|
115
|
+
<p class="mono text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">co2</p>
|
|
116
|
+
<p class="mono font-bold text-3xl mt-1" id="co2">-</p>
|
|
117
|
+
<p class="mono text-[11px] mt-1" style="color: var(--dim)">est. from output</p>
|
|
112
118
|
</div>
|
|
113
|
-
<div class="
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
+
<div class="rise col-span-4 md:col-span-2 rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel)">
|
|
120
|
+
<p class="mono text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">cache share</p>
|
|
121
|
+
<p class="mono font-bold text-3xl mt-1" id="cacheShare">-</p>
|
|
122
|
+
<p class="mono text-[11px] mt-1" style="color: var(--dim)">of tokens cached</p>
|
|
123
|
+
</div>
|
|
124
|
+
<div class="rise col-span-4 md:col-span-2 rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel)">
|
|
125
|
+
<p class="mono text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">assistant msgs</p>
|
|
126
|
+
<p class="mono font-bold text-3xl mt-1" id="msgs">0</p>
|
|
127
|
+
<p class="mono text-[11px] mt-1" style="color: var(--dim)" id="msgNote">sessions parsed</p>
|
|
119
128
|
</div>
|
|
120
129
|
</section>
|
|
121
130
|
|
|
122
|
-
<!-- Token strip:
|
|
123
|
-
<section class="rise
|
|
131
|
+
<!-- Token strip: nonzero buckets only -->
|
|
132
|
+
<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
133
|
|
|
125
|
-
<!--
|
|
126
|
-
<section id="activity" class="rise mt-8 rounded-xl p-5
|
|
127
|
-
<div class="flex items-center gap-
|
|
128
|
-
<i data-lucide="
|
|
129
|
-
<h2 class="font-bold tracking-tight text-xl">
|
|
130
|
-
<
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
</div>
|
|
136
|
-
<div class="mono text-[11px] mt-3 flex items-center gap-1.5" style="color: var(--dim)">
|
|
137
|
-
<span>less</span>
|
|
138
|
-
<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
|
-
<span>more</span>
|
|
134
|
+
<!-- Token activity: stacked per-model bars + range toggle -->
|
|
135
|
+
<section id="activity" class="rise mt-8 rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel); scroll-margin-top: 90px" aria-label="Token activity">
|
|
136
|
+
<div class="flex flex-wrap items-center gap-3 mb-1">
|
|
137
|
+
<i data-lucide="chart-column" class="size-4" style="color: var(--accent)"></i>
|
|
138
|
+
<h2 class="display font-bold tracking-tight text-xl">Token activity</h2>
|
|
139
|
+
<div class="seg mono text-xs flex items-center gap-1 ml-auto p-1 rounded-[10px]" style="border: 1px solid var(--line)" role="tablist" aria-label="Range">
|
|
140
|
+
<button data-range="daily" class="on px-3 py-1.5" role="tab">Daily</button>
|
|
141
|
+
<button data-range="weekly" class="px-3 py-1.5" role="tab">Weekly</button>
|
|
142
|
+
<button data-range="cumulative" class="px-3 py-1.5" role="tab">Cumulative</button>
|
|
143
|
+
</div>
|
|
140
144
|
</div>
|
|
145
|
+
<p class="mono text-xs mb-4" style="color: var(--dim)" id="rangeNote">stacked by model / last 60 days</p>
|
|
146
|
+
<div class="flex items-end gap-[3px] h-56" id="bars"></div>
|
|
147
|
+
<div class="flex gap-[3px] mt-2 mono text-[10px]" style="color: var(--dim)" id="barDays"></div>
|
|
148
|
+
<div class="flex flex-wrap gap-x-4 gap-y-1.5 mt-4 mono text-[11px]" style="color: var(--dim)" id="legend"></div>
|
|
141
149
|
</section>
|
|
142
150
|
|
|
143
|
-
<!--
|
|
144
|
-
<
|
|
151
|
+
<!-- Top models: 3 cards -->
|
|
152
|
+
<section class="mt-8" aria-label="Top models">
|
|
153
|
+
<div class="flex items-center gap-2 mb-1">
|
|
154
|
+
<i data-lucide="cpu" class="size-4" style="color: var(--accent)"></i>
|
|
155
|
+
<h2 class="display font-bold tracking-tight text-xl">Top models</h2>
|
|
156
|
+
<span class="mono text-xs ml-auto" style="color: var(--dim)">usage across sessions</span>
|
|
157
|
+
</div>
|
|
158
|
+
<p class="mono text-xs mb-4" style="color: var(--dim)">week-over-week vs prior 7 days</p>
|
|
159
|
+
<div class="grid grid-cols-1 md:grid-cols-12 grid-flow-dense gap-3" id="modelCards"></div>
|
|
160
|
+
</section>
|
|
145
161
|
|
|
146
|
-
|
|
147
|
-
|
|
162
|
+
<!-- Models list + top tools: 8 + 4 -->
|
|
163
|
+
<main class="grid grid-cols-1 lg:grid-cols-12 grid-flow-dense gap-8 mt-8">
|
|
164
|
+
<div class="lg:col-span-8">
|
|
165
|
+
<section class="rise" aria-label="All models">
|
|
148
166
|
<div class="flex items-center gap-2 mb-1">
|
|
149
|
-
<i data-lucide="
|
|
150
|
-
<h2 class="font-bold tracking-tight text-xl">
|
|
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>
|
|
167
|
+
<i data-lucide="layers" class="size-4" style="color: var(--accent)"></i>
|
|
168
|
+
<h2 class="display font-bold tracking-tight text-xl">Models</h2>
|
|
156
169
|
</div>
|
|
170
|
+
<p class="mono text-xs mb-4" style="color: var(--dim)">ranked by tokens / top 8</p>
|
|
171
|
+
<ol id="models" class="rounded-xl overflow-hidden" style="border: 1px solid var(--line); background: var(--panel)"></ol>
|
|
157
172
|
</section>
|
|
158
|
-
|
|
159
|
-
|
|
173
|
+
</div>
|
|
174
|
+
<div class="lg:col-span-4">
|
|
175
|
+
<section id="tools" class="rise rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel); scroll-margin-top: 90px" aria-label="Top tools">
|
|
160
176
|
<div class="flex items-center gap-2 mb-1">
|
|
161
177
|
<i data-lucide="terminal" class="size-4" style="color: var(--accent)"></i>
|
|
162
|
-
<h2 class="font-bold tracking-tight text-xl">Top
|
|
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>
|
|
168
|
-
</div>
|
|
169
|
-
|
|
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>
|
|
178
|
+
<h2 class="display font-bold tracking-tight text-xl">Top tools</h2>
|
|
187
179
|
</div>
|
|
188
|
-
<
|
|
180
|
+
<p class="mono text-xs mb-4" style="color: var(--dim)">tool calls in sessions</p>
|
|
181
|
+
<ol id="top" class="mono text-[13px] space-y-3"></ol>
|
|
182
|
+
<p id="emptyTop" class="mono text-sm py-6 text-center hidden" style="color: var(--dim)">No tool calls yet.</p>
|
|
189
183
|
</section>
|
|
190
184
|
</div>
|
|
191
185
|
</main>
|
|
@@ -200,24 +194,29 @@
|
|
|
200
194
|
<a id="rawlink" href="data.json" class="underline underline-offset-2">raw data.json</a>
|
|
201
195
|
</footer>
|
|
202
196
|
</div>
|
|
197
|
+
</main>
|
|
203
198
|
|
|
204
199
|
<script>
|
|
205
200
|
(function () {
|
|
206
201
|
var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
202
|
+
var hasGsap = !reduce && typeof window.gsap !== 'undefined';
|
|
203
|
+
var hasST = hasGsap && typeof window.ScrollTrigger !== 'undefined';
|
|
204
|
+
if (hasST) gsap.registerPlugin(ScrollTrigger);
|
|
207
205
|
|
|
208
206
|
function fmt(n) { return Number(n).toLocaleString('en-US'); }
|
|
207
|
+
function fmtShort(n) {
|
|
208
|
+
if (n >= 1e12) return (n / 1e12).toFixed(1) + 'T';
|
|
209
|
+
if (n >= 1e9) return (n / 1e9).toFixed(1) + 'B';
|
|
210
|
+
if (n >= 1e6) return (n / 1e6).toFixed(1) + 'M';
|
|
211
|
+
if (n >= 1e3) return (n / 1e3).toFixed(1) + 'K';
|
|
212
|
+
return String(Math.round(n));
|
|
213
|
+
}
|
|
209
214
|
|
|
210
215
|
function countUp(el, target, money) {
|
|
211
216
|
function text(v) { return money ? '$' + v.toFixed(2) : fmt(Math.round(v)); }
|
|
212
|
-
if (
|
|
213
|
-
var
|
|
214
|
-
function
|
|
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);
|
|
217
|
+
if (!hasGsap || target <= 0) { el.textContent = text(target); return; }
|
|
218
|
+
var o = { v: 0 };
|
|
219
|
+
gsap.to(o, { v: target, duration: 1.1, ease: 'power3.out', onUpdate: function () { el.textContent = text(o.v); } });
|
|
221
220
|
}
|
|
222
221
|
|
|
223
222
|
function tick() {
|
|
@@ -226,86 +225,257 @@
|
|
|
226
225
|
}
|
|
227
226
|
tick(); setInterval(tick, 1000);
|
|
228
227
|
|
|
228
|
+
var VENDORS = [
|
|
229
|
+
[/glm|z-ai|zhipu/i, 'Zhipu', 'Z', '#3b82f6'],
|
|
230
|
+
[/muse/i, 'Meta', 'M', '#0082fb'],
|
|
231
|
+
[/qwen|qwq/i, 'Alibaba', 'Q', '#7c3aed'],
|
|
232
|
+
[/deepseek/i, 'DeepSeek', 'D', '#4d6bfe'],
|
|
233
|
+
[/mimo/i, 'Xiaomi', 'Mi', '#ff6900'],
|
|
234
|
+
[/kimi|moonshot/i, 'Moonshot', 'K', '#a855f7'],
|
|
235
|
+
[/minimax/i, 'MiniMax', 'M', '#e11d48'],
|
|
236
|
+
[/nemotron|nvidia/i, 'NVIDIA', 'N', '#76b900'],
|
|
237
|
+
[/gpt|openai|codex/i, 'OpenAI', 'O', '#10a37f'],
|
|
238
|
+
[/claude|anthropic/i, 'Anthropic', 'A', '#d97757'],
|
|
239
|
+
[/gemini|google/i, 'Google', 'G', '#4285f4'],
|
|
240
|
+
[/grok|xai/i, 'xAI', 'X', '#71717a']
|
|
241
|
+
];
|
|
242
|
+
function vendorOf(model) {
|
|
243
|
+
for (var i = 0; i < VENDORS.length; i++) {
|
|
244
|
+
if (VENDORS[i][0].test(model)) return { name: VENDORS[i][1], mark: VENDORS[i][2], color: VENDORS[i][3] };
|
|
245
|
+
}
|
|
246
|
+
return { name: 'Other', mark: (model || '?').charAt(0).toUpperCase(), color: '#71717a' };
|
|
247
|
+
}
|
|
248
|
+
var PALETTE = ['#34d399', '#818cf8', '#22d3ee', '#fbbf24', '#f472b6', '#a78bfa', '#fb923c', '#2dd4bf'];
|
|
249
|
+
|
|
229
250
|
function dayKey(d) {
|
|
230
251
|
return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
|
|
231
252
|
}
|
|
253
|
+
function modelTotal(byModel, m) {
|
|
254
|
+
var b = byModel[m] || { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
255
|
+
return b.input + b.output + b.cacheRead + b.cacheWrite;
|
|
256
|
+
}
|
|
232
257
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
258
|
+
var DATA = null, RANGE = 'daily';
|
|
259
|
+
|
|
260
|
+
function topModels(byModel, n) {
|
|
261
|
+
return Object.keys(byModel).sort(function (a, b) { return modelTotal(byModel, b) - modelTotal(byModel, a); }).slice(0, n);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function allDays() {
|
|
265
|
+
var set = {};
|
|
266
|
+
Object.keys(DATA.byDay || {}).forEach(function (d) { set[d] = 1; });
|
|
267
|
+
Object.keys(DATA.byDayModel || {}).forEach(function (d) { set[d] = 1; });
|
|
268
|
+
return Object.keys(set).sort();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Buckets: [{ key, label, total, segs: [[model, v]] }]
|
|
272
|
+
function buckets() {
|
|
273
|
+
var days = allDays(), tops = topModels(DATA.byModel || {}, 8), out = [];
|
|
274
|
+
if (RANGE === 'daily') {
|
|
275
|
+
var last = days.slice(-60);
|
|
276
|
+
last.forEach(function (d) {
|
|
277
|
+
var per = (DATA.byDayModel || {})[d] || {}, segs = [], total = 0, other = 0;
|
|
278
|
+
tops.forEach(function (m) { var v = per[m] || 0; if (v) { segs.push([m, v]); total += v; } });
|
|
279
|
+
Object.keys(per).forEach(function (m) { if (tops.indexOf(m) < 0) other += per[m]; });
|
|
280
|
+
if (other) { segs.push(['other', other]); total += other; }
|
|
281
|
+
var dt = new Date(d + 'T12:00:00');
|
|
282
|
+
out.push({ key: d, label: isNaN(dt) ? '' : String(dt.getDate()), total: total, segs: segs });
|
|
283
|
+
});
|
|
284
|
+
document.getElementById('rangeNote').textContent = 'stacked by model / last 60 days';
|
|
285
|
+
} else if (RANGE === 'weekly') {
|
|
286
|
+
var weeks = {}, order = [];
|
|
287
|
+
days.forEach(function (d) {
|
|
288
|
+
var dt = new Date(d + 'T12:00:00'); if (isNaN(dt)) return;
|
|
289
|
+
var monday = new Date(dt); monday.setDate(dt.getDate() - ((dt.getDay() + 6) % 7));
|
|
290
|
+
var k = dayKey(monday);
|
|
291
|
+
if (!weeks[k]) { weeks[k] = {}; order.push(k); }
|
|
292
|
+
var per = (DATA.byDayModel || {})[d] || {};
|
|
293
|
+
Object.keys(per).forEach(function (m) { weeks[k][m] = (weeks[k][m] || 0) + per[m]; });
|
|
294
|
+
});
|
|
295
|
+
order.slice(-26).forEach(function (k) {
|
|
296
|
+
var per = weeks[k], segs = [], total = 0, other = 0;
|
|
297
|
+
tops.forEach(function (m) { var v = per[m] || 0; if (v) { segs.push([m, v]); total += v; } });
|
|
298
|
+
Object.keys(per).forEach(function (m) { if (tops.indexOf(m) < 0) other += per[m]; });
|
|
299
|
+
if (other) { segs.push(['other', other]); total += other; }
|
|
300
|
+
var dt = new Date(k + 'T12:00:00');
|
|
301
|
+
out.push({ key: k, label: isNaN(dt) ? '' : (dt.toLocaleString('en-US', { month: 'short' }) + ' ' + dt.getDate()), total: total, segs: segs });
|
|
302
|
+
});
|
|
303
|
+
document.getElementById('rangeNote').textContent = 'stacked by model / last 26 weeks';
|
|
304
|
+
} else {
|
|
305
|
+
var run = {}, cum = 0;
|
|
306
|
+
days.forEach(function (d) {
|
|
307
|
+
var per = (DATA.byDayModel || {})[d] || {}, segs = [], other = 0;
|
|
308
|
+
tops.forEach(function (m) {
|
|
309
|
+
var v = per[m] || 0;
|
|
310
|
+
run[m] = (run[m] || 0) + v;
|
|
311
|
+
if (run[m]) segs.push([m, run[m]]);
|
|
312
|
+
});
|
|
313
|
+
Object.keys(per).forEach(function (m) { if (tops.indexOf(m) < 0) { run[m] = (run[m] || 0) + per[m]; } });
|
|
314
|
+
other = Object.keys(run).reduce(function (a, m) { return tops.indexOf(m) < 0 ? a + run[m] : a; }, 0);
|
|
315
|
+
var tot = segs.reduce(function (a, s) { return a + s[1]; }, 0) + other;
|
|
316
|
+
if (other) segs.push(['other', other]);
|
|
317
|
+
cum = tot;
|
|
318
|
+
var dt = new Date(d + 'T12:00:00');
|
|
319
|
+
out.push({ key: d, label: isNaN(dt) ? '' : String(dt.getDate()), total: tot, segs: segs });
|
|
320
|
+
});
|
|
321
|
+
out = out.slice(-60);
|
|
322
|
+
document.getElementById('rangeNote').textContent = 'running total by model / all time';
|
|
323
|
+
}
|
|
324
|
+
return { list: out, tops: tops };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function renderBars() {
|
|
328
|
+
var b = buckets(), wrap = document.getElementById('bars'), axis = document.getElementById('barDays');
|
|
329
|
+
wrap.innerHTML = ''; axis.innerHTML = '';
|
|
330
|
+
var max = 1;
|
|
331
|
+
b.list.forEach(function (r) { max = Math.max(max, r.total); });
|
|
332
|
+
b.list.forEach(function (r) {
|
|
246
333
|
var col = document.createElement('div');
|
|
247
|
-
col.className = 'flex flex-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
334
|
+
col.className = 'flex-1 flex items-end h-full';
|
|
335
|
+
col.title = r.key + ': ' + fmt(r.total) + ' tokens' +
|
|
336
|
+
(r.segs.length ? '\n' + r.segs.map(function (s) { return s[0] + ' ' + fmt(s[1]); }).join('\n') : '');
|
|
337
|
+
var bar = document.createElement('div');
|
|
338
|
+
bar.className = 'mbar w-full';
|
|
339
|
+
bar.style.height = Math.max(2, Math.round(r.total / max * 100)) + '%';
|
|
340
|
+
var order = r.segs.slice().sort(function (x, y) { return y[1] - x[1]; });
|
|
341
|
+
order.forEach(function (s) {
|
|
342
|
+
var seg = document.createElement('span');
|
|
343
|
+
var ci = s[0] === 'other' ? -1 : b.tops.indexOf(s[0]);
|
|
344
|
+
seg.style.background = ci < 0 ? 'var(--dim)' : PALETTE[ci % PALETTE.length];
|
|
345
|
+
seg.style.height = Math.max(1, Math.round(s[1] / r.total * 100)) + '%';
|
|
346
|
+
bar.appendChild(seg);
|
|
347
|
+
});
|
|
348
|
+
col.appendChild(bar); wrap.appendChild(col);
|
|
349
|
+
var lb = document.createElement('span');
|
|
350
|
+
lb.className = 'flex-1 text-center truncate'; lb.textContent = r.label;
|
|
351
|
+
axis.appendChild(lb);
|
|
352
|
+
});
|
|
353
|
+
var legend = document.getElementById('legend');
|
|
354
|
+
legend.innerHTML = '';
|
|
355
|
+
b.tops.slice(0, 8).forEach(function (m, i) {
|
|
356
|
+
var s = document.createElement('span');
|
|
357
|
+
s.className = 'flex items-center gap-1.5';
|
|
358
|
+
var dot = document.createElement('span');
|
|
359
|
+
dot.style.cssText = 'width:9px;height:9px;border-radius:3px;background:' + PALETTE[i % PALETTE.length] + ';display:inline-block';
|
|
360
|
+
s.appendChild(dot);
|
|
361
|
+
s.appendChild(document.createTextNode(m));
|
|
362
|
+
legend.appendChild(s);
|
|
363
|
+
});
|
|
364
|
+
if (hasGsap) {
|
|
365
|
+
gsap.from('#bars > div', { scaleY: 0.2, opacity: 0.2, transformOrigin: 'bottom', duration: 0.5, ease: 'power2.out', stagger: 0.008, overwrite: true });
|
|
366
|
+
if (hasST) {
|
|
367
|
+
gsap.from('#modelCards > div', {
|
|
368
|
+
scale: 0.92, opacity: 0.2, duration: 0.6, ease: 'power2.out', stagger: 0.08, overwrite: true,
|
|
369
|
+
scrollTrigger: { trigger: '#modelCards', start: 'top 88%' }
|
|
370
|
+
});
|
|
256
371
|
}
|
|
257
|
-
weeks++;
|
|
258
|
-
g.appendChild(col);
|
|
259
372
|
}
|
|
260
|
-
document.getElementById('graphRange').textContent =
|
|
261
|
-
dayKey(start) + ' to ' + dayKey(today);
|
|
262
373
|
}
|
|
263
374
|
|
|
264
|
-
function
|
|
265
|
-
var
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
375
|
+
function wow(model) {
|
|
376
|
+
var days = allDays().slice(-14), cur = 0, prev = 0;
|
|
377
|
+
days.forEach(function (d, i) {
|
|
378
|
+
var v = ((DATA.byDayModel || {})[d] || {})[model] || 0;
|
|
379
|
+
if (i < days.length - 7) prev += v; else cur += v;
|
|
380
|
+
});
|
|
381
|
+
if (!prev) return cur ? 'new' : '—';
|
|
382
|
+
var p = Math.round((cur - prev) / prev * 100);
|
|
383
|
+
return (p >= 0 ? '+' : '') + p + '%';
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function renderModels() {
|
|
387
|
+
var byModel = DATA.byModel || {}, tops = topModels(byModel, 8);
|
|
388
|
+
var cards = document.getElementById('modelCards');
|
|
389
|
+
cards.innerHTML = '';
|
|
390
|
+
tops.slice(0, 3).forEach(function (m, i) {
|
|
391
|
+
var v = vendorOf(m), d = wow(m);
|
|
392
|
+
var card = document.createElement('div');
|
|
393
|
+
card.className = 'rise md:col-span-4 rounded-xl p-5 overflow-hidden relative';
|
|
394
|
+
card.style.cssText = 'border: 1px solid var(--line); background: var(--panel)';
|
|
395
|
+
var ghost = document.createElement('span');
|
|
396
|
+
ghost.className = 'display pointer-events-none select-none absolute -right-2 -bottom-7 font-bold';
|
|
397
|
+
ghost.style.cssText = 'font-size: 120px; line-height: 1; opacity: .07';
|
|
398
|
+
ghost.textContent = v.mark;
|
|
399
|
+
var head = document.createElement('p');
|
|
400
|
+
head.className = 'mono text-xs mb-3'; head.style.color = 'var(--dim)';
|
|
401
|
+
head.textContent = '0' + (i + 1);
|
|
402
|
+
var row = document.createElement('div');
|
|
403
|
+
row.className = 'flex items-center gap-3';
|
|
404
|
+
var badge = document.createElement('span');
|
|
405
|
+
badge.className = 'monogram'; badge.style.background = v.color; badge.textContent = v.mark;
|
|
406
|
+
var mid = document.createElement('div'); mid.className = 'min-w-0 flex-1';
|
|
407
|
+
var nm = document.createElement('p'); nm.className = 'mono text-sm font-bold truncate'; nm.textContent = m;
|
|
408
|
+
var vn = document.createElement('p'); vn.className = 'mono text-xs truncate'; vn.style.color = 'var(--dim)'; vn.textContent = v.name;
|
|
409
|
+
mid.appendChild(nm); mid.appendChild(vn);
|
|
410
|
+
var tot = document.createElement('p'); tot.className = 'mono font-bold text-lg shrink-0'; tot.textContent = fmtShort(modelTotal(byModel, m));
|
|
411
|
+
row.appendChild(badge); row.appendChild(mid); row.appendChild(tot);
|
|
412
|
+
var delta = document.createElement('p');
|
|
413
|
+
delta.className = 'mono text-xs mt-3';
|
|
414
|
+
delta.style.color = d.charAt(0) === '-' ? '#f87171' : 'var(--accent)';
|
|
415
|
+
delta.textContent = d;
|
|
416
|
+
card.appendChild(ghost); card.appendChild(head); card.appendChild(row); card.appendChild(delta);
|
|
417
|
+
cards.appendChild(card);
|
|
418
|
+
});
|
|
419
|
+
var ol = document.getElementById('models');
|
|
420
|
+
ol.innerHTML = '';
|
|
272
421
|
var max = 1;
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
var
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
var
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
var
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
422
|
+
tops.forEach(function (m) { max = Math.max(max, modelTotal(byModel, m)); });
|
|
423
|
+
tops.forEach(function (m, i) {
|
|
424
|
+
var v = vendorOf(m);
|
|
425
|
+
var li = document.createElement('li');
|
|
426
|
+
li.className = 'tilt flex items-center gap-3 px-4 py-3' + (i < tops.length - 1 ? ' rowline' : '');
|
|
427
|
+
var badge = document.createElement('span');
|
|
428
|
+
badge.className = 'monogram'; badge.style.cssText = 'width:28px;height:28px;font-size:11px;border-radius:8px;background:' + v.color;
|
|
429
|
+
badge.textContent = v.mark;
|
|
430
|
+
badge.title = v.name;
|
|
431
|
+
var mid = document.createElement('div'); mid.className = 'min-w-0 flex-1';
|
|
432
|
+
var nm = document.createElement('p'); nm.className = 'mono text-sm truncate'; nm.textContent = m;
|
|
433
|
+
var track = document.createElement('div'); track.className = 'bar-track mt-1.5 h-1.5 overflow-hidden';
|
|
434
|
+
var fill = document.createElement('div'); fill.className = 'bar-fill h-full';
|
|
435
|
+
fill.style.width = Math.max(2, Math.round(modelTotal(byModel, m) / max * 100)) + '%';
|
|
436
|
+
track.appendChild(fill); mid.appendChild(nm); mid.appendChild(track);
|
|
437
|
+
var n = document.createElement('span'); n.className = 'mono font-bold shrink-0'; n.textContent = fmt(modelTotal(byModel, m));
|
|
438
|
+
li.appendChild(badge); li.appendChild(mid); li.appendChild(n);
|
|
439
|
+
ol.appendChild(li);
|
|
290
440
|
});
|
|
441
|
+
if (!tops.length) ol.innerHTML = '<li class="mono text-sm px-4 py-6" style="color: var(--dim)">no session tokens yet</li>';
|
|
291
442
|
}
|
|
292
443
|
|
|
293
|
-
function
|
|
294
|
-
var
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
444
|
+
function renderTools() {
|
|
445
|
+
var ol = document.getElementById('top');
|
|
446
|
+
ol.innerHTML = '';
|
|
447
|
+
var tools = DATA.byTool || [], max = 1;
|
|
448
|
+
tools.forEach(function (r) { max = Math.max(max, r[1]); });
|
|
449
|
+
tools.forEach(function (r) {
|
|
450
|
+
var li = document.createElement('li');
|
|
451
|
+
var top = document.createElement('div');
|
|
452
|
+
top.className = 'flex justify-between gap-2';
|
|
453
|
+
var nm = document.createElement('span'); nm.className = 'truncate'; nm.textContent = r[0];
|
|
454
|
+
var n = document.createElement('span'); n.className = 'font-bold'; n.textContent = fmt(r[1]);
|
|
455
|
+
top.appendChild(nm); top.appendChild(n);
|
|
456
|
+
var track = document.createElement('div'); track.className = 'bar-track mt-1 h-1 overflow-hidden';
|
|
457
|
+
var fill = document.createElement('div'); fill.className = 'bar-fill h-full';
|
|
458
|
+
fill.style.width = Math.max(2, Math.round(r[1] / max * 100)) + '%';
|
|
459
|
+
track.appendChild(fill);
|
|
460
|
+
li.appendChild(top); li.appendChild(track);
|
|
461
|
+
ol.appendChild(li);
|
|
462
|
+
});
|
|
463
|
+
document.getElementById('emptyTop').classList.toggle('hidden', tools.length > 0);
|
|
464
|
+
}
|
|
302
465
|
|
|
303
|
-
|
|
466
|
+
function renderStrip(t) {
|
|
467
|
+
var defs = [
|
|
468
|
+
['input', t.input, 'arrow-down-to-line'],
|
|
469
|
+
['output', t.output, 'arrow-up-from-line'],
|
|
470
|
+
['cache read', t.cacheRead, 'hard-drive-download'],
|
|
471
|
+
['cache write', t.cacheWrite, 'hard-drive-upload']
|
|
472
|
+
].filter(function (b) { return b[1] > 0; });
|
|
473
|
+
if (!defs.length) defs = [['input', 0, 'arrow-down-to-line'], ['output', 0, 'arrow-up-from-line']];
|
|
304
474
|
var strip = document.getElementById('tokens');
|
|
305
475
|
strip.innerHTML = '';
|
|
306
|
-
|
|
476
|
+
defs.forEach(function (b, i) {
|
|
307
477
|
var cell = document.createElement('div');
|
|
308
|
-
cell.className = 'tilt px-4 py-4' + (i > 0 ? ' sm:border-l' : '');
|
|
478
|
+
cell.className = 'tilt flex-1 px-4 py-4' + (i > 0 ? ' sm:border-l' : '');
|
|
309
479
|
cell.style.borderColor = 'var(--line)';
|
|
310
480
|
cell.innerHTML = '<div class="flex items-center gap-2 mono text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">' +
|
|
311
481
|
'<i data-lucide="' + b[2] + '" class="size-3.5"></i><span></span></div>' +
|
|
@@ -314,63 +484,60 @@
|
|
|
314
484
|
cell.querySelector('p').textContent = fmt(b[1]);
|
|
315
485
|
strip.appendChild(cell);
|
|
316
486
|
});
|
|
487
|
+
}
|
|
317
488
|
|
|
318
|
-
|
|
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.';
|
|
489
|
+
function money(v) { return '$' + v.toFixed(2); }
|
|
328
490
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
491
|
+
function render(d) {
|
|
492
|
+
DATA = d;
|
|
493
|
+
var t = d.tokens || { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
494
|
+
var total = t.input + t.output + t.cacheRead + t.cacheWrite;
|
|
495
|
+
document.getElementById('meta').textContent = fmt(total) + ' session tokens / ' + d.total + ' ledger rows';
|
|
496
|
+
var sub = document.getElementById('herosub');
|
|
497
|
+
sub.innerHTML = '';
|
|
498
|
+
('input ' + fmt(t.input) + ' · output ' + fmt(t.output) + ' · cache read ' + fmt(t.cacheRead) +
|
|
499
|
+
(t.cacheWrite ? ' · cache write ' + fmt(t.cacheWrite) : '') + ' · ' + money(d.usd || 0)).split(' ').forEach(function (w) {
|
|
500
|
+
var s = document.createElement('span'); s.textContent = w + ' ';
|
|
501
|
+
sub.appendChild(s);
|
|
339
502
|
});
|
|
340
|
-
|
|
341
|
-
|
|
503
|
+
var totalEl = document.getElementById('total');
|
|
504
|
+
if (hasGsap) {
|
|
505
|
+
var o = { v: 0 };
|
|
506
|
+
gsap.to(o, { v: total, duration: 1.2, ease: 'power3.out', onUpdate: function () { totalEl.textContent = fmt(o.v); } });
|
|
507
|
+
} else {
|
|
508
|
+
totalEl.textContent = fmt(total);
|
|
342
509
|
}
|
|
510
|
+
countUp(document.getElementById('usd'), d.usd || 0, true);
|
|
511
|
+
document.getElementById('usdNote').textContent = d.priced ? 'per-model price table' : 'incl. default pricing';
|
|
512
|
+
countUp(document.getElementById('saved'), d.savedUsd || 0, true);
|
|
513
|
+
document.getElementById('co2').textContent = '~' + (d.co2g || 0).toFixed(1) + 'g';
|
|
514
|
+
var share = total ? Math.round((t.cacheRead + t.cacheWrite) / total * 100) : 0;
|
|
515
|
+
document.getElementById('cacheShare').textContent = share + '%';
|
|
516
|
+
document.getElementById('msgs').textContent = fmt(d.messages || 0);
|
|
343
517
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
(
|
|
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);
|
|
363
|
-
});
|
|
364
|
-
document.getElementById('emptyTop').classList.toggle('hidden', (d.total || 0) > 0);
|
|
365
|
-
ol.classList.toggle('hidden', (d.total || 0) === 0);
|
|
518
|
+
renderStrip(t);
|
|
519
|
+
renderBars();
|
|
520
|
+
renderModels();
|
|
521
|
+
renderTools();
|
|
366
522
|
|
|
367
523
|
if (window.lucide) lucide.createIcons();
|
|
368
|
-
|
|
524
|
+
if (hasGsap) {
|
|
525
|
+
gsap.from('.hero-in', { y: 26, opacity: 0, duration: 0.8, ease: 'power3.out', stagger: 0.08 });
|
|
526
|
+
// Scrub: hero sub words fade in sequence on scroll.
|
|
527
|
+
if (hasST) {
|
|
528
|
+
gsap.fromTo('#herosub span', { opacity: 0.15 }, {
|
|
529
|
+
opacity: 1, ease: 'none', stagger: 0.05,
|
|
530
|
+
scrollTrigger: { trigger: '#herosub', start: 'top 92%', end: 'top 55%', scrub: true }
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
} else {
|
|
534
|
+
observe();
|
|
535
|
+
}
|
|
369
536
|
}
|
|
370
537
|
|
|
371
538
|
var seen = new WeakSet();
|
|
372
539
|
function observe() {
|
|
373
|
-
var els = document.querySelectorAll('.rise
|
|
540
|
+
var els = document.querySelectorAll('.rise');
|
|
374
541
|
if (!('IntersectionObserver' in window) || reduce) {
|
|
375
542
|
els.forEach(function (el) { el.classList.add('in'); });
|
|
376
543
|
return;
|
|
@@ -385,10 +552,19 @@
|
|
|
385
552
|
els.forEach(function (el) { if (!seen.has(el)) io.observe(el); });
|
|
386
553
|
}
|
|
387
554
|
|
|
555
|
+
document.querySelectorAll('.seg button').forEach(function (btn) {
|
|
556
|
+
btn.addEventListener('click', function () {
|
|
557
|
+
document.querySelectorAll('.seg button').forEach(function (b) { b.classList.remove('on'); });
|
|
558
|
+
btn.classList.add('on');
|
|
559
|
+
RANGE = btn.getAttribute('data-range');
|
|
560
|
+
if (DATA) renderBars();
|
|
561
|
+
});
|
|
562
|
+
});
|
|
563
|
+
|
|
388
564
|
function load() {
|
|
389
565
|
document.getElementById('meta').textContent = 'connecting to ledger';
|
|
390
566
|
fetch('data.json').then(function (r) { return r.json(); }).then(render).catch(function () {
|
|
391
|
-
document.getElementById('meta').textContent = 'No live data.
|
|
567
|
+
document.getElementById('meta').textContent = 'No live data. Open with: tersio gain --open';
|
|
392
568
|
document.getElementById('rawlink').style.display = 'none';
|
|
393
569
|
document.getElementById('emptyTop').classList.remove('hidden');
|
|
394
570
|
if (window.lucide) lucide.createIcons();
|