vibedate 0.1.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/LICENSE +21 -0
- package/README.md +117 -0
- package/dist/chunk-64ME4THO.js +126 -0
- package/dist/chunk-AU7FN2LY.js +133 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.js +1075 -0
- package/dist/index.d.ts +98 -0
- package/dist/index.js +28 -0
- package/dist/mcp.d.ts +7 -0
- package/dist/mcp.js +7 -0
- package/package.json +63 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1075 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
connectProfile,
|
|
4
|
+
loadProfile,
|
|
5
|
+
runMcp
|
|
6
|
+
} from "./chunk-64ME4THO.js";
|
|
7
|
+
import {
|
|
8
|
+
CANDIDATES,
|
|
9
|
+
league,
|
|
10
|
+
matches,
|
|
11
|
+
readUsage
|
|
12
|
+
} from "./chunk-AU7FN2LY.js";
|
|
13
|
+
|
|
14
|
+
// src/cli.ts
|
|
15
|
+
import { pathToFileURL } from "url";
|
|
16
|
+
import process from "process";
|
|
17
|
+
|
|
18
|
+
// src/server.ts
|
|
19
|
+
import http from "http";
|
|
20
|
+
|
|
21
|
+
// src/web-app-html.ts
|
|
22
|
+
var webAppHtml = `<!DOCTYPE html>
|
|
23
|
+
<html lang="en">
|
|
24
|
+
<head>
|
|
25
|
+
<meta charset="UTF-8">
|
|
26
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
27
|
+
<title>vibedating \u2014 matched by how hard you push the model</title>
|
|
28
|
+
<style>
|
|
29
|
+
:root{
|
|
30
|
+
--bg: #1a1120;
|
|
31
|
+
--bg-1: #22162a;
|
|
32
|
+
--bg-card: #2a1b33;
|
|
33
|
+
--bg-card-2: #31203c;
|
|
34
|
+
--fg: #f8efe8;
|
|
35
|
+
--muted: #cbb2c4;
|
|
36
|
+
--muted-2: #a88fae;
|
|
37
|
+
--border: rgba(248,239,232,0.09);
|
|
38
|
+
--border-2: rgba(248,239,232,0.15);
|
|
39
|
+
--coral: #ff7a68;
|
|
40
|
+
--coral-dim: #e26456;
|
|
41
|
+
--amber: #ffb15e;
|
|
42
|
+
--mint: #7fe3c0;
|
|
43
|
+
--danger: #ff6b6f;
|
|
44
|
+
--lg-1m: #d69a6e;
|
|
45
|
+
--lg-5m: #cfd8e6;
|
|
46
|
+
--lg-10m: #ffcf6b;
|
|
47
|
+
--lg-100m: #f0839c;
|
|
48
|
+
--lg-1b: #cba8ff;
|
|
49
|
+
--shadow-1: 0 1px 2px rgba(0,0,0,.35);
|
|
50
|
+
--shadow-2: 0 18px 40px -20px rgba(0,0,0,.65);
|
|
51
|
+
--shadow-3: 0 30px 70px -28px rgba(0,0,0,.7);
|
|
52
|
+
--radius: 20px;
|
|
53
|
+
--ease-out: cubic-bezier(.16,1,.3,1);
|
|
54
|
+
--ease-bounce: cubic-bezier(.34,1.56,.64,1);
|
|
55
|
+
--dur-fast: .18s;
|
|
56
|
+
--dur-med: .42s;
|
|
57
|
+
--dur-slow: .9s;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@media (prefers-reduced-motion: reduce){
|
|
61
|
+
*, *::before, *::after{
|
|
62
|
+
animation-duration: .001s !important;
|
|
63
|
+
animation-iteration-count: 1 !important;
|
|
64
|
+
transition-duration: .001s !important;
|
|
65
|
+
scroll-behavior: auto !important;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
*{ box-sizing: border-box; }
|
|
70
|
+
html,body{ margin:0; padding:0; }
|
|
71
|
+
body{
|
|
72
|
+
background:
|
|
73
|
+
radial-gradient(1100px 640px at 14% -6%, rgba(255,122,104,.14), transparent 60%),
|
|
74
|
+
radial-gradient(900px 560px at 92% 8%, rgba(203,168,255,.10), transparent 55%),
|
|
75
|
+
var(--bg);
|
|
76
|
+
color: var(--fg);
|
|
77
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
78
|
+
-webkit-font-smoothing: antialiased;
|
|
79
|
+
min-height: 100vh;
|
|
80
|
+
}
|
|
81
|
+
::selection{ background: rgba(255,122,104,.35); }
|
|
82
|
+
.mono{ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
|
83
|
+
|
|
84
|
+
a, button{ font: inherit; color: inherit; }
|
|
85
|
+
button{ cursor:pointer; }
|
|
86
|
+
:focus-visible{ outline: 2px solid var(--mint); outline-offset: 2px; border-radius: 6px; }
|
|
87
|
+
|
|
88
|
+
header.topbar{
|
|
89
|
+
position: sticky; top:0; z-index: 40;
|
|
90
|
+
display:flex; align-items:center; justify-content:space-between; gap:16px;
|
|
91
|
+
padding: 16px 24px;
|
|
92
|
+
background: rgba(26,17,32,.78);
|
|
93
|
+
backdrop-filter: blur(14px) saturate(140%);
|
|
94
|
+
-webkit-backdrop-filter: blur(14px) saturate(140%);
|
|
95
|
+
border-bottom: 1px solid var(--border);
|
|
96
|
+
}
|
|
97
|
+
.wordmark{ display:flex; align-items:baseline; gap:10px; }
|
|
98
|
+
.wordmark .name{ font-size: 1.25rem; font-weight: 800; letter-spacing: -0.02em; }
|
|
99
|
+
.wordmark .name span{ color: var(--coral); }
|
|
100
|
+
.wordmark .tag{ font-size: .82rem; color: var(--muted); font-weight: 500; }
|
|
101
|
+
.local-badge{
|
|
102
|
+
display:inline-flex; align-items:center; gap:8px;
|
|
103
|
+
padding: 7px 13px 7px 10px;
|
|
104
|
+
border-radius: 999px;
|
|
105
|
+
background: rgba(127,227,192,.09);
|
|
106
|
+
border: 1px solid rgba(127,227,192,.28);
|
|
107
|
+
font-size: .78rem; font-weight: 600; color: #bff2df;
|
|
108
|
+
white-space: nowrap;
|
|
109
|
+
}
|
|
110
|
+
.local-badge .dot{
|
|
111
|
+
width:7px; height:7px; border-radius:50%;
|
|
112
|
+
background: var(--mint);
|
|
113
|
+
box-shadow: 0 0 0 3px rgba(127,227,192,.18);
|
|
114
|
+
animation: pulse-dot 2.4s ease-in-out infinite;
|
|
115
|
+
flex-shrink:0;
|
|
116
|
+
}
|
|
117
|
+
@keyframes pulse-dot{
|
|
118
|
+
0%,100%{ transform: scale(1); opacity:1; }
|
|
119
|
+
50%{ transform: scale(1.35); opacity:.65; }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.hero{ max-width: 1240px; margin: 0 auto; padding: 44px 24px 8px; }
|
|
123
|
+
.hero h1{
|
|
124
|
+
font-size: clamp(1.8rem, 3.6vw, 2.9rem);
|
|
125
|
+
line-height: 1.08; letter-spacing: -0.03em; font-weight: 800;
|
|
126
|
+
max-width: 20ch; margin: 0 0 10px;
|
|
127
|
+
}
|
|
128
|
+
.hero p{
|
|
129
|
+
color: var(--muted); font-size: 1.02rem; max-width: 56ch; margin:0;
|
|
130
|
+
line-height: 1.5;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.stage{
|
|
134
|
+
max-width: 1240px; margin: 0 auto; padding: 30px 24px 100px;
|
|
135
|
+
display: grid;
|
|
136
|
+
grid-template-columns: minmax(280px,336px) minmax(320px,404px) minmax(268px,320px);
|
|
137
|
+
gap: 26px;
|
|
138
|
+
align-items: start;
|
|
139
|
+
}
|
|
140
|
+
@media (max-width: 1020px){
|
|
141
|
+
.stage{ grid-template-columns: 1fr; max-width: 620px; }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.panel{ opacity: 0; transform: translateY(16px); }
|
|
145
|
+
.loaded .panel{ animation: rise var(--dur-slow) var(--ease-out) forwards; }
|
|
146
|
+
.loaded .panel:nth-of-type(1){ animation-delay: .02s; }
|
|
147
|
+
.loaded .panel:nth-of-type(2){ animation-delay: .12s; }
|
|
148
|
+
.loaded .panel:nth-of-type(3){ animation-delay: .22s; }
|
|
149
|
+
@keyframes rise{ to{ opacity:1; transform: translateY(0); } }
|
|
150
|
+
|
|
151
|
+
h2.panel-title{
|
|
152
|
+
font-size: 1.02rem; font-weight: 700; margin: 0 0 14px;
|
|
153
|
+
display:flex; align-items:center; gap:8px;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.card{
|
|
157
|
+
background: linear-gradient(180deg, var(--bg-card), var(--bg-card-2));
|
|
158
|
+
border: 1px solid var(--border);
|
|
159
|
+
border-radius: var(--radius);
|
|
160
|
+
box-shadow: var(--shadow-2);
|
|
161
|
+
padding: 22px;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.avatar{
|
|
165
|
+
width: 76px; height:76px; border-radius: 50%;
|
|
166
|
+
overflow: hidden; flex-shrink:0;
|
|
167
|
+
background: var(--bg-1);
|
|
168
|
+
border: 1px solid var(--border-2);
|
|
169
|
+
box-shadow: var(--shadow-1);
|
|
170
|
+
}
|
|
171
|
+
.avatar svg{ width:100%; height:100%; display:block; }
|
|
172
|
+
|
|
173
|
+
.connect-head{ display:flex; align-items:center; gap:14px; margin-bottom: 16px; }
|
|
174
|
+
.connect-head .who .h{ font-weight: 700; font-size: 1rem; }
|
|
175
|
+
.connect-head .who .s{ color: var(--muted); font-size: .85rem; }
|
|
176
|
+
|
|
177
|
+
.provider-picker{
|
|
178
|
+
display:flex; gap:6px; padding:4px; border-radius: 12px;
|
|
179
|
+
background: rgba(0,0,0,.22); border: 1px solid var(--border); margin-bottom: 14px;
|
|
180
|
+
}
|
|
181
|
+
.provider-picker button{
|
|
182
|
+
flex:1; border:0; background: transparent; color: var(--muted);
|
|
183
|
+
padding: 9px 8px; border-radius: 9px; font-size: .84rem; font-weight: 600;
|
|
184
|
+
transition: background var(--dur-fast) ease, color var(--dur-fast) ease;
|
|
185
|
+
}
|
|
186
|
+
.provider-picker button.is-active{
|
|
187
|
+
background: var(--bg-card); color: var(--fg);
|
|
188
|
+
box-shadow: var(--shadow-1);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.btn{
|
|
192
|
+
border:0; border-radius: 13px; padding: 13px 18px; font-weight: 700; font-size: .92rem;
|
|
193
|
+
display:inline-flex; align-items:center; justify-content:center; gap:8px;
|
|
194
|
+
width:100%;
|
|
195
|
+
transition: transform var(--dur-fast) var(--ease-out), filter var(--dur-fast) ease, box-shadow var(--dur-fast) ease;
|
|
196
|
+
}
|
|
197
|
+
.btn:active{ transform: scale(.97); }
|
|
198
|
+
.btn-primary{
|
|
199
|
+
background: linear-gradient(180deg, var(--coral), var(--coral-dim));
|
|
200
|
+
color: #2a1109;
|
|
201
|
+
box-shadow: 0 12px 24px -12px rgba(255,122,104,.55);
|
|
202
|
+
}
|
|
203
|
+
.btn-primary:hover{ filter: brightness(1.06); }
|
|
204
|
+
.btn-ghost{
|
|
205
|
+
background: transparent; color: var(--muted); border: 1px solid var(--border-2);
|
|
206
|
+
}
|
|
207
|
+
.btn-ghost:hover{ color: var(--fg); border-color: var(--muted-2); }
|
|
208
|
+
.btn:disabled{ opacity:.45; cursor: not-allowed; filter:none; box-shadow:none; }
|
|
209
|
+
|
|
210
|
+
.step{ display:none; }
|
|
211
|
+
.step.is-active{ display:block; animation: fadein var(--dur-med) var(--ease-out); }
|
|
212
|
+
@keyframes fadein{ from{ opacity:0; transform: translateY(4px);} to{ opacity:1; transform:none; } }
|
|
213
|
+
|
|
214
|
+
.consent-box{
|
|
215
|
+
background: rgba(0,0,0,.22); border: 1px solid var(--border-2);
|
|
216
|
+
border-radius: 14px; padding: 14px 16px; margin-bottom: 14px;
|
|
217
|
+
}
|
|
218
|
+
.consent-box .lead{ font-size: .86rem; font-weight: 700; margin-bottom: 10px; }
|
|
219
|
+
.consent-line{ display:flex; gap:8px; align-items:flex-start; font-size: .82rem; color: var(--muted); margin-bottom:7px; line-height:1.4; }
|
|
220
|
+
.consent-line:last-child{ margin-bottom:0; }
|
|
221
|
+
.consent-line .yes{ color: var(--mint); flex-shrink:0; }
|
|
222
|
+
.consent-line .no{ color: var(--muted-2); flex-shrink:0; }
|
|
223
|
+
.consent-actions{ display:flex; gap:10px; margin-top: 14px; }
|
|
224
|
+
|
|
225
|
+
.verify-box .lead{ font-size: .86rem; font-weight: 700; margin-bottom: 12px; display:flex; align-items:center; gap:10px; }
|
|
226
|
+
.spinner{
|
|
227
|
+
width:16px; height:16px; border-radius:50%;
|
|
228
|
+
border: 2px solid rgba(255,177,94,.25); border-top-color: var(--amber);
|
|
229
|
+
animation: spin .8s linear infinite; flex-shrink:0;
|
|
230
|
+
}
|
|
231
|
+
@keyframes spin{ to{ transform: rotate(360deg); } }
|
|
232
|
+
.progress-track{
|
|
233
|
+
height:6px; border-radius: 999px; background: rgba(0,0,0,.3); overflow:hidden; margin-bottom:10px;
|
|
234
|
+
}
|
|
235
|
+
.progress-fill{
|
|
236
|
+
height:100%; width:0%; border-radius:999px;
|
|
237
|
+
background: linear-gradient(90deg, var(--amber), var(--coral));
|
|
238
|
+
transition: width 1.7s var(--ease-out);
|
|
239
|
+
}
|
|
240
|
+
.verify-step-text{ font-size:.8rem; color: var(--muted); min-height: 1.2em; }
|
|
241
|
+
|
|
242
|
+
.error-box{
|
|
243
|
+
background: rgba(255,107,111,.08); border: 1px solid rgba(255,107,111,.3);
|
|
244
|
+
border-radius: 14px; padding: 14px 16px; margin-bottom:14px;
|
|
245
|
+
}
|
|
246
|
+
.error-box .lead{ font-size:.86rem; font-weight:700; color:#ffb3b6; margin-bottom:6px; }
|
|
247
|
+
.error-box p{ font-size: .82rem; color: var(--muted); margin: 0 0 12px; line-height:1.45; }
|
|
248
|
+
|
|
249
|
+
.reveal-top{ display:flex; gap:14px; align-items:center; margin-bottom: 16px; }
|
|
250
|
+
.reveal-top .handle{ font-weight:700; }
|
|
251
|
+
.verified-line{ font-size:.78rem; color: var(--mint); display:flex; align-items:center; gap:5px; margin-top:3px; }
|
|
252
|
+
|
|
253
|
+
.raw-counter{ font-size: .84rem; color: var(--muted); margin-bottom: 4px; min-height: 1.2em; }
|
|
254
|
+
.raw-counter .n{ color: var(--amber); }
|
|
255
|
+
|
|
256
|
+
.league-badge-wrap{ display:flex; justify-content:center; margin: 6px 0 16px; }
|
|
257
|
+
.league-badge{
|
|
258
|
+
display:inline-flex; align-items:center; gap:9px;
|
|
259
|
+
padding: 12px 20px;
|
|
260
|
+
border-radius: 16px;
|
|
261
|
+
font-weight: 800; font-size: 1.05rem;
|
|
262
|
+
transform: scale(.4); opacity:0;
|
|
263
|
+
}
|
|
264
|
+
.league-badge.is-in{ animation: badge-in .6s var(--ease-bounce) forwards; }
|
|
265
|
+
@keyframes badge-in{ to{ transform: scale(1); opacity:1; } }
|
|
266
|
+
.league-badge .crest{ width:22px; height:22px; flex-shrink:0; }
|
|
267
|
+
|
|
268
|
+
.traits{ display:flex; flex-wrap:wrap; gap:7px; margin-bottom: 14px; }
|
|
269
|
+
.trait{
|
|
270
|
+
font-size: .76rem; font-weight:600; padding: 6px 11px; border-radius: 999px;
|
|
271
|
+
background: rgba(248,239,232,.06); border: 1px solid var(--border-2); color: var(--muted);
|
|
272
|
+
opacity:0; transform: translateY(6px);
|
|
273
|
+
}
|
|
274
|
+
.trait.is-in{ animation: trait-in var(--dur-med) var(--ease-out) forwards; }
|
|
275
|
+
@keyframes trait-in{ to{ opacity:1; transform:none; } }
|
|
276
|
+
|
|
277
|
+
.raw-toggle{
|
|
278
|
+
background:none; border:0; color: var(--muted-2); font-size:.76rem; font-weight:600;
|
|
279
|
+
text-decoration: underline; text-underline-offset:2px; padding:2px 0;
|
|
280
|
+
}
|
|
281
|
+
.raw-toggle:hover{ color: var(--muted); }
|
|
282
|
+
.raw-reveal{
|
|
283
|
+
max-height:0; overflow:hidden; transition: max-height var(--dur-med) var(--ease-out);
|
|
284
|
+
font-size: .78rem; color: var(--muted-2);
|
|
285
|
+
}
|
|
286
|
+
.raw-reveal.is-open{ max-height: 60px; margin-top:8px; }
|
|
287
|
+
|
|
288
|
+
.stack-wrap{ position: relative; height: 430px; }
|
|
289
|
+
.card-stack{ position:absolute; inset:0; }
|
|
290
|
+
.match-card{
|
|
291
|
+
position:absolute; inset:0;
|
|
292
|
+
background: linear-gradient(165deg, var(--bg-card), var(--bg-card-2));
|
|
293
|
+
border: 1px solid var(--border);
|
|
294
|
+
border-radius: var(--radius);
|
|
295
|
+
box-shadow: var(--shadow-2);
|
|
296
|
+
padding: 20px;
|
|
297
|
+
display:flex; flex-direction:column;
|
|
298
|
+
transition: transform var(--dur-med) var(--ease-out), opacity var(--dur-med) ease;
|
|
299
|
+
}
|
|
300
|
+
.match-card[data-depth="0"]{ z-index:3; }
|
|
301
|
+
.match-card[data-depth="1"]{ z-index:2; transform: translateY(14px) scale(.96); opacity:.85; }
|
|
302
|
+
.match-card[data-depth="2"]{ z-index:1; transform: translateY(26px) scale(.92); opacity:.55; }
|
|
303
|
+
.match-card.fly-like{ transform: translate(140%, -30px) rotate(18deg) !important; opacity:0 !important; }
|
|
304
|
+
.match-card.fly-pass{ transform: translate(-140%, -30px) rotate(-18deg) !important; opacity:0 !important; }
|
|
305
|
+
|
|
306
|
+
.mc-top{ display:flex; gap:12px; align-items:center; }
|
|
307
|
+
.mc-avatar{ width:58px; height:58px; border-radius:50%; overflow:hidden; flex-shrink:0; border:1px solid var(--border-2); background:var(--bg-1); }
|
|
308
|
+
.mc-avatar svg{ width:100%; height:100%; display:block; transition: transform .5s var(--ease-out); }
|
|
309
|
+
.match-card:hover .mc-avatar svg{ transform: rotate(8deg) scale(1.05); }
|
|
310
|
+
.mc-handle{ font-weight:700; font-size:.98rem; }
|
|
311
|
+
.mc-league{
|
|
312
|
+
display:inline-flex; align-items:center; gap:5px; font-size:.72rem; font-weight:700;
|
|
313
|
+
padding: 4px 9px; border-radius:999px; margin-top:4px;
|
|
314
|
+
}
|
|
315
|
+
.mc-verified{ font-size:.7rem; color:var(--mint); display:flex; align-items:center; gap:4px; margin-top:5px; }
|
|
316
|
+
|
|
317
|
+
.mc-bio{ margin-top:16px; flex:1; }
|
|
318
|
+
.mc-bio p{ font-size:.87rem; color: var(--muted); line-height:1.5; margin: 0 0 8px; }
|
|
319
|
+
|
|
320
|
+
.mc-actions{ display:flex; gap:10px; margin-top: 12px; }
|
|
321
|
+
.round-btn{
|
|
322
|
+
width:52px; height:52px; border-radius:50%; border:1px solid var(--border-2);
|
|
323
|
+
background: rgba(0,0,0,.18); font-size:1.3rem;
|
|
324
|
+
display:flex; align-items:center; justify-content:center;
|
|
325
|
+
transition: transform var(--dur-fast) var(--ease-bounce), background var(--dur-fast) ease, border-color var(--dur-fast) ease;
|
|
326
|
+
}
|
|
327
|
+
.round-btn:hover:not(:disabled){ transform: translateY(-3px); }
|
|
328
|
+
.round-btn:active:not(:disabled){ transform: scale(.9); }
|
|
329
|
+
.round-btn.pass:hover:not(:disabled){ border-color: var(--muted-2); }
|
|
330
|
+
.round-btn.like:hover:not(:disabled){ border-color: var(--coral); background: rgba(255,122,104,.12); }
|
|
331
|
+
.round-btn:disabled{ opacity:.35; cursor: not-allowed; }
|
|
332
|
+
.mc-actions .hint{ align-self:center; font-size:.76rem; color: var(--muted-2); margin-left:2px; }
|
|
333
|
+
|
|
334
|
+
.empty-state{
|
|
335
|
+
position:absolute; inset:0; display:flex; flex-direction:column; align-items:center; justify-content:center;
|
|
336
|
+
text-align:center; padding: 30px;
|
|
337
|
+
background: linear-gradient(165deg, var(--bg-card), var(--bg-card-2));
|
|
338
|
+
border: 1px dashed var(--border-2); border-radius: var(--radius);
|
|
339
|
+
}
|
|
340
|
+
.empty-state p{ color: var(--muted); font-size:.88rem; max-width: 30ch; margin: 8px 0 16px; line-height:1.5; }
|
|
341
|
+
.empty-state strong{ font-size:1rem; }
|
|
342
|
+
|
|
343
|
+
.celebrate{
|
|
344
|
+
position: fixed; inset:0; z-index: 60;
|
|
345
|
+
display:flex; align-items:center; justify-content:center;
|
|
346
|
+
background: rgba(12,7,15,.6);
|
|
347
|
+
backdrop-filter: blur(6px);
|
|
348
|
+
opacity:0; pointer-events:none;
|
|
349
|
+
transition: opacity var(--dur-med) ease;
|
|
350
|
+
}
|
|
351
|
+
.celebrate.is-open{ opacity:1; pointer-events:auto; }
|
|
352
|
+
.celebrate-card{
|
|
353
|
+
background: linear-gradient(165deg, var(--bg-card), var(--bg-card-2));
|
|
354
|
+
border: 1px solid var(--border-2); border-radius: 24px; box-shadow: var(--shadow-3);
|
|
355
|
+
padding: 34px 30px; max-width: 340px; text-align:center;
|
|
356
|
+
transform: scale(.85) translateY(10px); transition: transform var(--dur-med) var(--ease-bounce);
|
|
357
|
+
}
|
|
358
|
+
.celebrate.is-open .celebrate-card{ transform: scale(1) translateY(0); }
|
|
359
|
+
.celebrate-avatars{ display:flex; justify-content:center; gap:-10px; margin-bottom:14px; }
|
|
360
|
+
.celebrate-avatars .avatar{ width:56px; height:56px; margin: 0 -8px; border-width:2px; border-color: var(--bg-card-2); }
|
|
361
|
+
.celebrate h3{ margin: 4px 0 6px; font-size:1.25rem; font-weight:800; }
|
|
362
|
+
.celebrate p{ color: var(--muted); font-size:.88rem; margin: 0 0 18px; line-height:1.5; }
|
|
363
|
+
|
|
364
|
+
.ladder-row{
|
|
365
|
+
display:flex; align-items:center; gap:10px; padding: 8px 0;
|
|
366
|
+
border-bottom: 1px solid var(--border);
|
|
367
|
+
}
|
|
368
|
+
.ladder-row:last-child{ border-bottom:0; }
|
|
369
|
+
.ladder-label{ width:64px; flex-shrink:0; font-size:.78rem; font-weight:700; }
|
|
370
|
+
.ladder-bar-track{ flex:1; height:9px; border-radius:999px; background: rgba(0,0,0,.28); overflow:hidden; }
|
|
371
|
+
.ladder-bar-fill{
|
|
372
|
+
height:100%; border-radius:999px; width:0%;
|
|
373
|
+
transition: width 1.1s var(--ease-out);
|
|
374
|
+
}
|
|
375
|
+
.ladder-pool{ width:64px; flex-shrink:0; text-align:right; font-size:.72rem; color: var(--muted-2); }
|
|
376
|
+
.you-marker{
|
|
377
|
+
display:inline-flex; align-items:center; gap:4px; font-size:.66rem; font-weight:800;
|
|
378
|
+
color: #2a1109; background: var(--mint); padding: 2px 7px; border-radius:999px;
|
|
379
|
+
margin-left:6px; opacity:0; transform: translateX(-4px);
|
|
380
|
+
}
|
|
381
|
+
.you-marker.is-in{ animation: you-in var(--dur-med) var(--ease-out) forwards; }
|
|
382
|
+
@keyframes you-in{ to{ opacity:1; transform:none; } }
|
|
383
|
+
|
|
384
|
+
.ladder-note{ font-size:.78rem; color: var(--muted-2); line-height:1.5; margin: 14px 0 0; padding-top:14px; border-top: 1px solid var(--border); }
|
|
385
|
+
|
|
386
|
+
.explainer-chip{
|
|
387
|
+
width:100%; text-align:left; background: rgba(0,0,0,.18); border: 1px solid var(--border-2);
|
|
388
|
+
border-radius: 14px; padding: 13px 15px; margin-top: 18px;
|
|
389
|
+
display:flex; align-items:center; justify-content:space-between; gap:10px;
|
|
390
|
+
font-weight:700; font-size:.86rem;
|
|
391
|
+
}
|
|
392
|
+
.explainer-chip:hover{ border-color: var(--muted-2); }
|
|
393
|
+
.explainer-chip .chevron{ transition: transform var(--dur-fast) var(--ease-out); color: var(--muted-2); }
|
|
394
|
+
.explainer-chip[aria-expanded="true"] .chevron{ transform: rotate(180deg); }
|
|
395
|
+
.explainer-panel{
|
|
396
|
+
max-height:0; overflow:hidden; transition: max-height var(--dur-med) var(--ease-out);
|
|
397
|
+
}
|
|
398
|
+
.explainer-panel.is-open{ max-height: 260px; }
|
|
399
|
+
.explainer-panel .inner{ padding: 13px 4px 2px; font-size:.84rem; color: var(--muted); line-height:1.55; }
|
|
400
|
+
.explainer-panel .inner b{ color: var(--fg); }
|
|
401
|
+
|
|
402
|
+
footer.foot{
|
|
403
|
+
max-width:1240px; margin: 0 auto; padding: 0 24px 50px; color: var(--muted-2); font-size:.76rem;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
@media (max-width: 480px){
|
|
407
|
+
.hero{ padding: 32px 18px 4px; }
|
|
408
|
+
.stage{ padding: 22px 18px 80px; }
|
|
409
|
+
header.topbar{ padding: 13px 16px; flex-wrap: wrap; }
|
|
410
|
+
}
|
|
411
|
+
</style>
|
|
412
|
+
</head>
|
|
413
|
+
<body>
|
|
414
|
+
|
|
415
|
+
<header class="topbar">
|
|
416
|
+
<div class="wordmark">
|
|
417
|
+
<span class="name">vibe<span>dating</span></span>
|
|
418
|
+
<span class="tag">matched by how hard you push the model</span>
|
|
419
|
+
</div>
|
|
420
|
+
<div class="local-badge"><span class="dot" aria-hidden="true"></span> raw usage stays local · only league shared</div>
|
|
421
|
+
</header>
|
|
422
|
+
|
|
423
|
+
<div class="hero">
|
|
424
|
+
<h1>Heavy users of the same tools share something. Let's find out what.</h1>
|
|
425
|
+
<p>Connect your Claude Code or Codex usage, get sorted into a league by volume, and match with people who burn tokens the way you do.</p>
|
|
426
|
+
</div>
|
|
427
|
+
|
|
428
|
+
<main class="stage" id="stage">
|
|
429
|
+
|
|
430
|
+
<section class="panel" aria-label="Your profile">
|
|
431
|
+
<h2 class="panel-title">Your profile</h2>
|
|
432
|
+
<div class="card">
|
|
433
|
+
|
|
434
|
+
<div class="step is-active" data-step="idle">
|
|
435
|
+
<div class="connect-head">
|
|
436
|
+
<div class="avatar" id="idleAvatar"></div>
|
|
437
|
+
<div class="who">
|
|
438
|
+
<div class="h">You</div>
|
|
439
|
+
<div class="s">Not connected yet</div>
|
|
440
|
+
</div>
|
|
441
|
+
</div>
|
|
442
|
+
<div class="provider-picker" role="group" aria-label="Choose provider">
|
|
443
|
+
<button type="button" class="is-active" data-harness="claude-code">Claude Code</button>
|
|
444
|
+
<button type="button" data-harness="codex">Codex</button>
|
|
445
|
+
</div>
|
|
446
|
+
<button class="btn btn-primary" id="btnConnect" type="button">Connect Claude / Codex</button>
|
|
447
|
+
</div>
|
|
448
|
+
|
|
449
|
+
<div class="step" data-step="consent">
|
|
450
|
+
<div class="connect-head">
|
|
451
|
+
<div class="avatar" id="consentAvatar"></div>
|
|
452
|
+
<div class="who">
|
|
453
|
+
<div class="h">You</div>
|
|
454
|
+
<div class="s" id="consentProviderLabel">Connecting Claude Code...</div>
|
|
455
|
+
</div>
|
|
456
|
+
</div>
|
|
457
|
+
<div class="consent-box">
|
|
458
|
+
<div class="lead">vibedating is requesting:</div>
|
|
459
|
+
<div class="consent-line"><span class="yes">✓</span> Read-only usage history — token counts, time-of-day patterns</div>
|
|
460
|
+
<div class="consent-line"><span class="no">✗</span> Your prompts, code, or conversation content</div>
|
|
461
|
+
<div class="consent-line"><span class="no">✗</span> Your password — auth happens in your CLI's own OAuth flow</div>
|
|
462
|
+
<div class="consent-line"><span class="no">✗</span> Any write access, ever</div>
|
|
463
|
+
</div>
|
|
464
|
+
<div class="consent-actions">
|
|
465
|
+
<button class="btn btn-ghost" id="btnCancel" type="button">Cancel</button>
|
|
466
|
+
<button class="btn btn-primary" id="btnAuthorize" type="button">Authorize read-only</button>
|
|
467
|
+
</div>
|
|
468
|
+
</div>
|
|
469
|
+
|
|
470
|
+
<div class="step" data-step="verifying">
|
|
471
|
+
<div class="verify-box">
|
|
472
|
+
<div class="lead"><span class="spinner" aria-hidden="true"></span> Reading your usage...</div>
|
|
473
|
+
<div class="progress-track"><div class="progress-fill" id="progressFill"></div></div>
|
|
474
|
+
<div class="verify-step-text mono" id="verifyStepText" role="status" aria-live="polite">Reading local usage...</div>
|
|
475
|
+
</div>
|
|
476
|
+
</div>
|
|
477
|
+
|
|
478
|
+
<div class="step" data-step="error">
|
|
479
|
+
<div class="error-box">
|
|
480
|
+
<div class="lead">Couldn't read usage</div>
|
|
481
|
+
<p>The local usage read failed. Nothing was shared either way — no prompts, no partial numbers, nothing.</p>
|
|
482
|
+
</div>
|
|
483
|
+
<button class="btn btn-primary" id="btnRetry" type="button">Try again</button>
|
|
484
|
+
</div>
|
|
485
|
+
|
|
486
|
+
<div class="step" data-step="reveal">
|
|
487
|
+
<div class="reveal-top">
|
|
488
|
+
<div class="avatar" id="revealAvatar"></div>
|
|
489
|
+
<div class="who">
|
|
490
|
+
<div class="h" id="revealHandle">@you</div>
|
|
491
|
+
<div class="verified-line" id="verifiedLine"><span>✓</span> usage read locally</div>
|
|
492
|
+
</div>
|
|
493
|
+
</div>
|
|
494
|
+
|
|
495
|
+
<div class="raw-counter mono" id="rawCounter" aria-live="polite">aggregating usage...</div>
|
|
496
|
+
|
|
497
|
+
<div class="league-badge-wrap">
|
|
498
|
+
<div class="league-badge" id="leagueBadge"></div>
|
|
499
|
+
</div>
|
|
500
|
+
|
|
501
|
+
<div class="traits" id="traitsRow"></div>
|
|
502
|
+
|
|
503
|
+
<button class="raw-toggle" id="rawToggle" type="button" aria-expanded="false">show raw usage (visible only to you)</button>
|
|
504
|
+
<div class="raw-reveal mono" id="rawReveal">-</div>
|
|
505
|
+
|
|
506
|
+
<div style="margin-top:14px;">
|
|
507
|
+
<button class="btn btn-ghost" id="btnReset" type="button">Disconnect & reset</button>
|
|
508
|
+
</div>
|
|
509
|
+
</div>
|
|
510
|
+
|
|
511
|
+
</div>
|
|
512
|
+
</section>
|
|
513
|
+
|
|
514
|
+
<section class="panel" aria-label="Match stack">
|
|
515
|
+
<h2 class="panel-title">Match stack</h2>
|
|
516
|
+
<div class="stack-wrap">
|
|
517
|
+
<div class="card-stack" id="cardStack"></div>
|
|
518
|
+
</div>
|
|
519
|
+
</section>
|
|
520
|
+
|
|
521
|
+
<section class="panel" aria-label="League ladder">
|
|
522
|
+
<h2 class="panel-title">League ladder</h2>
|
|
523
|
+
<div class="card">
|
|
524
|
+
<div id="ladderRows"></div>
|
|
525
|
+
<p class="ladder-note">Pool math gets thin fast above 100M — up at 1B+ you might be choosing from single digits. We're not hiding that; it's the honest tradeoff of matching on something this specific.</p>
|
|
526
|
+
|
|
527
|
+
<button class="explainer-chip" id="explainerBtn" type="button" aria-expanded="false" aria-controls="explainerPanel">
|
|
528
|
+
<span>Why leagues?</span>
|
|
529
|
+
<span class="chevron" aria-hidden="true">▾</span>
|
|
530
|
+
</button>
|
|
531
|
+
<div class="explainer-panel" id="explainerPanel">
|
|
532
|
+
<div class="inner">
|
|
533
|
+
Because the number has to be <b>real</b>. Matching on "heavy user" only means something if the usage behind it is yours — so vibedating reads it from your own machine. <b>Raw token counts never leave this device</b>; only your league bucket is ever shared. In v0 the pool is a local seeded demo; verification via read-only OAuth is the next step.
|
|
534
|
+
</div>
|
|
535
|
+
</div>
|
|
536
|
+
</div>
|
|
537
|
+
</section>
|
|
538
|
+
|
|
539
|
+
</main>
|
|
540
|
+
|
|
541
|
+
<footer class="foot">Local-first. Your league is computed from usage read on this machine and stored here only — raw usage never leaves your device.</footer>
|
|
542
|
+
|
|
543
|
+
<div class="celebrate" id="celebrate" role="dialog" aria-modal="true" aria-live="polite">
|
|
544
|
+
<div class="celebrate-card">
|
|
545
|
+
<div class="celebrate-avatars">
|
|
546
|
+
<div class="avatar" id="celebrateYou"></div>
|
|
547
|
+
<div class="avatar" id="celebrateThem"></div>
|
|
548
|
+
</div>
|
|
549
|
+
<h3>It's a match!</h3>
|
|
550
|
+
<p id="celebrateText">You're both in the same league.</p>
|
|
551
|
+
<button class="btn btn-primary" id="btnCelebrateClose" type="button">Keep swiping</button>
|
|
552
|
+
</div>
|
|
553
|
+
</div>
|
|
554
|
+
|
|
555
|
+
<script>
|
|
556
|
+
(function(){
|
|
557
|
+
"use strict";
|
|
558
|
+
|
|
559
|
+
var BLOB_PATHS = [
|
|
560
|
+
"M50 8C68 8 82 22 86 40C90 58 78 74 60 84C42 94 22 86 12 68C2 50 8 28 24 16C32 10 42 8 50 8Z",
|
|
561
|
+
"M52 6C72 10 90 26 88 46C86 66 68 82 48 88C28 94 10 82 6 62C2 42 12 22 30 12C38 8 46 4 52 6Z",
|
|
562
|
+
"M46 10C64 4 84 14 90 32C96 50 88 70 70 82C52 94 30 92 16 78C2 64 4 42 16 26C24 16 34 14 46 10Z",
|
|
563
|
+
"M50 4C70 4 88 18 92 38C96 58 84 78 64 88C44 98 20 92 10 74C0 56 6 32 22 18C30 10 40 4 50 4Z",
|
|
564
|
+
"M48 12C66 2 88 10 94 30C100 50 92 72 74 86C56 100 30 96 14 80C-2 64 2 40 18 22C26 14 38 16 48 12Z",
|
|
565
|
+
"M54 8C74 12 88 30 86 50C84 70 66 86 46 86C26 86 10 70 10 50C10 30 26 12 46 8C48.7 7.4 51.3 7.4 54 8Z"
|
|
566
|
+
];
|
|
567
|
+
function avatarSVG(variant, hexColor){
|
|
568
|
+
var p1 = BLOB_PATHS[variant % BLOB_PATHS.length];
|
|
569
|
+
var p2 = BLOB_PATHS[(variant + 3) % BLOB_PATHS.length];
|
|
570
|
+
return '<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">' +
|
|
571
|
+
'<rect width="100" height="100" fill="' + hexColor + '" opacity="0.16"/>' +
|
|
572
|
+
'<path d="' + p1 + '" fill="' + hexColor + '" opacity="0.85" transform="translate(2,3) scale(0.94)"/>' +
|
|
573
|
+
'<path d="' + p2 + '" fill="#f8efe8" opacity="0.14" transform="translate(-4,4) scale(0.62) translate(20,20)"/>' +
|
|
574
|
+
'</svg>';
|
|
575
|
+
}
|
|
576
|
+
function crestSVG(hexColor){
|
|
577
|
+
return '<svg class="crest" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">' +
|
|
578
|
+
'<path d="M12 2L20 6V12C20 17 16.6 20.7 12 22C7.4 20.7 4 17 4 12V6L12 2Z" fill="' + hexColor + '" opacity="0.9"/>' +
|
|
579
|
+
'<path d="M12 6.5L9 12L12 17.5L15 12L12 6.5Z" fill="#1a1120" opacity="0.55"/>' +
|
|
580
|
+
'</svg>';
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
var LEAGUES = [
|
|
584
|
+
{ id:"1M", label:"1M League", min:1e6, max:4.999e6, pool:214880, hex:"#d69a6e", pct:100 },
|
|
585
|
+
{ id:"5M", label:"5M League", min:5e6, max:9.999e6, pool:42110, hex:"#cfd8e6", pct:74 },
|
|
586
|
+
{ id:"10M", label:"10M League", min:10e6, max:99.9e6, pool:9340, hex:"#ffcf6b", pct:56 },
|
|
587
|
+
{ id:"100M",label:"100M League", min:100e6, max:999e6, pool:612, hex:"#f0839c", pct:32 },
|
|
588
|
+
{ id:"1B+", label:"1B+ League", min:1e9, max:Infinity,pool:7, hex:"#cba8ff", pct:12 }
|
|
589
|
+
];
|
|
590
|
+
function leagueById(id){ for (var i=0;i<LEAGUES.length;i++){ if (LEAGUES[i].id===id) return LEAGUES[i]; } return null; }
|
|
591
|
+
function leagueColorHex(id){ var l = leagueById(id); return l ? l.hex : "#ffcf6b"; }
|
|
592
|
+
function fmt(n){ return Math.round(n).toLocaleString("en-US"); }
|
|
593
|
+
function variantFor(handle){ var h=0; for (var i=0;i<handle.length;i++){ h=(h*31+handle.charCodeAt(i))>>>0; } return h % BLOB_PATHS.length; }
|
|
594
|
+
|
|
595
|
+
var state = { connected:false };
|
|
596
|
+
var stackIndex = 0;
|
|
597
|
+
var provider = "claude-code";
|
|
598
|
+
function providerLabel(){ return provider === "codex" ? "Codex" : "Claude Code"; }
|
|
599
|
+
|
|
600
|
+
var steps = {};
|
|
601
|
+
document.querySelectorAll("[data-step]").forEach(function(el){ steps[el.getAttribute("data-step")] = el; });
|
|
602
|
+
function showStep(name){
|
|
603
|
+
Object.keys(steps).forEach(function(k){ steps[k].classList.toggle("is-active", k === name); });
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
document.getElementById("idleAvatar").innerHTML = avatarSVG(3, "#ffb15e");
|
|
607
|
+
document.getElementById("consentAvatar").innerHTML = avatarSVG(3, "#ffb15e");
|
|
608
|
+
|
|
609
|
+
document.querySelectorAll(".provider-picker button").forEach(function(btn){
|
|
610
|
+
btn.addEventListener("click", function(){
|
|
611
|
+
document.querySelectorAll(".provider-picker button").forEach(function(b){ b.classList.remove("is-active"); });
|
|
612
|
+
btn.classList.add("is-active");
|
|
613
|
+
provider = btn.getAttribute("data-harness") || "claude-code";
|
|
614
|
+
});
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
document.getElementById("btnConnect").addEventListener("click", function(){
|
|
618
|
+
document.getElementById("consentProviderLabel").textContent = "Connecting " + providerLabel() + "...";
|
|
619
|
+
showStep("consent");
|
|
620
|
+
});
|
|
621
|
+
document.getElementById("btnCancel").addEventListener("click", function(){ showStep("idle"); });
|
|
622
|
+
|
|
623
|
+
var VERIFY_STEPS = ["Contacting your {p} CLI...","Requesting read-only usage-history scope...","Counting tokens (not reading them)...","Computing your league..."];
|
|
624
|
+
function runConnect(){
|
|
625
|
+
showStep("verifying");
|
|
626
|
+
var fillEl = document.getElementById("progressFill");
|
|
627
|
+
var stepText = document.getElementById("verifyStepText");
|
|
628
|
+
fillEl.style.width = "0%";
|
|
629
|
+
void fillEl.offsetWidth;
|
|
630
|
+
requestAnimationFrame(function(){ fillEl.style.width = "100%"; });
|
|
631
|
+
var i = 0;
|
|
632
|
+
var iv = setInterval(function(){
|
|
633
|
+
stepText.textContent = VERIFY_STEPS[i % VERIFY_STEPS.length].split("{p}").join(providerLabel());
|
|
634
|
+
i++;
|
|
635
|
+
}, 430);
|
|
636
|
+
|
|
637
|
+
fetch("/api/connect", {
|
|
638
|
+
method: "POST",
|
|
639
|
+
headers: { "content-type": "application/json" },
|
|
640
|
+
body: JSON.stringify({ harness: provider })
|
|
641
|
+
}).then(function(r){
|
|
642
|
+
if (!r.ok) throw new Error("connect failed");
|
|
643
|
+
return r.json();
|
|
644
|
+
}).then(function(s){
|
|
645
|
+
clearInterval(iv);
|
|
646
|
+
applyState(s);
|
|
647
|
+
}).catch(function(){
|
|
648
|
+
clearInterval(iv);
|
|
649
|
+
showStep("error");
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
document.getElementById("btnAuthorize").addEventListener("click", runConnect);
|
|
653
|
+
document.getElementById("btnRetry").addEventListener("click", runConnect);
|
|
654
|
+
|
|
655
|
+
function applyState(s){
|
|
656
|
+
state = s || { connected:false };
|
|
657
|
+
if (state.connected){
|
|
658
|
+
renderReveal(state);
|
|
659
|
+
renderYouMarker(state.league);
|
|
660
|
+
stackIndex = 0;
|
|
661
|
+
renderStack();
|
|
662
|
+
showStep("reveal");
|
|
663
|
+
} else {
|
|
664
|
+
stackIndex = 0;
|
|
665
|
+
renderStack();
|
|
666
|
+
showStep("idle");
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function renderReveal(s){
|
|
671
|
+
var lg = leagueById(s.league);
|
|
672
|
+
var hex = lg ? lg.hex : "#ffcf6b";
|
|
673
|
+
document.getElementById("revealAvatar").innerHTML = avatarSVG(3, hex);
|
|
674
|
+
document.getElementById("revealHandle").textContent = (s.handle || "@you") + " - " + (s.harness || "Claude Code");
|
|
675
|
+
|
|
676
|
+
var vline = document.getElementById("verifiedLine");
|
|
677
|
+
vline.innerHTML = s.verified
|
|
678
|
+
? "<span>✓</span> usage verified - read-only OAuth"
|
|
679
|
+
: "<span>✓</span> usage read locally - self-reported";
|
|
680
|
+
|
|
681
|
+
document.getElementById("rawCounter").textContent = "usage aggregated locally";
|
|
682
|
+
document.getElementById("rawReveal").textContent = fmt(s.totalTokens) + " tokens - never leaves this device.";
|
|
683
|
+
|
|
684
|
+
var badge = document.getElementById("leagueBadge");
|
|
685
|
+
badge.classList.remove("is-in");
|
|
686
|
+
badge.style.background = "color-mix(in srgb, " + hex + " 16%, transparent)";
|
|
687
|
+
badge.style.border = "1px solid color-mix(in srgb, " + hex + " 42%, transparent)";
|
|
688
|
+
badge.style.color = hex;
|
|
689
|
+
badge.innerHTML = crestSVG(hex) + "<span>" + (lg ? lg.label : (s.league + " League")) + "</span>";
|
|
690
|
+
requestAnimationFrame(function(){ badge.classList.add("is-in"); });
|
|
691
|
+
|
|
692
|
+
var traitsRow = document.getElementById("traitsRow");
|
|
693
|
+
traitsRow.innerHTML = "";
|
|
694
|
+
var TRAITS = ["night-shift committer","refactor-heavy","context-window hoarder","test-coverage maximalist"];
|
|
695
|
+
TRAITS.forEach(function(t, idx){
|
|
696
|
+
var chip = document.createElement("span");
|
|
697
|
+
chip.className = "trait";
|
|
698
|
+
chip.textContent = t;
|
|
699
|
+
chip.style.animationDelay = (idx * 0.09 + 0.05) + "s";
|
|
700
|
+
traitsRow.appendChild(chip);
|
|
701
|
+
requestAnimationFrame(function(){ chip.classList.add("is-in"); });
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
var rawToggle = document.getElementById("rawToggle");
|
|
706
|
+
rawToggle.addEventListener("click", function(){
|
|
707
|
+
var open = this.getAttribute("aria-expanded") === "true";
|
|
708
|
+
this.setAttribute("aria-expanded", String(!open));
|
|
709
|
+
document.getElementById("rawReveal").classList.toggle("is-open", !open);
|
|
710
|
+
this.textContent = open ? "show raw usage (visible only to you)" : "hide raw usage";
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
document.getElementById("btnReset").addEventListener("click", function(){
|
|
714
|
+
state = { connected:false };
|
|
715
|
+
document.getElementById("rawReveal").classList.remove("is-open");
|
|
716
|
+
rawToggle.setAttribute("aria-expanded","false");
|
|
717
|
+
rawToggle.textContent = "show raw usage (visible only to you)";
|
|
718
|
+
document.querySelectorAll(".you-marker").forEach(function(m){ m.remove(); });
|
|
719
|
+
stackIndex = 0;
|
|
720
|
+
renderStack();
|
|
721
|
+
showStep("idle");
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
var ladderRowsEl = document.getElementById("ladderRows");
|
|
725
|
+
LEAGUES.forEach(function(l){
|
|
726
|
+
var row = document.createElement("div");
|
|
727
|
+
row.className = "ladder-row";
|
|
728
|
+
row.setAttribute("data-league", l.id);
|
|
729
|
+
row.innerHTML =
|
|
730
|
+
'<span class="ladder-label" style="color:' + l.hex + '">' + l.id + '</span>' +
|
|
731
|
+
'<span class="ladder-bar-track"><span class="ladder-bar-fill" style="background:' + l.hex + '"></span></span>' +
|
|
732
|
+
'<span class="ladder-pool mono">' + fmt(l.pool) + '</span>';
|
|
733
|
+
ladderRowsEl.appendChild(row);
|
|
734
|
+
requestAnimationFrame(function(){ row.querySelector(".ladder-bar-fill").style.width = l.pct + "%"; });
|
|
735
|
+
});
|
|
736
|
+
function renderYouMarker(leagueId){
|
|
737
|
+
document.querySelectorAll(".you-marker").forEach(function(m){ m.remove(); });
|
|
738
|
+
var row = ladderRowsEl.querySelector('[data-league="' + leagueId + '"]');
|
|
739
|
+
if (!row) return;
|
|
740
|
+
var marker = document.createElement("span");
|
|
741
|
+
marker.className = "you-marker";
|
|
742
|
+
marker.textContent = "YOU";
|
|
743
|
+
row.querySelector(".ladder-label").appendChild(marker);
|
|
744
|
+
requestAnimationFrame(function(){ marker.classList.add("is-in"); });
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
var explainerBtn = document.getElementById("explainerBtn");
|
|
748
|
+
var explainerPanel = document.getElementById("explainerPanel");
|
|
749
|
+
explainerBtn.addEventListener("click", function(){
|
|
750
|
+
var open = explainerBtn.getAttribute("aria-expanded") === "true";
|
|
751
|
+
explainerBtn.setAttribute("aria-expanded", String(!open));
|
|
752
|
+
explainerPanel.classList.toggle("is-open", !open);
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
var cardStackEl = document.getElementById("cardStack");
|
|
756
|
+
function renderStack(){
|
|
757
|
+
cardStackEl.innerHTML = "";
|
|
758
|
+
var pool = state.connected && state.candidates ? state.candidates : [];
|
|
759
|
+
var visible = pool.slice(stackIndex, stackIndex + 3);
|
|
760
|
+
if (visible.length === 0){
|
|
761
|
+
var empty = document.createElement("div");
|
|
762
|
+
empty.className = "empty-state";
|
|
763
|
+
if (!state.connected){
|
|
764
|
+
empty.innerHTML = "<strong>Connect to see matches.</strong><p>Read your usage to get sorted into a league and start matching.</p>";
|
|
765
|
+
} else {
|
|
766
|
+
empty.innerHTML = "<strong>That's everyone in range.</strong><p>You've matched through today's pool. It refreshes with the next billing cycle.</p>" +
|
|
767
|
+
'<button class="btn btn-primary" id="btnRestart" type="button" style="width:auto;padding:11px 20px;">Start over</button>';
|
|
768
|
+
}
|
|
769
|
+
cardStackEl.appendChild(empty);
|
|
770
|
+
var restart = empty.querySelector("#btnRestart");
|
|
771
|
+
if (restart) restart.addEventListener("click", function(){ stackIndex = 0; renderStack(); });
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
visible.forEach(function(cand, depth){
|
|
775
|
+
var el = document.createElement("article");
|
|
776
|
+
el.className = "match-card";
|
|
777
|
+
el.setAttribute("data-depth", depth);
|
|
778
|
+
var lg = leagueById(cand.league);
|
|
779
|
+
var hex = lg ? lg.hex : "#ffcf6b";
|
|
780
|
+
var label = lg ? lg.label : (cand.league + " League");
|
|
781
|
+
var bio = cand.bio || [];
|
|
782
|
+
el.innerHTML =
|
|
783
|
+
'<div class="mc-top">' +
|
|
784
|
+
'<div class="mc-avatar">' + avatarSVG(variantFor(cand.handle), hex) + '</div>' +
|
|
785
|
+
'<div>' +
|
|
786
|
+
'<div class="mc-handle">' + cand.handle + '</div>' +
|
|
787
|
+
'<span class="mc-league" style="background:color-mix(in srgb,' + hex + ' 16%, transparent);border:1px solid color-mix(in srgb,' + hex + ' 40%, transparent);color:' + hex + '">' + label + '</span>' +
|
|
788
|
+
'<div class="mc-verified">✓ usage verified</div>' +
|
|
789
|
+
'</div>' +
|
|
790
|
+
'</div>' +
|
|
791
|
+
'<div class="mc-bio"><p>' + (bio[0] || "") + '</p><p>' + (bio[1] || "") + '</p></div>' +
|
|
792
|
+
(depth === 0 ?
|
|
793
|
+
'<div class="mc-actions">' +
|
|
794
|
+
'<button class="round-btn pass" type="button" title="Pass">✕</button>' +
|
|
795
|
+
'<button class="round-btn like" type="button" title="Like">♥</button>' +
|
|
796
|
+
'<span class="hint"></span>' +
|
|
797
|
+
'</div>' : '');
|
|
798
|
+
cardStackEl.appendChild(el);
|
|
799
|
+
if (depth === 0){
|
|
800
|
+
el.querySelector(".pass").addEventListener("click", function(){ swipe(el, cand, "pass"); });
|
|
801
|
+
el.querySelector(".like").addEventListener("click", function(){ swipe(el, cand, "like"); });
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function swipe(el, cand, dir){
|
|
807
|
+
el.classList.add(dir === "like" ? "fly-like" : "fly-pass");
|
|
808
|
+
var isMatch = dir === "like" && state.league && cand.league === state.league;
|
|
809
|
+
setTimeout(function(){
|
|
810
|
+
stackIndex++;
|
|
811
|
+
renderStack();
|
|
812
|
+
if (isMatch) openCelebrate(cand);
|
|
813
|
+
}, 360);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
var celebrateEl = document.getElementById("celebrate");
|
|
817
|
+
function openCelebrate(cand){
|
|
818
|
+
var lg = leagueById(cand.league);
|
|
819
|
+
var hex = lg ? lg.hex : "#ffcf6b";
|
|
820
|
+
var youHex = state.league ? leagueColorHex(state.league) : "#ffcf6b";
|
|
821
|
+
document.getElementById("celebrateYou").innerHTML = avatarSVG(3, youHex);
|
|
822
|
+
document.getElementById("celebrateThem").innerHTML = avatarSVG(variantFor(cand.handle), hex);
|
|
823
|
+
document.getElementById("celebrateText").textContent = "You're both in the " + (lg ? lg.label : (cand.league + " League")) + ". " + cand.handle + " just entered the stack.";
|
|
824
|
+
celebrateEl.classList.add("is-open");
|
|
825
|
+
}
|
|
826
|
+
document.getElementById("btnCelebrateClose").addEventListener("click", function(){ celebrateEl.classList.remove("is-open"); });
|
|
827
|
+
celebrateEl.addEventListener("click", function(e){ if (e.target === celebrateEl) celebrateEl.classList.remove("is-open"); });
|
|
828
|
+
|
|
829
|
+
renderStack();
|
|
830
|
+
requestAnimationFrame(function(){ document.body.classList.add("loaded"); });
|
|
831
|
+
|
|
832
|
+
fetch("/api/state").then(function(r){ return r.json(); }).then(function(s){ applyState(s); }).catch(function(){ showStep("idle"); });
|
|
833
|
+
|
|
834
|
+
})();
|
|
835
|
+
</script>
|
|
836
|
+
</body>
|
|
837
|
+
</html>`;
|
|
838
|
+
|
|
839
|
+
// src/server.ts
|
|
840
|
+
function currentState(dir) {
|
|
841
|
+
const p = loadProfile(dir);
|
|
842
|
+
if (!p) return { connected: false, candidates: [] };
|
|
843
|
+
return profileToState(p);
|
|
844
|
+
}
|
|
845
|
+
function profileToState(p) {
|
|
846
|
+
return {
|
|
847
|
+
connected: true,
|
|
848
|
+
handle: p.handle,
|
|
849
|
+
harness: p.harness,
|
|
850
|
+
league: p.league,
|
|
851
|
+
leagueMin: p.leagueMin,
|
|
852
|
+
totalTokens: p.totalTokens,
|
|
853
|
+
verified: p.verified,
|
|
854
|
+
candidates: matches(p.league, CANDIDATES)
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
function send(res, status, contentType, body) {
|
|
858
|
+
res.statusCode = status;
|
|
859
|
+
res.setHeader("content-type", contentType);
|
|
860
|
+
res.setHeader("cache-control", "no-store");
|
|
861
|
+
res.end(body);
|
|
862
|
+
}
|
|
863
|
+
function sendJson(res, status, data) {
|
|
864
|
+
send(res, status, "application/json; charset=utf-8", JSON.stringify(data));
|
|
865
|
+
}
|
|
866
|
+
function readBody(req) {
|
|
867
|
+
return new Promise((resolve, reject) => {
|
|
868
|
+
const chunks = [];
|
|
869
|
+
req.on("data", (c) => chunks.push(c));
|
|
870
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
871
|
+
req.on("error", reject);
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
function startServer(opts = {}) {
|
|
875
|
+
const hostname = opts.hostname ?? "127.0.0.1";
|
|
876
|
+
const server = http.createServer((req, res) => handle(req, res, opts).catch((err) => {
|
|
877
|
+
sendJson(res, 500, { error: err instanceof Error ? err.message : "internal error" });
|
|
878
|
+
}));
|
|
879
|
+
return new Promise((resolve, reject) => {
|
|
880
|
+
server.on("error", reject);
|
|
881
|
+
server.listen(opts.port ?? 0, hostname, () => {
|
|
882
|
+
const addr = server.address();
|
|
883
|
+
const port = typeof addr === "object" && addr ? addr.port : opts.port ?? 0;
|
|
884
|
+
resolve({ server, port, url: `http://${hostname}:${port}` });
|
|
885
|
+
});
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
async function handle(req, res, opts) {
|
|
889
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
890
|
+
const pathname = url.pathname;
|
|
891
|
+
if (req.method === "GET" && pathname === "/") {
|
|
892
|
+
send(res, 200, "text/html; charset=utf-8", webAppHtml);
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
if (req.method === "GET" && pathname === "/api/state") {
|
|
896
|
+
sendJson(res, 200, currentState(opts.dir));
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
if (req.method === "POST" && pathname === "/api/connect") {
|
|
900
|
+
const body = await readBody(req);
|
|
901
|
+
let parsed = {};
|
|
902
|
+
if (body.trim() !== "") {
|
|
903
|
+
try {
|
|
904
|
+
parsed = JSON.parse(body);
|
|
905
|
+
} catch {
|
|
906
|
+
sendJson(res, 400, { error: "invalid JSON body" });
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
const harness = typeof parsed["harness"] === "string" ? parsed["harness"] : "claude-code";
|
|
911
|
+
const handle2 = typeof parsed["handle"] === "string" && parsed["handle"].trim() !== "" ? parsed["handle"] : opts.handle ?? "@you";
|
|
912
|
+
const snapshot = await readUsage(harness);
|
|
913
|
+
const profile = connectProfile(snapshot, handle2, opts.dir);
|
|
914
|
+
sendJson(res, 200, profileToState(profile));
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
sendJson(res, 404, { error: "not found" });
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// src/cli.ts
|
|
921
|
+
var VERSION = "0.1.0";
|
|
922
|
+
function parsePort(raw) {
|
|
923
|
+
const n = Number(raw);
|
|
924
|
+
if (!Number.isInteger(n) || n < 1 || n > 65535) return void 0;
|
|
925
|
+
return n;
|
|
926
|
+
}
|
|
927
|
+
function parseArgs(argv) {
|
|
928
|
+
let out = { command: null, port: void 0 };
|
|
929
|
+
for (let i = 0; i < argv.length; i++) {
|
|
930
|
+
const a = argv[i];
|
|
931
|
+
if (a === "--version" || a === "-v") return { command: "version", port: void 0 };
|
|
932
|
+
if (a === "--help" || a === "-h") return { command: "help", port: void 0 };
|
|
933
|
+
if (a === "--port") {
|
|
934
|
+
const next = argv[i + 1];
|
|
935
|
+
if (next !== void 0) {
|
|
936
|
+
const p = parsePort(next);
|
|
937
|
+
if (p !== void 0) out = { ...out, port: p };
|
|
938
|
+
i++;
|
|
939
|
+
}
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
if (a.startsWith("--port=")) {
|
|
943
|
+
const p = parsePort(a.slice("--port=".length));
|
|
944
|
+
if (p !== void 0) out = { ...out, port: p };
|
|
945
|
+
continue;
|
|
946
|
+
}
|
|
947
|
+
if (a.startsWith("-")) continue;
|
|
948
|
+
const known = a === "connect" || a === "matches" || a === "open" || a === "mcp" || a === "help" ? a : null;
|
|
949
|
+
if (known !== null && out.command === null) {
|
|
950
|
+
out = { ...out, command: known };
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
return out;
|
|
954
|
+
}
|
|
955
|
+
function leagueLabel(name) {
|
|
956
|
+
return name === "below-1M" ? "below 1M (not yet in a league)" : `${name} League`;
|
|
957
|
+
}
|
|
958
|
+
async function cmdConnect() {
|
|
959
|
+
const harness = process.env["VIBEDATING_HARNESS"] ?? "claude-code";
|
|
960
|
+
const handle2 = process.env["VIBEDATING_HANDLE"] ?? "@you";
|
|
961
|
+
const snapshot = await readUsage(harness);
|
|
962
|
+
const profile = connectProfile(snapshot, handle2);
|
|
963
|
+
const lg = league(snapshot.totalTokens);
|
|
964
|
+
process.stdout.write("\n");
|
|
965
|
+
process.stdout.write(` ${leagueLabel(lg.name)}
|
|
966
|
+
`);
|
|
967
|
+
process.stdout.write(` handle: ${profile.handle} \xB7 harness: ${profile.harness}
|
|
968
|
+
`);
|
|
969
|
+
process.stdout.write(
|
|
970
|
+
` verification: ${profile.verified ? "verified (read-only OAuth)" : "self-reported"}
|
|
971
|
+
`
|
|
972
|
+
);
|
|
973
|
+
process.stdout.write("\n");
|
|
974
|
+
process.stdout.write(" \u2022 raw usage stays local \xB7 only league shared\n\n");
|
|
975
|
+
return 0;
|
|
976
|
+
}
|
|
977
|
+
async function cmdMatches() {
|
|
978
|
+
const profile = loadProfile();
|
|
979
|
+
if (!profile) {
|
|
980
|
+
process.stderr.write("Not connected yet. Run `vibedating connect` first.\n");
|
|
981
|
+
return 1;
|
|
982
|
+
}
|
|
983
|
+
const list = matches(profile.league, CANDIDATES);
|
|
984
|
+
process.stdout.write(`Your league: ${leagueLabel(profile.league)}
|
|
985
|
+
`);
|
|
986
|
+
process.stdout.write(`${list.length} candidate${list.length === 1 ? "" : "s"} in range:
|
|
987
|
+
|
|
988
|
+
`);
|
|
989
|
+
if (list.length === 0) {
|
|
990
|
+
process.stdout.write(" (no candidates in range)\n");
|
|
991
|
+
return 0;
|
|
992
|
+
}
|
|
993
|
+
for (const c of list) {
|
|
994
|
+
process.stdout.write(` ${c.handle.padEnd(28)} ${c.league}
|
|
995
|
+
`);
|
|
996
|
+
}
|
|
997
|
+
process.stdout.write("\n");
|
|
998
|
+
return 0;
|
|
999
|
+
}
|
|
1000
|
+
async function cmdOpen(port) {
|
|
1001
|
+
const { url } = await startServer({ port });
|
|
1002
|
+
process.stdout.write(`
|
|
1003
|
+
vibedating local web app \u2192 ${url}
|
|
1004
|
+
|
|
1005
|
+
`);
|
|
1006
|
+
process.stdout.write(" \u2022 raw usage stays local \xB7 only league shared\n");
|
|
1007
|
+
process.stdout.write(" (Ctrl+C to stop)\n\n");
|
|
1008
|
+
return 0;
|
|
1009
|
+
}
|
|
1010
|
+
var HELP = `vibedating ${VERSION} \u2014 dating by tokens (local-first)
|
|
1011
|
+
|
|
1012
|
+
Usage:
|
|
1013
|
+
vibedating connect Read your usage, compute + print your league
|
|
1014
|
+
vibedating matches List candidates in your league
|
|
1015
|
+
vibedating open [--port N] Serve the local web app (default: random port)
|
|
1016
|
+
vibedating mcp Run the stdio MCP server (profile, matches)
|
|
1017
|
+
vibedating --version
|
|
1018
|
+
vibedating --help
|
|
1019
|
+
|
|
1020
|
+
Privacy:
|
|
1021
|
+
Raw token usage is read and stored LOCALLY (~/.vibedating). Only the league
|
|
1022
|
+
bucket is ever shared. v0 has no central directory \u2014 the pool is a seeded demo.
|
|
1023
|
+
|
|
1024
|
+
Env:
|
|
1025
|
+
VIBEDATING_TOKENS=<n> Self-report a token count (e.g. 23400000 or 12M)
|
|
1026
|
+
VIBEDATING_HARNESS=<h> Harness id (claude-code, codex, \u2026)
|
|
1027
|
+
VIBEDATING_HANDLE=<@id> Display handle
|
|
1028
|
+
`;
|
|
1029
|
+
async function main(argv) {
|
|
1030
|
+
const parsed = parseArgs(argv);
|
|
1031
|
+
switch (parsed.command) {
|
|
1032
|
+
case "version":
|
|
1033
|
+
process.stdout.write(`vibedating ${VERSION}
|
|
1034
|
+
`);
|
|
1035
|
+
return 0;
|
|
1036
|
+
case "help":
|
|
1037
|
+
case null:
|
|
1038
|
+
process.stdout.write(HELP);
|
|
1039
|
+
return 0;
|
|
1040
|
+
case "connect":
|
|
1041
|
+
return cmdConnect();
|
|
1042
|
+
case "matches":
|
|
1043
|
+
return cmdMatches();
|
|
1044
|
+
case "open":
|
|
1045
|
+
return cmdOpen(parsed.port);
|
|
1046
|
+
case "mcp":
|
|
1047
|
+
await runMcp();
|
|
1048
|
+
return 0;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
var entryUrl = process.argv[1];
|
|
1052
|
+
if (entryUrl !== void 0) {
|
|
1053
|
+
let isMain = false;
|
|
1054
|
+
try {
|
|
1055
|
+
isMain = import.meta.url === pathToFileURL(entryUrl).href;
|
|
1056
|
+
} catch {
|
|
1057
|
+
isMain = false;
|
|
1058
|
+
}
|
|
1059
|
+
if (isMain) {
|
|
1060
|
+
void main(process.argv.slice(2)).then(
|
|
1061
|
+
(code) => {
|
|
1062
|
+
if (code !== 0) process.exit(code);
|
|
1063
|
+
},
|
|
1064
|
+
(err) => {
|
|
1065
|
+
process.stderr.write(err instanceof Error ? `${err.stack ?? err.message}
|
|
1066
|
+
` : `${String(err)}
|
|
1067
|
+
`);
|
|
1068
|
+
process.exit(1);
|
|
1069
|
+
}
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
export {
|
|
1074
|
+
parseArgs
|
|
1075
|
+
};
|