@krtclcdy/tersio 2.20.1 → 2.22.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 +8 -9
- package/cli/common.js +14 -1
- package/cli/dashboard.js +367 -35
- package/cli/doctor-fix.js +185 -0
- package/cli/doctor.js +120 -53
- package/cli/install.js +46 -60
- package/cli/rtk-wiring.js +1 -1
- package/cli/settings.js +128 -5
- package/cli/uninstall.js +4 -4
- package/cli/update.js +29 -20
- package/cli/usage.js +1 -1
- package/dashboard/{app.js → charts.js} +74 -361
- package/dashboard/core.js +357 -0
- package/dashboard/settings.js +166 -0
- package/dashboard/share.js +218 -0
- package/dashboard/styles.css +87 -12
- package/dashboard/template.html +162 -57
- package/extensions/ai-addons-updater/index.js +13 -30
- package/extensions/ai-addons-updater/index.ts +11 -29
- package/extensions/lib/utils.js +26 -1
- package/extensions/lib/utils.ts +28 -1
- package/extensions/shared/pricing.js +2 -2
- package/extensions/shared/pricing.ts +2 -1
- package/extensions/shared/usage-ledger.js +3 -2
- package/extensions/shared/usage-ledger.ts +3 -2
- package/extensions/shared/usage-store.js +7 -7
- package/extensions/shared/usage-store.ts +7 -6
- package/gain/dist/brand.webp +0 -0
- package/gain/dist/index.html +65 -0
- package/gain/dist/vite.svg +1 -0
- package/package.json +17 -34
- package/tersio.js +3 -2
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
var T = window.Tersio;
|
|
3
|
+
var fmt = T.fmt, fmtShort = T.fmtShort, fxMoney = T.fxMoney, toast = T.toast;
|
|
4
|
+
(function initShare() {
|
|
5
|
+
function dayStr(d) { return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0'); }
|
|
6
|
+
function streak(byDay) {
|
|
7
|
+
var cur = new Date(); cur.setHours(0, 0, 0, 0);
|
|
8
|
+
var key = dayStr(cur);
|
|
9
|
+
if (!byDay[key]) { cur.setDate(cur.getDate() - 1); key = dayStr(cur); if (!byDay[key]) return 0; }
|
|
10
|
+
var n = 0;
|
|
11
|
+
while (byDay[key]) { n++; cur.setDate(cur.getDate() - 1); key = dayStr(cur); }
|
|
12
|
+
return n;
|
|
13
|
+
}
|
|
14
|
+
function shareStats() {
|
|
15
|
+
var t = (T.getData() && T.getData().tokens) || { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
16
|
+
var total = t.input + t.output + t.cacheRead + t.cacheWrite;
|
|
17
|
+
var runs = (T.getData() && T.getData().messages) || 0;
|
|
18
|
+
var byModel = (T.getData() && T.getData().byModel) || {};
|
|
19
|
+
var models = Object.keys(byModel).length;
|
|
20
|
+
var cost = (T.getData() && T.getData().usd) || 0;
|
|
21
|
+
var best = 0, bestDay = '';
|
|
22
|
+
Object.keys((T.getData() && T.getData().byDay) || {}).forEach(function(k) {
|
|
23
|
+
var b = (T.getData() && T.getData().byDay)[k];
|
|
24
|
+
var v = b.input + b.output + b.cacheRead + b.cacheWrite;
|
|
25
|
+
if (v > best) { best = v; bestDay = k; }
|
|
26
|
+
});
|
|
27
|
+
return { total: total, runs: runs, avg: runs ? Math.round(total / runs) : 0, saved: (T.getData() && T.getData().savedUsd) || 0, streak: streak((T.getData() && T.getData().byDay) || {}), best: best, bestDay: bestDay, cost: cost, models: models };
|
|
28
|
+
}
|
|
29
|
+
function shareText() {
|
|
30
|
+
var s = shareStats();
|
|
31
|
+
return fmtShort(s.total) + ' tokens / ' + fmt(s.runs) + ' runs / ' + fmtShort(s.avg) + ' per run. Saved ' + fxMoney(s.saved) + ' via cache. ' + s.streak + '-day streak. My AI spend, tracked with Tersio.';
|
|
32
|
+
}
|
|
33
|
+
var brandURI = null, brandQueued = false;
|
|
34
|
+
function loadBrand() {
|
|
35
|
+
if (brandURI || brandQueued || typeof fetch !== 'function') return;
|
|
36
|
+
brandQueued = true;
|
|
37
|
+
fetch('brand.webp').then(function(r) { return r.blob(); }).then(function(b) {
|
|
38
|
+
var fr = new FileReader();
|
|
39
|
+
fr.onload = function() { brandURI = fr.result; };
|
|
40
|
+
fr.readAsDataURL(b);
|
|
41
|
+
}).catch(function() { });
|
|
42
|
+
}
|
|
43
|
+
loadBrand();
|
|
44
|
+
function paint() {
|
|
45
|
+
var s = shareStats();
|
|
46
|
+
document.getElementById('shareTotal').textContent = fmtShort(s.total) + ' tokens';
|
|
47
|
+
document.getElementById('shareRuns').textContent = 'across ' + fmt(s.runs) + ' agent runs';
|
|
48
|
+
document.getElementById('shareAvg').textContent = fmtShort(s.avg) + ' / run';
|
|
49
|
+
document.getElementById('shareSaved').textContent = fxMoney(s.saved);
|
|
50
|
+
document.getElementById('shareBest').textContent = fmtShort(s.best);
|
|
51
|
+
document.getElementById('shareBest').title = s.bestDay || '';
|
|
52
|
+
document.getElementById('shareCost').textContent = fxMoney(s.cost);
|
|
53
|
+
document.getElementById('shareModels').textContent = String(s.models);
|
|
54
|
+
document.getElementById('shareStreak').textContent = s.streak + (s.streak === 1 ? ' day' : ' days');
|
|
55
|
+
document.getElementById('shareBest').textContent = fmtShort(s.best);
|
|
56
|
+
document.getElementById('shareBest').title = s.bestDay || '';
|
|
57
|
+
document.getElementById('shareCost').textContent = fxMoney(s.cost);
|
|
58
|
+
document.getElementById('shareModels').textContent = String(s.models);
|
|
59
|
+
document.getElementById('shareStreak').textContent = s.streak + (s.streak === 1 ? ' day' : ' days');
|
|
60
|
+
paintHeat();
|
|
61
|
+
}
|
|
62
|
+
function heatVals(count) {
|
|
63
|
+
var byDay = (T.getData() && T.getData().byDay) || {};
|
|
64
|
+
var d = new Date(); d.setHours(0, 0, 0, 0); d.setDate(d.getDate() - (count - 1));
|
|
65
|
+
var vals = [], max = 0;
|
|
66
|
+
for (var i = 0; i < count; i++) {
|
|
67
|
+
var k = dayStr(d);
|
|
68
|
+
var b = byDay[k], v = b ? T.dayTotal(b) : 0;
|
|
69
|
+
vals.push(v); if (v > max) max = v;
|
|
70
|
+
d.setDate(d.getDate() + 1);
|
|
71
|
+
}
|
|
72
|
+
return { vals: vals, max: max };
|
|
73
|
+
}
|
|
74
|
+
function heatLevel(v, max) {
|
|
75
|
+
var s = max ? Math.sqrt(v / max) : 0;
|
|
76
|
+
return !v ? '' : s >= 0.7 ? ' l4' : s >= 0.45 ? ' l3' : s >= 0.2 ? ' l2' : ' l1';
|
|
77
|
+
}
|
|
78
|
+
function paintHeat() {
|
|
79
|
+
var el = document.getElementById('shareHeat');
|
|
80
|
+
if (!el) return;
|
|
81
|
+
// GitHub layout: 26 weekly columns × 7 weekday rows, oldest first.
|
|
82
|
+
var h = heatVals(182), out = [];
|
|
83
|
+
for (var d = 0; d < 7; d++) {
|
|
84
|
+
for (var w = 0; w < 26; w++) {
|
|
85
|
+
out.push('<span class="cell' + heatLevel(h.vals[w * 7 + d], h.max) + '"></span>');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
el.innerHTML = out.join('');
|
|
89
|
+
}
|
|
90
|
+
function copyText(text, okMsg) {
|
|
91
|
+
function done() { toast('Copied', okMsg, 'copy'); }
|
|
92
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
93
|
+
navigator.clipboard.writeText(text).then(done, function() { fallback(); });
|
|
94
|
+
} else fallback();
|
|
95
|
+
function fallback() {
|
|
96
|
+
try {
|
|
97
|
+
var ta = document.createElement('textarea');
|
|
98
|
+
ta.value = text; document.body.appendChild(ta); ta.select();
|
|
99
|
+
document.execCommand('copy'); ta.remove(); done();
|
|
100
|
+
} catch (e) { toast('Copy failed', 'Select the text manually.', 'circle-alert'); }
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function svgCard() {
|
|
104
|
+
var s = shareStats();
|
|
105
|
+
function e(x) { return String(x).replace(/&/g, '&').replace(/</g, '<'); }
|
|
106
|
+
// Render honors the active theme: explicit choice wins, otherwise the
|
|
107
|
+
// OS preference (same rule as applyTheme).
|
|
108
|
+
var themeChoice = null;
|
|
109
|
+
try { themeChoice = localStorage.getItem('tersio-theme'); } catch (err) { }
|
|
110
|
+
var dark = themeChoice ? themeChoice === 'dark'
|
|
111
|
+
: !!(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
112
|
+
var pal = dark
|
|
113
|
+
? { bg: '#09090b', ink: '#f4f4f5', dim: '#a1a1aa', accent: '#34d399' }
|
|
114
|
+
: { bg: '#ffffff', ink: '#18181b', dim: '#52525b', accent: '#047857' };
|
|
115
|
+
var h = heatVals(182);
|
|
116
|
+
var cw = 32, gap = 9, step = cw + gap, gx = 64, gy = 340, grid = '';
|
|
117
|
+
for (var gd = 0; gd < 7; gd++) {
|
|
118
|
+
for (var gw = 0; gw < 26; gw++) {
|
|
119
|
+
var gv = h.vals[gw * 7 + gd];
|
|
120
|
+
var gs = h.max ? Math.sqrt(gv / h.max) : 0;
|
|
121
|
+
var go = gv ? (0.45 + 0.55 * gs).toFixed(2) : 0.13;
|
|
122
|
+
grid += '<rect x="' + (gx + gw * step) + '" y="' + (gy + gd * step) + '" width="' + cw + '" height="' + cw + '" rx="8" fill="' + pal.accent + '" opacity="' + go + '"/>';
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
var streakTxt = s.streak + (s.streak === 1 ? ' day' : ' days');
|
|
126
|
+
var logo = brandURI ? '<clipPath id="blogo"><rect x="1012" y="40" width="124" height="124" rx="62"/></clipPath>' +
|
|
127
|
+
'<image x="1012" y="40" width="124" height="124" clip-path="url(#blogo)" href="' + brandURI + '"/>' : '';
|
|
128
|
+
return '<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="850" viewBox="0 0 1200 850">' +
|
|
129
|
+
'<rect width="1200" height="850" rx="28" fill="' + pal.bg + '"/>' +
|
|
130
|
+
'<path d="M1090 700 L980 800 h70 l-8 60 80 -96 h-70 l8 -64 z" fill="none" stroke="' + pal.accent + '" stroke-width="14" opacity="0.08" stroke-linejoin="round"/>' +
|
|
131
|
+
'<text x="64" y="80" font-family="monospace" font-size="26" letter-spacing="6" fill="' + pal.accent + '">TERSIO · USAGE PROFILE</text>' +
|
|
132
|
+
'<text x="60" y="250" font-family="monospace" font-size="130" font-weight="bold" fill="' + pal.ink + '">' + e(fmtShort(s.total) + ' tokens') + '</text>' +
|
|
133
|
+
'<text x="64" y="300" font-family="monospace" font-size="30" fill="' + pal.dim + '">across ' + e(fmt(s.runs)) + ' agent runs</text>' +
|
|
134
|
+
grid +
|
|
135
|
+
'<text x="64" y="680" font-family="monospace" font-size="24" letter-spacing="3" fill="' + pal.dim + '">AVG / RUN</text>' +
|
|
136
|
+
'<text x="64" y="725" font-family="monospace" font-size="40" font-weight="bold" fill="' + pal.ink + '">' + e(fmtShort(s.avg) + ' / run') + '</text>' +
|
|
137
|
+
'<text x="430" y="680" font-family="monospace" font-size="24" letter-spacing="3" fill="' + pal.dim + '">SAVED</text>' +
|
|
138
|
+
'<text x="430" y="725" font-family="monospace" font-size="40" font-weight="bold" fill="' + pal.ink + '">' + e(fxMoney(s.saved)) + '</text>' +
|
|
139
|
+
'<text x="830" y="680" font-family="monospace" font-size="24" letter-spacing="3" fill="' + pal.dim + '">DAY STREAK</text>' +
|
|
140
|
+
'<text x="830" y="725" font-family="monospace" font-size="40" font-weight="bold" fill="' + pal.ink + '">' + e(streakTxt) + '</text>' +
|
|
141
|
+
'<text x="64" y="775" font-family="monospace" font-size="24" letter-spacing="3" fill="' + pal.dim + '">BEST DAY</text>' +
|
|
142
|
+
'<text x="64" y="820" font-family="monospace" font-size="40" font-weight="bold" fill="' + pal.ink + '">' + e(fmtShort(s.best)) + '</text>' +
|
|
143
|
+
'<text x="430" y="775" font-family="monospace" font-size="24" letter-spacing="3" fill="' + pal.dim + '">EST. COST</text>' +
|
|
144
|
+
'<text x="430" y="820" font-family="monospace" font-size="40" font-weight="bold" fill="' + pal.ink + '">' + e(fxMoney(s.cost)) + '</text>' +
|
|
145
|
+
'<text x="830" y="775" font-family="monospace" font-size="24" letter-spacing="3" fill="' + pal.dim + '">MODELS</text>' +
|
|
146
|
+
'<text x="830" y="820" font-family="monospace" font-size="40" font-weight="bold" fill="' + pal.ink + '">' + e(s.models) + '</text>' +
|
|
147
|
+
logo + '</svg>';
|
|
148
|
+
}
|
|
149
|
+
function pngBlob(cb) {
|
|
150
|
+
try {
|
|
151
|
+
var img = new Image();
|
|
152
|
+
var svg = new Blob([svgCard()], { type: 'image/svg+xml;charset=utf-8' });
|
|
153
|
+
var url = URL.createObjectURL(svg);
|
|
154
|
+
img.onload = function() {
|
|
155
|
+
try {
|
|
156
|
+
var c = document.createElement('canvas');
|
|
157
|
+
c.width = 1200; c.height = 850;
|
|
158
|
+
c.getContext('2d').drawImage(img, 0, 0, 1200, 850);
|
|
159
|
+
URL.revokeObjectURL(url);
|
|
160
|
+
if (c.toBlob) c.toBlob(function(b) { cb(b); }, 'image/png');
|
|
161
|
+
else cb(null);
|
|
162
|
+
} catch (e) { cb(null); }
|
|
163
|
+
};
|
|
164
|
+
img.onerror = function() { URL.revokeObjectURL(url); cb(null); };
|
|
165
|
+
img.src = url;
|
|
166
|
+
} catch (e) { cb(null); }
|
|
167
|
+
}
|
|
168
|
+
function copyImage(into) {
|
|
169
|
+
pngBlob(function(b) {
|
|
170
|
+
if (!b || !navigator.clipboard || !window.ClipboardItem) {
|
|
171
|
+
copyText(shareText(), 'Share text copied (image copy unsupported here).');
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
navigator.clipboard.write([new ClipboardItem({ 'image/png': b })]).then(function() {
|
|
175
|
+
toast('Image copied', into, 'image');
|
|
176
|
+
}, function() {
|
|
177
|
+
copyText(shareText(), 'Share text copied (image copy blocked).');
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
document.getElementById('shareX').addEventListener('click', function() {
|
|
182
|
+
window.open('https://x.com/intent/post?text=' + encodeURIComponent(shareText() + ' #Tersio'), '_blank', 'noopener,width=560,height=460');
|
|
183
|
+
});
|
|
184
|
+
document.getElementById('shareReddit').addEventListener('click', function() {
|
|
185
|
+
window.open('https://www.reddit.com/submit?title=' + encodeURIComponent('My Tersio usage profile') + '&text=' + encodeURIComponent(shareText()), '_blank', 'noopener');
|
|
186
|
+
});
|
|
187
|
+
document.getElementById('shareCopy').addEventListener('click', function() { copyImage('Card copied — paste it straight into your post.'); });
|
|
188
|
+
document.getElementById('shareLinkedIn').addEventListener('click', function() {
|
|
189
|
+
window.open('https://www.linkedin.com/feed/', '_blank', 'noopener');
|
|
190
|
+
copyImage('Image copied — paste it into the LinkedIn composer.');
|
|
191
|
+
});
|
|
192
|
+
document.getElementById('sharePng').addEventListener('click', function() {
|
|
193
|
+
pngBlob(function(b) {
|
|
194
|
+
if (!b) { toast('Save failed', 'Browser blocked the render.', 'circle-alert'); return; }
|
|
195
|
+
try {
|
|
196
|
+
var a = document.createElement('a');
|
|
197
|
+
a.download = 'tersio-usage.png';
|
|
198
|
+
a.href = URL.createObjectURL(b);
|
|
199
|
+
a.click();
|
|
200
|
+
setTimeout(function() { URL.revokeObjectURL(a.href); }, 5000);
|
|
201
|
+
toast('Saved', 'Card downloaded as PNG.', 'download');
|
|
202
|
+
} catch (e) { toast('Save failed', 'Browser blocked the render.', 'circle-alert'); }
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
T.onRender(paint);
|
|
206
|
+
paint();
|
|
207
|
+
var sh = document.getElementById('shareDialog');
|
|
208
|
+
if (sh && typeof sh.showModal === 'function') {
|
|
209
|
+
document.getElementById('shareBtn').addEventListener('click', function() { paint(); sh.showModal(); });
|
|
210
|
+
document.getElementById('shareClose').addEventListener('click', function() { sh.close(); });
|
|
211
|
+
sh.addEventListener('click', function(ev) { if (ev.target === sh) sh.close(); });
|
|
212
|
+
sh.addEventListener('close', function() { document.body.style.overflow = ''; });
|
|
213
|
+
new MutationObserver(function() {
|
|
214
|
+
if (sh.open) document.body.style.overflow = 'hidden';
|
|
215
|
+
}).observe(sh, { attributes: true, attributeFilter: ['open'] });
|
|
216
|
+
}
|
|
217
|
+
})();
|
|
218
|
+
})();
|
package/dashboard/styles.css
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
--cell-1: rgba(4, 120, 87, .3); --cell-2: rgba(4, 120, 87, .55);
|
|
7
7
|
--cell-3: rgba(4, 120, 87, .8); --cell-4: #047857;
|
|
8
8
|
--danger: #dc2626; --danger-border: rgba(220, 38, 38, .35); --danger-soft: rgba(220, 38, 38, .05);
|
|
9
|
+
--warn-soft: rgba(217, 119, 6, .1); --warn-border: rgba(217, 119, 6, .4);
|
|
9
10
|
--shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.07);
|
|
10
11
|
}
|
|
11
12
|
html[data-theme="light"] { color-scheme: light; }
|
|
@@ -42,17 +43,8 @@
|
|
|
42
43
|
--shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.55);
|
|
43
44
|
}
|
|
44
45
|
html[data-theme="dark"] .modelsSection {
|
|
45
|
-
|
|
46
|
-
--ink: #18181b;
|
|
47
|
-
--dim: #52525b;
|
|
48
|
-
--line: rgba(24, 24, 27, .12);
|
|
49
|
-
--track: rgba(24, 24, 27, .08);
|
|
50
|
-
--cell-0: rgba(24, 24, 27, .07);
|
|
51
|
-
--accent-soft: rgba(4, 120, 87, .12);
|
|
52
|
-
--accent: #047857;
|
|
53
|
-
color-scheme: light;
|
|
46
|
+
color-scheme: dark;
|
|
54
47
|
}
|
|
55
|
-
html[data-theme="dark"] .modelsSection .brandmark.placeholder { background: #fff !important; color: #000 !important; }
|
|
56
48
|
.ticker { animation: tick 36s linear infinite; will-change: transform; }
|
|
57
49
|
.ticker:hover { animation-play-state: paused; }
|
|
58
50
|
@keyframes tick { from { transform: translateX(0); } to { transform: translateX(-50%); } }
|
|
@@ -144,7 +136,6 @@
|
|
|
144
136
|
.cell.l3 { background: var(--cell-3); } .cell.l4 { background: var(--cell-4); }
|
|
145
137
|
.brandmark { width: 34px; height: 34px; border-radius: 10px; display: grid; place-items: center; flex-shrink: 0; color: #fff; }
|
|
146
138
|
.brandmark.placeholder { background: transparent !important; border: 1px solid var(--line); color: var(--dim); }
|
|
147
|
-
html[data-theme="dark"] .brandmark.placeholder { background: #fff !important; color: #000 !important; }
|
|
148
139
|
.brandmark img { width: 18px; height: 18px; }
|
|
149
140
|
.brandmark svg { width: 18px; height: 18px; display: block; }
|
|
150
141
|
.brandmark.sm { width: 28px; height: 28px; border-radius: 8px; }
|
|
@@ -206,6 +197,7 @@ html[data-theme="dark"] .brandmark.placeholder { background: #fff !important; co
|
|
|
206
197
|
.live-dot { animation: none; }
|
|
207
198
|
.tilt, .btn-push, .mrow, .bar-fill { transition: none; }
|
|
208
199
|
.hero-glow, .ticker { animation: none; }
|
|
200
|
+
.skel { animation: none; }
|
|
209
201
|
}
|
|
210
202
|
/* Empty states (shadcn pattern): dashed well, icon chip, title + hint. */
|
|
211
203
|
.empty { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 2px; border: 1px dashed var(--line); border-radius: 12px; padding: 32px 16px; }
|
|
@@ -236,6 +228,85 @@ html[data-theme="dark"] .brandmark.placeholder { background: #fff !important; co
|
|
|
236
228
|
#reset:hover { border-color: var(--danger); box-shadow: 0 0 14px var(--danger-border); }
|
|
237
229
|
.dlg .seg button { display: grid; place-items: center; }
|
|
238
230
|
|
|
231
|
+
/* Settings modal (sidebar + panes). Wide like the app settings panels. */
|
|
232
|
+
.dlg.setdlg { width: min(920px, calc(100vw - 32px)); max-height: min(88vh, 860px); padding: 0; overflow: hidden; display: none; }
|
|
233
|
+
.dlg.setdlg[open] { display: block; }
|
|
234
|
+
.set-shell { display: grid; grid-template-columns: 240px minmax(0, 1fr); min-height: min(70vh, 700px); max-height: inherit; }
|
|
235
|
+
.set-side { border-right: 1px solid var(--line); padding: 16px 12px; display: flex; flex-direction: column; gap: 10px; min-height: 0; background: var(--panel); }
|
|
236
|
+
.set-search { display: flex; align-items: center; gap: 8px; border: 1px solid var(--line); border-radius: 10px; padding: 8px 10px; color: var(--dim); }
|
|
237
|
+
.set-search input { all: unset; flex: 1; min-width: 0; font-size: 13px; color: var(--ink); }
|
|
238
|
+
.set-nav { display: grid; gap: 2px; overflow-y: auto; }
|
|
239
|
+
.set-navitem { display: flex; align-items: center; gap: 10px; width: 100%; padding: 9px 10px; border: 0; border-radius: 10px; background: transparent; color: var(--ink); font-size: 13px; cursor: pointer; text-align: left; }
|
|
240
|
+
.set-navitem:hover { background: var(--track); }
|
|
241
|
+
.set-navitem.on { background: var(--track); font-weight: 600; }
|
|
242
|
+
.set-navitem.hide { display: none; }
|
|
243
|
+
.set-ver { margin-top: auto; font-size: 11px; color: var(--dim); padding: 0 10px; }
|
|
244
|
+
.set-main { position: relative; display: flex; flex-direction: column; min-width: 0; min-height: 0; }
|
|
245
|
+
.set-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 16px 20px 0; }
|
|
246
|
+
.set-pane > .dlg-row:first-child { margin-top: 14px; }
|
|
247
|
+
.set-panes { overflow-y: auto; min-height: 0; padding: 0 20px 20px; overscroll-behavior: contain; scrollbar-width: thin; scrollbar-color: var(--line) transparent; }
|
|
248
|
+
.set-pane { display: none; }
|
|
249
|
+
.set-pane.on { display: block; animation: set-in .16s ease-out; }
|
|
250
|
+
@keyframes set-in { from { opacity: 0; transform: translateY(6px); } }
|
|
251
|
+
.set-note { margin: 2px 0 10px; }
|
|
252
|
+
.set-list { display: grid; gap: 2px; }
|
|
253
|
+
.set-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 2px; border-bottom: 1px solid var(--line); font-size: 14px; }
|
|
254
|
+
.set-row:last-child { border-bottom: 0; }
|
|
255
|
+
.set-row .k { font-weight: 600; }
|
|
256
|
+
.set-row .s { font-size: 12px; color: var(--dim); margin: 2px 0 0; }
|
|
257
|
+
.set-row .v { display: flex; align-items: center; gap: 8px; color: var(--dim); font-size: 12px; white-space: nowrap; }
|
|
258
|
+
.set-dot { width: 8px; height: 8px; border-radius: 999px; background: var(--track); flex-shrink: 0; }
|
|
259
|
+
.set-dot.ok { background: var(--accent); }
|
|
260
|
+
.set-dot.bad { background: var(--danger); }
|
|
261
|
+
button.set-row { width: 100%; background: transparent; border: 0; border-bottom: 1px solid var(--line); border-radius: 0; cursor: pointer; text-align: left; color: var(--ink); }
|
|
262
|
+
button.set-row:hover { background: var(--track); }
|
|
263
|
+
button.set-row[aria-selected="true"] .v { color: var(--accent); }
|
|
264
|
+
.selwrap { position: relative; flex-shrink: 0; }
|
|
265
|
+
.selbtn { display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--line); background: var(--panel); color: var(--ink); border-radius: 10px; padding: 7px 10px; font-size: 12px; cursor: pointer; }
|
|
266
|
+
.selbtn:hover { border-color: var(--accent); }
|
|
267
|
+
.selbtn svg { transition: transform .15s; }
|
|
268
|
+
.selbtn[aria-expanded="true"] svg { transform: rotate(180deg); }
|
|
269
|
+
.selpanel { position: fixed; z-index: 200; min-width: 148px; max-height: 260px; overflow-y: auto; padding: 4px; border-radius: 10px; border: 1px solid var(--line); background: var(--panel); box-shadow: 0 8px 28px rgba(0, 0, 0, .28); }
|
|
270
|
+
.selopt { display: flex; align-items: center; gap: 8px; width: 100%; padding: 7px 8px; border-radius: 7px; font-size: 12px; cursor: pointer; color: var(--ink); background: transparent; border: 0; text-align: left; }
|
|
271
|
+
.selopt:hover { background: var(--accent-soft); }
|
|
272
|
+
.selopt .tick { margin-left: auto; color: var(--accent); visibility: hidden; flex-shrink: 0; }
|
|
273
|
+
.selopt[aria-selected="true"] .tick { visibility: visible; }
|
|
274
|
+
.selopt[aria-selected="true"] { color: var(--accent); }
|
|
275
|
+
.set-list.set-scroll { max-height: 300px; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; scrollbar-color: var(--line) transparent; border: 1px solid var(--line); border-radius: 12px; padding: 4px 12px; }
|
|
276
|
+
#setDoctor { max-height: 380px; overflow-y: auto; min-height: 0; overscroll-behavior: contain; scrollbar-width: thin; scrollbar-color: var(--line) transparent; }
|
|
277
|
+
.set-group { font-size: 11px; text-transform: uppercase; letter-spacing: .14em; color: var(--dim); margin: 12px 0 2px; }
|
|
278
|
+
.set-group:first-child { margin-top: 4px; }
|
|
279
|
+
.share-card { position: relative; overflow: hidden; border: 1px solid var(--line); border-radius: 12px; padding: 20px; background: var(--bg); }
|
|
280
|
+
.share-logo { position: absolute; top: 16px; right: 16px; width: 44px; height: 44px; border-radius: 12px; }
|
|
281
|
+
.dlg.shdlg { width: min(660px, calc(100vw - 32px)); max-height: min(92vh, 940px); overflow-y: auto; }
|
|
282
|
+
.sh-glow { position: absolute; left: 50%; top: 0; width: min(480px, 90%); height: 180px; transform: translate(-50%, -40%); pointer-events: none; background: radial-gradient(ellipse at center, var(--accent-soft) 0%, transparent 65%); }
|
|
283
|
+
.sh-heat { display: grid; grid-template-columns: repeat(26, minmax(0, 1fr)); gap: 3px; margin-top: 16px; }
|
|
284
|
+
.sh-heat .cell { aspect-ratio: 1 / 1; width: 100%; min-width: 0; }
|
|
285
|
+
.sh-card { margin-top: 14px; }
|
|
286
|
+
.sh-card .ghosticon { width: 120px; height: 120px; right: -8px; bottom: -14px; }
|
|
287
|
+
.sh-card .ghosticon svg { width: 110px; height: 110px; }
|
|
288
|
+
.share-kicker { font-size: 11px; text-transform: uppercase; letter-spacing: .18em; color: var(--accent); margin: 0; }
|
|
289
|
+
.share-total { font-size: clamp(2rem, 5vw, 3rem); font-weight: 800; letter-spacing: -0.03em; margin: 6px 0 0; font-variant-numeric: tabular-nums; }
|
|
290
|
+
.share-sub { font-size: 12px; color: var(--dim); margin: 4px 0 0; }
|
|
291
|
+
.share-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; margin-top: 14px; }
|
|
292
|
+
.share-mini-k { font-size: 10px; text-transform: uppercase; letter-spacing: .14em; color: var(--dim); margin: 0; }
|
|
293
|
+
.share-mini-v { font-size: 15px; font-weight: 700; margin: 2px 0 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-variant-numeric: tabular-nums; }
|
|
294
|
+
.share-btn { display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--line); background: transparent; color: var(--ink); border-radius: 10px; padding: 7px 12px; font-size: 12px; cursor: pointer; }
|
|
295
|
+
.share-btn:hover { border-color: var(--accent); }
|
|
296
|
+
.share-btn.share-icon { padding: 7px 9px; }
|
|
297
|
+
.skel-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 2px; border-bottom: 1px solid var(--line); }
|
|
298
|
+
.skel-col { flex: 1; min-width: 0; }
|
|
299
|
+
.skel { display: block; border-radius: 6px; background: var(--track); animation: skel-pulse 1.2s ease-in-out infinite; }
|
|
300
|
+
.skel.t { height: 14px; width: 42%; }
|
|
301
|
+
.skel.s { height: 11px; width: 64%; margin-top: 6px; }
|
|
302
|
+
.skel.v { height: 12px; width: 64px; flex-shrink: 0; }
|
|
303
|
+
@keyframes skel-pulse { 0%, 100% { opacity: .45; } 50% { opacity: 1; } }
|
|
304
|
+
.set-pane[data-pane="diagnosis"] .dlg-row { margin-bottom: 14px; }
|
|
305
|
+
.set-fixrow { display: flex; align-items: center; gap: 10px; margin-top: 12px; }
|
|
306
|
+
.set-fixhint { font-size: 11px; color: var(--dim); }
|
|
307
|
+
.set-fixbox { position: sticky; bottom: 0; display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 12px; border: 1px solid var(--warn-border); border-radius: 12px; padding: 12px 14px; background: var(--warn-soft); }
|
|
308
|
+
@media (max-width: 640px) { .set-shell { grid-template-columns: minmax(0, 1fr); } .set-side { border-right: 0; border-bottom: 1px solid var(--line); } }
|
|
309
|
+
|
|
239
310
|
/* Toaster (shadcn/ui sonner-style, dependency-free) */
|
|
240
311
|
.toaster { position: fixed; inset: auto 16px 16px auto; z-index: 100; display: flex; flex-direction: column; gap: 8px; width: min(360px, calc(100vw - 32px)); border: 0; padding: 0; background: transparent; overflow: visible; }
|
|
241
312
|
.toast { display: flex; align-items: flex-start; gap: 10px; padding: 12px 14px; border: 1px solid var(--line); background: var(--panel); border-radius: 12px; box-shadow: var(--shadow-xs), 0 8px 24px rgba(0, 0, 0, .22); animation: toast-in .18s ease-out; }
|
|
@@ -273,7 +344,11 @@ html[data-theme="dark"] .brandmark.placeholder { background: #fff !important; co
|
|
|
273
344
|
#mdSpark .line { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
|
|
274
345
|
#mdSpark .dot { fill: var(--accent); stroke: var(--panel); stroke-width: 2; }
|
|
275
346
|
#mdSpark .hoverdot { fill: var(--accent); stroke: var(--panel); stroke-width: 2; }
|
|
276
|
-
.md-tip { position: absolute; pointer-events: none; background: var(--panel); color: var(--ink); border: 1px solid var(--line); box-shadow: 0 8px 24px rgba(0, 0, 0, .28); border-radius:
|
|
347
|
+
.md-tip { position: absolute; pointer-events: none; background: var(--panel); color: var(--ink); border: 1px solid var(--line); box-shadow: 0 8px 24px rgba(0, 0, 0, .28); border-radius: 10px; padding: 10px 12px; font-size: 11px; line-height: 1.5; white-space: nowrap; max-width: calc(100% - 28px); overflow: hidden; text-overflow: ellipsis; transform: translate(-50%, -110%); display: none; z-index: 5; font-variant-numeric: tabular-nums; }
|
|
348
|
+
.md-tip .tt { font-size: 10px; text-transform: uppercase; letter-spacing: .12em; color: var(--dim); }
|
|
349
|
+
.md-tip .tv { font-size: 14px; font-weight: 800; margin-top: 2px; }
|
|
350
|
+
.md-legend .row { cursor: default; border-radius: 7px; padding: 2px 4px; margin: 0 -4px; }
|
|
351
|
+
.md-legend .row:hover { background: var(--track); }
|
|
277
352
|
.md-cols { display: grid; grid-template-columns: minmax(0, 1fr); grid-auto-flow: dense; gap: 12px; }
|
|
278
353
|
@media (min-width: 720px) { .md-cols { grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); } }
|
|
279
354
|
.md-months { display: flex; justify-content: space-between; margin-top: 6px; font-size: 10px; color: var(--dim); text-transform: uppercase; letter-spacing: .12em; }
|
package/dashboard/template.html
CHANGED
|
@@ -36,68 +36,128 @@
|
|
|
36
36
|
<span id="clock">--:--</span>
|
|
37
37
|
</span>
|
|
38
38
|
<span class="hidden sm:block w-px h-5 shrink-0" style="background: var(--line)" aria-hidden="true"></span>
|
|
39
|
+
<button id="shareBtn" class="btn-push flex shrink-0 items-center p-2 rounded-xl" style="border: 1px solid var(--line); color: var(--ink)" aria-label="Share your usage">
|
|
40
|
+
<i data-lucide="share-2" class="size-4"></i>
|
|
41
|
+
</button>
|
|
39
42
|
<button id="settingsBtn" class="btn-push flex shrink-0 items-center p-2 rounded-xl" style="border: 1px solid var(--line); color: var(--ink)" aria-label="Open settings">
|
|
40
43
|
<i data-lucide="settings" class="size-4"></i>
|
|
41
44
|
</button>
|
|
42
45
|
</header>
|
|
43
46
|
|
|
44
|
-
<!-- Settings
|
|
45
|
-
<dialog id="settings" class="dlg" aria-labelledby="settingsTitle">
|
|
46
|
-
<div class="
|
|
47
|
-
<
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
<p class="
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
<
|
|
96
|
-
|
|
47
|
+
<!-- Settings modal (ChatGPT pattern): sidebar nav + content panes -->
|
|
48
|
+
<dialog id="settings" class="dlg setdlg" aria-labelledby="settingsTitle">
|
|
49
|
+
<div class="set-shell">
|
|
50
|
+
<aside class="set-side" aria-label="Settings sections">
|
|
51
|
+
<div class="set-search">
|
|
52
|
+
<i data-lucide="search" class="size-4"></i>
|
|
53
|
+
<input id="setSearch" type="search" placeholder="Search settings" aria-label="Search settings" autocomplete="off">
|
|
54
|
+
</div>
|
|
55
|
+
<nav class="set-nav" id="setNav" aria-label="Settings">
|
|
56
|
+
<button class="set-navitem on" data-pane="general"><i data-lucide="settings" class="size-4"></i><span>General</span></button>
|
|
57
|
+
<button class="set-navitem" data-pane="connection"><i data-lucide="plug" class="size-4"></i><span>Connection</span></button>
|
|
58
|
+
<button class="set-navitem" data-pane="diagnosis"><i data-lucide="stethoscope" class="size-4"></i><span>Diagnosis</span></button>
|
|
59
|
+
<button class="set-navitem" data-pane="data"><i data-lucide="database" class="size-4"></i><span>Data</span></button>
|
|
60
|
+
</nav>
|
|
61
|
+
<p class="mono set-ver" id="verChip">tersio</p>
|
|
62
|
+
</aside>
|
|
63
|
+
<div class="set-main">
|
|
64
|
+
<div class="set-head">
|
|
65
|
+
<p class="dlg-title" id="settingsTitle">General</p>
|
|
66
|
+
<button id="settingsClose" class="btn-push flex shrink-0 items-center p-2 rounded-xl" style="border: 1px solid var(--line); color: var(--ink)" aria-label="Close settings">
|
|
67
|
+
<i data-lucide="x" class="size-4"></i>
|
|
68
|
+
</button>
|
|
69
|
+
</div>
|
|
70
|
+
<div class="set-panes">
|
|
71
|
+
<section class="set-pane on" data-pane="general" aria-label="General">
|
|
72
|
+
<p class="dlg-label">Appearance</p>
|
|
73
|
+
<div class="dlg-row">
|
|
74
|
+
<div class="min-w-0">
|
|
75
|
+
<p class="dlg-name">Theme</p>
|
|
76
|
+
<p class="dlg-hint">Light, dark, or follow the system.</p>
|
|
77
|
+
</div>
|
|
78
|
+
<div class="seg mono text-xs flex items-center gap-1 p-1 rounded-[10px]" style="border: 1px solid var(--line); background: var(--panel)" role="tablist" aria-label="Theme">
|
|
79
|
+
<button data-theme-val="light" class="px-2.5 py-1.5" role="tab" aria-label="Light" title="Light"><i data-lucide="sun" class="size-4"></i></button>
|
|
80
|
+
<button data-theme-val="dark" class="px-2.5 py-1.5" role="tab" aria-label="Dark" title="Dark"><i data-lucide="moon" class="size-4"></i></button>
|
|
81
|
+
<button data-theme-val="system" class="px-2.5 py-1.5" role="tab" aria-label="System" title="System"><i data-lucide="monitor" class="size-4"></i></button>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
<div class="dlg-row">
|
|
85
|
+
<div class="min-w-0">
|
|
86
|
+
<p class="dlg-name">Currency</p>
|
|
87
|
+
<p class="dlg-hint">Display currency for cost figures. Persists to tersio settings.</p>
|
|
88
|
+
</div>
|
|
89
|
+
<span class="selwrap">
|
|
90
|
+
<button id="setCurBtn" class="selbtn mono" aria-haspopup="listbox" aria-expanded="false"><span id="setCurLabel">USD</span><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></button>
|
|
91
|
+
<span id="setCurPanel" class="selpanel hidden" role="listbox" aria-label="Display currency"></span>
|
|
92
|
+
</span>
|
|
93
|
+
</div>
|
|
94
|
+
</section>
|
|
95
|
+
<section class="set-pane" data-pane="connection" aria-label="Connection">
|
|
96
|
+
<div class="set-list" id="setHealth"></div>
|
|
97
|
+
</section>
|
|
98
|
+
<section class="set-pane" data-pane="diagnosis" aria-label="Diagnosis">
|
|
99
|
+
<p class="dlg-hint set-note">What works and what does not. Repair with <span class="mono">tersio doctor --fix</span>.</p>
|
|
100
|
+
<div class="dlg-row">
|
|
101
|
+
<div class="min-w-0">
|
|
102
|
+
<p class="dlg-name">Auto-check</p>
|
|
103
|
+
<p class="dlg-hint" id="setDiagChecked">Never checked.</p>
|
|
104
|
+
</div>
|
|
105
|
+
<span class="selwrap">
|
|
106
|
+
<button id="setSchedBtn" class="selbtn mono" aria-haspopup="listbox" aria-expanded="false"><span id="setSchedLabel">Manual</span><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></button>
|
|
107
|
+
<span id="setSchedPanel" class="selpanel hidden" role="listbox" aria-label="Diagnosis schedule"></span>
|
|
108
|
+
</span>
|
|
109
|
+
</div>
|
|
110
|
+
<div class="dlg-row">
|
|
111
|
+
<div class="min-w-0">
|
|
112
|
+
<p class="dlg-name">Scan</p>
|
|
113
|
+
<p class="dlg-hint">Runs every check fresh and refreshes the list below.</p>
|
|
114
|
+
</div>
|
|
115
|
+
<button id="setDiagRefresh" 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="Scan now">
|
|
116
|
+
<i data-lucide="scan-line" class="size-3.5"></i><span>Scan</span>
|
|
117
|
+
</button>
|
|
118
|
+
</div>
|
|
119
|
+
<div class="set-list" id="setDoctor"></div>
|
|
120
|
+
<div class="set-fixbox">
|
|
121
|
+
<div class="min-w-0">
|
|
122
|
+
<p class="dlg-name">Fix issues</p>
|
|
123
|
+
<p class="dlg-hint">Repairs extension files, config registrations, and the bundled ponytail copy. RTK binary and CLI update stay manual.</p>
|
|
124
|
+
</div>
|
|
125
|
+
<button id="setFixIssues" 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="Fix diagnosed issues">
|
|
126
|
+
<i data-lucide="wrench" class="size-3.5"></i><span id="setFixLabel">Fix issues</span>
|
|
127
|
+
</button>
|
|
128
|
+
</div>
|
|
129
|
+
</section>
|
|
130
|
+
</section>
|
|
131
|
+
<section class="set-pane" data-pane="data" aria-label="Data">
|
|
132
|
+
<div class="dlg-row">
|
|
133
|
+
<div class="min-w-0">
|
|
134
|
+
<p class="dlg-name">Reload</p>
|
|
135
|
+
<p class="dlg-hint">Re-read statistics from disk.</p>
|
|
136
|
+
</div>
|
|
137
|
+
<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">
|
|
138
|
+
<i data-lucide="refresh-cw" class="size-3.5"></i><span>Reload</span>
|
|
139
|
+
</button>
|
|
140
|
+
</div>
|
|
141
|
+
<div class="dlg-paths">
|
|
142
|
+
<div class="dlg-path">
|
|
143
|
+
<p class="dlg-name">Usage DB <span class="dlg-own">tersio-owned · local hosted</span></p>
|
|
144
|
+
<p class="mono dlg-file" id="pathUsageDb" title="">–</p>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
<div class="dlg-danger">
|
|
148
|
+
<p class="dlg-label">Danger zone</p>
|
|
149
|
+
<div class="dlg-row">
|
|
150
|
+
<div class="min-w-0">
|
|
151
|
+
<p class="dlg-name">Reset statistics</p>
|
|
152
|
+
<p class="dlg-hint">Clears tersio statistics. Host-owned files stay intact. Click twice to confirm.</p>
|
|
153
|
+
</div>
|
|
154
|
+
<button id="reset" 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(--danger-border)" aria-label="Reset tersio statistics">
|
|
155
|
+
<i data-lucide="rotate-ccw" class="size-3.5"></i><span id="resetLabel">Reset</span>
|
|
156
|
+
</button>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
</section>
|
|
97
160
|
</div>
|
|
98
|
-
<button id="reset" 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(--danger-border)" aria-label="Reset tersio statistics">
|
|
99
|
-
<i data-lucide="rotate-ccw" class="size-3.5"></i><span id="resetLabel">Reset</span>
|
|
100
|
-
</button>
|
|
101
161
|
</div>
|
|
102
162
|
</div>
|
|
103
163
|
</dialog>
|
|
@@ -392,6 +452,47 @@
|
|
|
392
452
|
<p class="mono text-[11px] mt-3" style="color: var(--dim)">bash commands run through the rtk-wired OMP hook meter automatically; rows without metering show –. caveman + ponytail gains are instruction-following, bench-measured in BENCHMARK.md.</p>
|
|
393
453
|
</section>
|
|
394
454
|
|
|
455
|
+
<!-- Share dialog: stats card preview + social actions -->
|
|
456
|
+
<dialog id="shareDialog" class="dlg shdlg" aria-labelledby="shareTitle">
|
|
457
|
+
<div class="sh-glow" aria-hidden="true"></div>
|
|
458
|
+
<div class="dlg-head">
|
|
459
|
+
<div class="min-w-0">
|
|
460
|
+
<p class="dlg-title" id="shareTitle">Share your usage</p>
|
|
461
|
+
<p class="dlg-desc">Download the card or post your stats.</p>
|
|
462
|
+
</div>
|
|
463
|
+
<button id="shareClose" class="btn-push flex shrink-0 items-center p-2 rounded-xl" style="border: 1px solid var(--line); color: var(--ink)" aria-label="Close share">
|
|
464
|
+
<i data-lucide="x" class="size-4"></i>
|
|
465
|
+
</button>
|
|
466
|
+
</div>
|
|
467
|
+
<div class="share-card sh-card" aria-hidden="false">
|
|
468
|
+
<span class="ghosticon" aria-hidden="true"><i data-lucide="zap"></i></span>
|
|
469
|
+
<img src="brand.webp" alt="" width="44" height="44" class="share-logo" aria-hidden="true">
|
|
470
|
+
<p class="mono share-kicker">tersio · usage profile</p>
|
|
471
|
+
<p class="mono share-total" id="shareTotal">-</p>
|
|
472
|
+
<p class="mono share-sub" id="shareRuns">-</p>
|
|
473
|
+
<div class="sh-heat" id="shareHeat" aria-hidden="true"></div>
|
|
474
|
+
<div class="share-grid mono">
|
|
475
|
+
<div><p class="share-mini-k">avg / run</p><p class="share-mini-v" id="shareAvg">-</p></div>
|
|
476
|
+
<div><p class="share-mini-k">saved</p><p class="share-mini-v" id="shareSaved">-</p></div>
|
|
477
|
+
<div><p class="share-mini-k">day streak</p><p class="share-mini-v" id="shareStreak">-</p></div>
|
|
478
|
+
<div><p class="share-mini-k">best day</p><p class="share-mini-v" id="shareBest">-</p></div>
|
|
479
|
+
<div><p class="share-mini-k">est. cost</p><p class="share-mini-v" id="shareCost">-</p></div>
|
|
480
|
+
<div><p class="share-mini-k">models</p><p class="share-mini-v" id="shareModels">-</p></div>
|
|
481
|
+
</div>
|
|
482
|
+
</div>
|
|
483
|
+
<div class="mono text-xs mt-4 flex flex-wrap items-center gap-2">
|
|
484
|
+
<span class="flex items-center gap-2">
|
|
485
|
+
<button id="shareX" class="btn-push share-btn share-icon" aria-label="Share on X" title="Share on X"><svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg></button>
|
|
486
|
+
<button id="shareReddit" class="btn-push share-btn share-icon" aria-label="Share on Reddit" title="Share on Reddit"><svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0zm5.01 4.744c.688 0 1.25.561 1.25 1.249a1.25 1.25 0 0 1-2.498.056l-2.597-.547-.8 3.747c1.824.07 3.48.632 4.674 1.488.308-.309.73-.491 1.207-.491.968 0 1.754.786 1.754 1.754 0 .716-.435 1.333-1.01 1.614a3.111 3.111 0 0 1 .042.52c0 2.694-3.13 4.87-7.004 4.87-3.874 0-7.004-2.176-7.004-4.87 0-.183.015-.366.043-.534A1.748 1.748 0 0 1 4.028 12c0-.968.786-1.754 1.754-1.754.463 0 .898.196 1.207.49 1.207-.883 2.878-1.43 4.744-1.487l.885-4.182a.342.342 0 0 1 .14-.197.35.35 0 0 1 .238-.042l2.906.617a1.214 1.214 0 0 1 1.108-.701zM9.25 12C8.561 12 8 12.562 8 13.25c0 .687.561 1.248 1.25 1.248.687 0 1.248-.561 1.248-1.249 0-.688-.561-1.249-1.249-1.249zm5.5 0c-.687 0-1.248.561-1.248 1.25 0 .687.561 1.248 1.249 1.248.688 0 1.249-.561 1.249-1.249 0-.688-.562-1.249-1.25-1.249zm-5.466 3.99a.327.327 0 0 0-.231.094.33.33 0 0 0 0 .463c.842.842 2.484.913 2.961.913.477 0 2.105-.056 2.961-.913a.361.361 0 0 0 .029-.463.33.33 0 0 0-.464 0c-.547.533-1.684.73-2.512.73-.828 0-1.979-.196-2.512-.73a.326.326 0 0 0-.232-.095z"/></svg></button>
|
|
487
|
+
<button id="shareLinkedIn" class="btn-push share-btn share-icon" aria-label="Share on LinkedIn" title="Share on LinkedIn"><svg width="14" height="14" 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></button>
|
|
488
|
+
</span>
|
|
489
|
+
<span class="flex items-center gap-2 ml-auto">
|
|
490
|
+
<button id="shareCopy" class="btn-push share-btn" aria-label="Copy card image"><i data-lucide="copy" class="size-3.5"></i><span>Copy</span></button>
|
|
491
|
+
<button id="sharePng" class="btn-push share-btn" aria-label="Download card as PNG"><i data-lucide="download" class="size-3.5"></i><span>Download</span></button>
|
|
492
|
+
</span>
|
|
493
|
+
</div>
|
|
494
|
+
</dialog>
|
|
495
|
+
|
|
395
496
|
<footer class="mono text-xs mt-12 pt-4" style="color: var(--dim); border-top: 1px solid var(--line)">
|
|
396
497
|
<div class="flex flex-wrap items-center gap-x-4 gap-y-3">
|
|
397
498
|
<span>© 2026 Tersio. KurutoDenzeru. All rights reserved.</span>
|
|
@@ -405,6 +506,10 @@
|
|
|
405
506
|
</div>
|
|
406
507
|
</main>
|
|
407
508
|
|
|
408
|
-
<script
|
|
509
|
+
<script>window.__TERSIO_SNAP = null;</script>
|
|
510
|
+
<script src="core.js" defer></script>
|
|
511
|
+
<script src="charts.js" defer></script>
|
|
512
|
+
<script src="settings.js" defer></script>
|
|
513
|
+
<script src="share.js" defer></script>
|
|
409
514
|
</body>
|
|
410
515
|
</html>
|