@krtclcdy/tersio 2.8.0 → 2.10.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 +53 -36
- package/cli/banner.js +82 -0
- package/cli/dashboard.js +30 -10
- package/cli/doctor.js +3 -1
- package/cli/install.js +12 -4
- package/cli/update.js +17 -6
- package/cli/usage.js +87 -22
- package/dashboard/app.js +801 -0
- package/dashboard/styles.css +157 -0
- package/dashboard/template.html +213 -0
- package/extensions/shared/carbon.js +88 -0
- package/extensions/shared/pricing.js +121 -0
- package/extensions/shared/rtk-gain.js +61 -0
- package/extensions/shared/usage-ledger.js +64 -37
- package/package.json +2 -3
- package/cli/dashboard.html +0 -580
- /package/{cli → dashboard}/brand.webp +0 -0
package/cli/dashboard.html
DELETED
|
@@ -1,580 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<meta name="color-scheme" content="dark light">
|
|
7
|
-
<title>Tersio Gain Dashboard</title>
|
|
8
|
-
<link rel="icon" type="image/png" href="brand.webp">
|
|
9
|
-
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
|
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">
|
|
14
|
-
<style>
|
|
15
|
-
:root {
|
|
16
|
-
--bg: #f4f4f5; --panel: #ffffff; --ink: #18181b; --dim: #52525b;
|
|
17
|
-
--line: rgba(24, 24, 27, .12); --accent: #047857; --accent-soft: rgba(4, 120, 87, .12);
|
|
18
|
-
--track: rgba(24, 24, 27, .08); --grid: rgba(4, 120, 87, .07);
|
|
19
|
-
}
|
|
20
|
-
@media (prefers-color-scheme: dark) {
|
|
21
|
-
:root {
|
|
22
|
-
--bg: #09090b; --panel: #101014; --ink: #f4f4f5; --dim: #a1a1aa;
|
|
23
|
-
--line: rgba(255, 255, 255, .09); --accent: #34d399; --accent-soft: rgba(52, 211, 153, .1);
|
|
24
|
-
--track: rgba(255, 255, 255, .07); --grid: rgba(52, 211, 153, .05);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
body { background: var(--bg); color: var(--ink); }
|
|
28
|
-
.display { font-family: "Outfit", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
|
29
|
-
.bg-blueprint {
|
|
30
|
-
background-image: linear-gradient(var(--grid) 1px, transparent 1px),
|
|
31
|
-
linear-gradient(90deg, var(--grid) 1px, transparent 1px);
|
|
32
|
-
background-size: 44px 44px;
|
|
33
|
-
mask-image: radial-gradient(ellipse 90% 70% at 50% 0%, black 30%, transparent 75%);
|
|
34
|
-
}
|
|
35
|
-
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-variant-numeric: tabular-nums; }
|
|
36
|
-
.rowline { border-bottom: 1px solid var(--line); }
|
|
37
|
-
.bar-fill { background: var(--accent); border-radius: 999px; transform-origin: left; }
|
|
38
|
-
.bar-track { background: var(--track); border-radius: 999px; }
|
|
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; }
|
|
40
|
-
@keyframes ping { 75%, 100% { box-shadow: 0 0 0 9px transparent; } }
|
|
41
|
-
.rise { opacity: 0; transform: translateY(26px); }
|
|
42
|
-
.rise.in { opacity: 1; transform: none; transition: opacity .7s cubic-bezier(.16, 1, .3, 1), transform .7s cubic-bezier(.16, 1, .3, 1); }
|
|
43
|
-
.tilt { transition: transform .25s cubic-bezier(.16, 1, .3, 1), background .25s; }
|
|
44
|
-
.tilt:hover { transform: translateX(6px); background: var(--accent-soft); }
|
|
45
|
-
.btn-push { transition: transform .12s, background .2s; }
|
|
46
|
-
.btn-push:hover { background: var(--accent-soft); }
|
|
47
|
-
.btn-push:active { transform: scale(.96); }
|
|
48
|
-
a.social { color: var(--ink); }
|
|
49
|
-
a.social:hover { color: var(--accent); }
|
|
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; }
|
|
56
|
-
.dock {
|
|
57
|
-
position: sticky; top: 12px; z-index: 50;
|
|
58
|
-
background: color-mix(in srgb, var(--panel) 84%, transparent);
|
|
59
|
-
-webkit-backdrop-filter: blur(18px) saturate(160%); backdrop-filter: blur(18px) saturate(160%);
|
|
60
|
-
border: 1px solid var(--line); border-radius: 20px;
|
|
61
|
-
box-shadow: 0 12px 40px rgba(0, 0, 0, .25);
|
|
62
|
-
}
|
|
63
|
-
.dock img.brand { border-radius: 10px; }
|
|
64
|
-
@media (prefers-reduced-motion: reduce) {
|
|
65
|
-
.rise { opacity: 1; transform: none; }
|
|
66
|
-
.live-dot { animation: none; }
|
|
67
|
-
.tilt, .btn-push, .seg button { transition: none; }
|
|
68
|
-
}
|
|
69
|
-
</style>
|
|
70
|
-
</head>
|
|
71
|
-
<body class="min-h-[100dvh] antialiased">
|
|
72
|
-
<div class="bg-blueprint pointer-events-none fixed inset-0" aria-hidden="true"></div>
|
|
73
|
-
|
|
74
|
-
<main class="overflow-x-hidden w-full max-w-full">
|
|
75
|
-
<div class="relative mx-auto max-w-7xl px-4 sm:px-6 pb-16">
|
|
76
|
-
|
|
77
|
-
<!-- Floating dock navbar -->
|
|
78
|
-
<header class="dock mt-4 flex w-full items-center gap-2 px-3 py-2">
|
|
79
|
-
<a href="#" class="flex shrink-0 items-center gap-2.5 pl-1 pr-2" aria-label="Tersio gain home">
|
|
80
|
-
<img src="brand.webp" alt="Tersio" width="32" height="32" class="brand size-8 rounded-full">
|
|
81
|
-
<span class="leading-tight text-left">
|
|
82
|
-
<span class="block font-bold tracking-tight text-sm">TERSIO GAIN</span>
|
|
83
|
-
<span class="mono block text-[11px]" style="color: var(--dim)" id="meta">connecting to ledger</span>
|
|
84
|
-
</span>
|
|
85
|
-
</a>
|
|
86
|
-
<span class="mono hidden shrink-0 sm:flex items-center gap-2 text-xs px-2 ml-auto" style="color: var(--dim)">
|
|
87
|
-
<span class="live-dot inline-block size-2 rounded-full"></span>
|
|
88
|
-
<span id="clock">--:--</span>
|
|
89
|
-
</span>
|
|
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">
|
|
91
|
-
<i data-lucide="refresh-cw" class="size-3.5"></i><span class="hidden md:inline">Reload</span>
|
|
92
|
-
</button>
|
|
93
|
-
</header>
|
|
94
|
-
|
|
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>
|
|
118
|
-
</div>
|
|
119
|
-
<div class="rise col-span-4 md:col-span-2 rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel)">
|
|
120
|
-
<p class="mono text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">cache share</p>
|
|
121
|
-
<p class="mono font-bold text-3xl mt-1" id="cacheShare">-</p>
|
|
122
|
-
<p class="mono text-[11px] mt-1" style="color: var(--dim)">of tokens cached</p>
|
|
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>
|
|
128
|
-
</div>
|
|
129
|
-
</section>
|
|
130
|
-
|
|
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>
|
|
133
|
-
|
|
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>
|
|
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>
|
|
149
|
-
</section>
|
|
150
|
-
|
|
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>
|
|
161
|
-
|
|
162
|
-
<!-- Models list + top tools: 8 + 4 -->
|
|
163
|
-
<main class="grid grid-cols-1 lg:grid-cols-12 grid-flow-dense gap-8 mt-8">
|
|
164
|
-
<div class="lg:col-span-8">
|
|
165
|
-
<section class="rise" aria-label="All models">
|
|
166
|
-
<div class="flex items-center gap-2 mb-1">
|
|
167
|
-
<i data-lucide="layers" class="size-4" style="color: var(--accent)"></i>
|
|
168
|
-
<h2 class="display font-bold tracking-tight text-xl">Models</h2>
|
|
169
|
-
</div>
|
|
170
|
-
<p class="mono text-xs mb-4" style="color: var(--dim)">ranked by tokens / top 8</p>
|
|
171
|
-
<ol id="models" class="rounded-xl overflow-hidden" style="border: 1px solid var(--line); background: var(--panel)"></ol>
|
|
172
|
-
</section>
|
|
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">
|
|
176
|
-
<div class="flex items-center gap-2 mb-1">
|
|
177
|
-
<i data-lucide="terminal" class="size-4" style="color: var(--accent)"></i>
|
|
178
|
-
<h2 class="display font-bold tracking-tight text-xl">Top tools</h2>
|
|
179
|
-
</div>
|
|
180
|
-
<p class="mono text-xs mb-4" style="color: var(--dim)">tool calls in sessions</p>
|
|
181
|
-
<ol id="top" class="mono text-[13px] space-y-3"></ol>
|
|
182
|
-
<p id="emptyTop" class="mono text-sm py-6 text-center hidden" style="color: var(--dim)">No tool calls yet.</p>
|
|
183
|
-
</section>
|
|
184
|
-
</div>
|
|
185
|
-
</main>
|
|
186
|
-
|
|
187
|
-
<footer class="mono text-xs mt-12 pt-4 flex items-center gap-4" style="color: var(--dim); border-top: 1px solid var(--line)">
|
|
188
|
-
<span>tersio gain dashboard / single file</span>
|
|
189
|
-
<span class="ml-auto flex items-center gap-1" style="color: var(--ink)">
|
|
190
|
-
<a href="https://github.com/KurutoDenzeru" target="_blank" rel="noopener" aria-label="GitHub" class="btn-push social grid size-8 place-items-center rounded-lg"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></a>
|
|
191
|
-
<a href="https://linkedin.com/in/kurtcalacday/" target="_blank" rel="noopener" aria-label="LinkedIn" class="btn-push social grid size-8 place-items-center rounded-lg"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg></a>
|
|
192
|
-
<a href="https://instagram.com/krtclcdy/" target="_blank" rel="noopener" aria-label="Instagram" class="btn-push social grid size-8 place-items-center rounded-lg"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077"/></svg></a>
|
|
193
|
-
</span>
|
|
194
|
-
<a id="rawlink" href="data.json" class="underline underline-offset-2">raw data.json</a>
|
|
195
|
-
</footer>
|
|
196
|
-
</div>
|
|
197
|
-
</main>
|
|
198
|
-
|
|
199
|
-
<script>
|
|
200
|
-
(function () {
|
|
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);
|
|
205
|
-
|
|
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
|
-
}
|
|
214
|
-
|
|
215
|
-
function countUp(el, target, money) {
|
|
216
|
-
function text(v) { return money ? '$' + v.toFixed(2) : fmt(Math.round(v)); }
|
|
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); } });
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function tick() {
|
|
223
|
-
var c = document.getElementById('clock');
|
|
224
|
-
if (c) c.textContent = new Date().toLocaleString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' });
|
|
225
|
-
}
|
|
226
|
-
tick(); setInterval(tick, 1000);
|
|
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
|
-
|
|
250
|
-
function dayKey(d) {
|
|
251
|
-
return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
|
|
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
|
-
}
|
|
257
|
-
|
|
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) {
|
|
333
|
-
var col = document.createElement('div');
|
|
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
|
-
});
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
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 = '';
|
|
421
|
-
var max = 1;
|
|
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);
|
|
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>';
|
|
442
|
-
}
|
|
443
|
-
|
|
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
|
-
}
|
|
465
|
-
|
|
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']];
|
|
474
|
-
var strip = document.getElementById('tokens');
|
|
475
|
-
strip.innerHTML = '';
|
|
476
|
-
defs.forEach(function (b, i) {
|
|
477
|
-
var cell = document.createElement('div');
|
|
478
|
-
cell.className = 'tilt flex-1 px-4 py-4' + (i > 0 ? ' sm:border-l' : '');
|
|
479
|
-
cell.style.borderColor = 'var(--line)';
|
|
480
|
-
cell.innerHTML = '<div class="flex items-center gap-2 mono text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">' +
|
|
481
|
-
'<i data-lucide="' + b[2] + '" class="size-3.5"></i><span></span></div>' +
|
|
482
|
-
'<p class="mono font-bold text-3xl mt-1"></p>';
|
|
483
|
-
cell.querySelector('span').textContent = b[0];
|
|
484
|
-
cell.querySelector('p').textContent = fmt(b[1]);
|
|
485
|
-
strip.appendChild(cell);
|
|
486
|
-
});
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
function money(v) { return '$' + v.toFixed(2); }
|
|
490
|
-
|
|
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);
|
|
502
|
-
});
|
|
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);
|
|
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);
|
|
517
|
-
|
|
518
|
-
renderStrip(t);
|
|
519
|
-
renderBars();
|
|
520
|
-
renderModels();
|
|
521
|
-
renderTools();
|
|
522
|
-
|
|
523
|
-
if (window.lucide) lucide.createIcons();
|
|
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
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
var seen = new WeakSet();
|
|
539
|
-
function observe() {
|
|
540
|
-
var els = document.querySelectorAll('.rise');
|
|
541
|
-
if (!('IntersectionObserver' in window) || reduce) {
|
|
542
|
-
els.forEach(function (el) { el.classList.add('in'); });
|
|
543
|
-
return;
|
|
544
|
-
}
|
|
545
|
-
var io = new IntersectionObserver(function (entries) {
|
|
546
|
-
entries.forEach(function (e) {
|
|
547
|
-
if (e.isIntersecting && !seen.has(e.target)) {
|
|
548
|
-
seen.add(e.target); e.target.classList.add('in'); io.unobserve(e.target);
|
|
549
|
-
}
|
|
550
|
-
});
|
|
551
|
-
}, { threshold: 0.12 });
|
|
552
|
-
els.forEach(function (el) { if (!seen.has(el)) io.observe(el); });
|
|
553
|
-
}
|
|
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
|
-
|
|
564
|
-
function load() {
|
|
565
|
-
document.getElementById('meta').textContent = 'connecting to ledger';
|
|
566
|
-
fetch('data.json').then(function (r) { return r.json(); }).then(render).catch(function () {
|
|
567
|
-
document.getElementById('meta').textContent = 'No live data. Open with: tersio gain --open';
|
|
568
|
-
document.getElementById('rawlink').style.display = 'none';
|
|
569
|
-
document.getElementById('emptyTop').classList.remove('hidden');
|
|
570
|
-
if (window.lucide) lucide.createIcons();
|
|
571
|
-
observe();
|
|
572
|
-
});
|
|
573
|
-
}
|
|
574
|
-
document.getElementById('reload').addEventListener('click', load);
|
|
575
|
-
load();
|
|
576
|
-
observe();
|
|
577
|
-
})();
|
|
578
|
-
</script>
|
|
579
|
-
</body>
|
|
580
|
-
</html>
|
|
File without changes
|