claude-rpc 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -11
- package/package.json +1 -1
- package/src/cli.js +12 -5
- package/src/community.js +6 -3
- package/src/default-config.js +10 -6
- package/src/install.js +22 -1
- package/src/server/page.js +230 -288
- package/src/version.js +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
**Discord Rich Presence for [Claude Code](https://claude.com/claude-code).**
|
|
11
11
|
Your live model, project, current tool, tokens, and lifetime stats — in your Discord profile. Driven by the hooks Claude Code already fires. Zero polling between sessions.
|
|
12
12
|
|
|
13
|
+
**→ [claude-rpc.vercel.app](https://claude-rpc.vercel.app)** — what it looks like, in one page.
|
|
14
|
+
|
|
15
|
+
[](#community-totals) [](#community-totals)
|
|
16
|
+
|
|
17
|
+
<sub>live — on by default for fresh installs, opt out any time. see [community totals](#community-totals)</sub>
|
|
18
|
+
|
|
13
19
|
[](LICENSE)
|
|
14
20
|
[](https://nodejs.org)
|
|
15
21
|
[](https://claude.com/claude-code)
|
|
@@ -123,23 +129,18 @@ Live equivalents when the daemon is up:
|
|
|
123
129
|
|
|
124
130
|
Cost numbers come from `src/pricing.js`, seeded with **approximate** public list prices. Your actual Claude Code subscription bill is unrelated.
|
|
125
131
|
|
|
126
|
-
### community totals
|
|
127
|
-
|
|
128
|
-
A small Cloudflare Worker ([`worker/`](worker/)) hosts running totals of sessions and tokens across every install that has opted in:
|
|
129
|
-
|
|
130
|
-

|
|
131
|
-

|
|
132
|
+
### community totals
|
|
132
133
|
|
|
133
|
-
The
|
|
134
|
+
The badges at the top of this README are live, served by a small Cloudflare Worker ([`worker/`](worker/)) that holds running totals of sessions and tokens across every install that's reporting. As of v0.7 **fresh installs are on by default** — `setup` mints an anonymous UUID v4 and the daemon starts flushing deltas every 30 minutes. Existing users upgrading from a pre-v0.7 config stay off until they explicitly run `community on` (the consent flow prints the exact payload first).
|
|
134
135
|
|
|
135
136
|
```sh
|
|
136
|
-
claude-rpc community # show state
|
|
137
|
-
claude-rpc community
|
|
138
|
-
claude-rpc community
|
|
137
|
+
claude-rpc community # show state + instanceId (last 8 chars)
|
|
138
|
+
claude-rpc community off # opt out; instanceId retained for re-enable continuity
|
|
139
|
+
claude-rpc community on # explicit consent flow (upgraders / re-enable)
|
|
139
140
|
claude-rpc community report # one-shot manual flush (testing)
|
|
140
141
|
```
|
|
141
142
|
|
|
142
|
-
Each report sends only: a `sessionsDelta`, a `tokensDelta`, the claude-rpc version, OS family (`linux`/`darwin`/`win32`), and
|
|
143
|
+
Each report sends only: a `sessionsDelta`, a `tokensDelta`, the claude-rpc version, OS family (`linux`/`darwin`/`win32`), and the anonymous UUID v4. No prompts, paths, models, repos, costs, usernames, or hostnames — the Worker's [`validateReport`](worker/src/index.js) is the schema of record. The full Worker source is in this repo so the privacy claim is auditable.
|
|
143
144
|
|
|
144
145
|
## three pieces, glued by json files
|
|
145
146
|
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -800,13 +800,16 @@ function doPrivacy() {
|
|
|
800
800
|
|
|
801
801
|
// ── Community totals ─────────────────────────────────────────────────────
|
|
802
802
|
//
|
|
803
|
-
// `claude-rpc community` → show current
|
|
803
|
+
// `claude-rpc community` → show current state + endpoint
|
|
804
804
|
// `claude-rpc community on` → interactive consent flow, mint instanceId
|
|
805
|
+
// (used by pre-v0.7 upgraders; fresh installs
|
|
806
|
+
// already had setup mint the id)
|
|
805
807
|
// `claude-rpc community off` → flip the flag off; instanceId retained
|
|
806
808
|
// `claude-rpc community report` → one-shot manual flush (useful for testing)
|
|
807
809
|
//
|
|
808
810
|
// See src/community.js for the payload schema and worker/src/index.js
|
|
809
|
-
// for the receiving end.
|
|
811
|
+
// for the receiving end. As of v0.7 this is on by default for fresh
|
|
812
|
+
// installs and preserved-off for pre-v0.7 upgraders (see migrateConfig).
|
|
810
813
|
|
|
811
814
|
function prompt(question) {
|
|
812
815
|
return new Promise((resolve) => {
|
|
@@ -1019,8 +1022,8 @@ function overview() {
|
|
|
1019
1022
|
|
|
1020
1023
|
function help() {
|
|
1021
1024
|
const cmds = [
|
|
1022
|
-
['setup', 'Install Claude Code hooks (~/.claude/settings.json)'],
|
|
1023
|
-
['uninstall', 'Remove Claude Code hooks'],
|
|
1025
|
+
['setup', 'Install Claude Code hooks + Windows startup entry (~/.claude/settings.json)'],
|
|
1026
|
+
['uninstall', 'Remove Claude Code hooks + Windows startup entry'],
|
|
1024
1027
|
['upgrade-config', 'Re-run idempotent migrations on an existing config.json'],
|
|
1025
1028
|
['start', 'Start the Discord RPC daemon (detached)'],
|
|
1026
1029
|
['stop', 'Stop the daemon'],
|
|
@@ -1079,7 +1082,11 @@ const packagedDefault = IS_PACKAGED && !cmd;
|
|
|
1079
1082
|
case '--help':
|
|
1080
1083
|
case '-h':
|
|
1081
1084
|
case 'help': help(); break;
|
|
1082
|
-
|
|
1085
|
+
// `setup` and `install` are aliases as of v0.7: both register hooks AND
|
|
1086
|
+
// the Windows startup entry. Older behavior split them (setup = no
|
|
1087
|
+
// startup, install = with) but in practice users expect one command
|
|
1088
|
+
// to do everything. Non-Windows: addStartupEntry is a no-op + warning.
|
|
1089
|
+
case 'setup':
|
|
1083
1090
|
case 'install': await runInstall({ exePath: EXE_PATH || process.execPath }); break;
|
|
1084
1091
|
case 'uninstall': await runUninstall(); break;
|
|
1085
1092
|
case 'upgrade-config': migrateConfig(); break;
|
package/src/community.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Community-totals client. On by default for fresh installs (setup mints
|
|
2
|
+
// the instanceId into the seeded config); existing users upgrading from a
|
|
3
|
+
// pre-v0.7 config keep the explicit-opt-in flow via `claude-rpc community
|
|
4
|
+
// on`. Reads aggregate.json + a small cursor file to compute counter
|
|
5
|
+
// DELTAs (not absolute values — the cursor moves forward as we report),
|
|
6
|
+
// then POSTs to the configured worker endpoint.
|
|
4
7
|
//
|
|
5
8
|
// Three guarantees this module owes the rest of the codebase:
|
|
6
9
|
//
|
package/src/default-config.js
CHANGED
|
@@ -39,13 +39,17 @@ export const DEFAULT_CONFIG = {
|
|
|
39
39
|
filename: "claude.svg",
|
|
40
40
|
public: true,
|
|
41
41
|
},
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
42
|
+
// Community totals. On by default for fresh installs — `setup` mints an
|
|
43
|
+
// anonymous instanceId (UUID v4) into the freshly-seeded config so the
|
|
44
|
+
// daemon starts batching deltas immediately. Existing users upgrading
|
|
45
|
+
// from a version without this block keep their old behavior: migrateConfig
|
|
46
|
+
// writes `community.enabled: false` into their file, and the consent flow
|
|
47
|
+
// at `claude-rpc community on` is the only path to enable. Opt out at any
|
|
48
|
+
// time with `claude-rpc community off`. See worker/src/index.js for the
|
|
49
|
+
// receiving end and exactly what payload is accepted (the validator there
|
|
50
|
+
// is the schema of record).
|
|
47
51
|
community: {
|
|
48
|
-
enabled:
|
|
52
|
+
enabled: true,
|
|
49
53
|
instanceId: null,
|
|
50
54
|
endpoint: "https://claude-rpc-totals.claude-rpc.workers.dev",
|
|
51
55
|
flushIntervalMin: 30,
|
package/src/install.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from 'node:fs';
|
|
10
10
|
import { dirname, join, resolve } from 'node:path';
|
|
11
11
|
import { spawn, spawnSync } from 'node:child_process';
|
|
12
|
+
import { randomUUID } from 'node:crypto';
|
|
12
13
|
import {
|
|
13
14
|
CLAUDE_SETTINGS, CONFIG_PATH, USER_CONFIG_DIR, ROOT,
|
|
14
15
|
HOOK_SCRIPT, IS_PACKAGED, IS_NPM_INSTALL,
|
|
@@ -211,8 +212,18 @@ export function seedConfig() {
|
|
|
211
212
|
return false;
|
|
212
213
|
}
|
|
213
214
|
mkdirSync(USER_CONFIG_DIR, { recursive: true });
|
|
214
|
-
|
|
215
|
+
// Fresh install: mint an anonymous instanceId so community.enabled:true
|
|
216
|
+
// (the new default in v0.7) is immediately actionable — the daemon needs
|
|
217
|
+
// an id to actually flush. Users who want out: `claude-rpc community off`.
|
|
218
|
+
const seeded = JSON.parse(JSON.stringify(DEFAULT_CONFIG));
|
|
219
|
+
if (seeded.community?.enabled && !seeded.community.instanceId) {
|
|
220
|
+
seeded.community.instanceId = randomUUID();
|
|
221
|
+
}
|
|
222
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(seeded, null, 2));
|
|
215
223
|
console.log(` config seeded → ${CONFIG_PATH}`);
|
|
224
|
+
if (seeded.community?.enabled && seeded.community.instanceId) {
|
|
225
|
+
console.log(` community totals on by default → opt out with \`claude-rpc community off\``);
|
|
226
|
+
}
|
|
216
227
|
return true;
|
|
217
228
|
}
|
|
218
229
|
|
|
@@ -279,6 +290,16 @@ export function migrateConfig() {
|
|
|
279
290
|
added.push('presence.byStatus.thinking.state');
|
|
280
291
|
}
|
|
281
292
|
|
|
293
|
+
// v0.7: community.enabled flipped to true in DEFAULT_CONFIG. For users
|
|
294
|
+
// upgrading from a version without a community block, we must NOT
|
|
295
|
+
// silently turn telemetry on — write an explicit `enabled: false` so
|
|
296
|
+
// the deep-merge in loadConfig sees their opt-out. They can run
|
|
297
|
+
// `claude-rpc community on` to consent.
|
|
298
|
+
if (!cfg.community) {
|
|
299
|
+
cfg.community = { enabled: false };
|
|
300
|
+
added.push('community (preserved-off)');
|
|
301
|
+
}
|
|
302
|
+
|
|
282
303
|
if (added.length === 0) {
|
|
283
304
|
console.log(` config up to date → ${CONFIG_PATH}`);
|
|
284
305
|
return false;
|
package/src/server/page.js
CHANGED
|
@@ -20,305 +20,286 @@
|
|
|
20
20
|
|
|
21
21
|
const CSS = `
|
|
22
22
|
:root {
|
|
23
|
-
--
|
|
24
|
-
--
|
|
25
|
-
--
|
|
26
|
-
--
|
|
27
|
-
--
|
|
28
|
-
--
|
|
29
|
-
|
|
30
|
-
--
|
|
31
|
-
--
|
|
32
|
-
--
|
|
33
|
-
|
|
34
|
-
--
|
|
35
|
-
--
|
|
36
|
-
--
|
|
37
|
-
|
|
38
|
-
--
|
|
39
|
-
--
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
--bg
|
|
44
|
-
--
|
|
45
|
-
--
|
|
46
|
-
--
|
|
47
|
-
--
|
|
48
|
-
--
|
|
49
|
-
--
|
|
50
|
-
--
|
|
51
|
-
--
|
|
23
|
+
--paper: #f4ede0;
|
|
24
|
+
--paper-dark: #ebe2d2;
|
|
25
|
+
--ink: #1a1611;
|
|
26
|
+
--ink-soft: #2d2520;
|
|
27
|
+
--ink-mute: #5c5147;
|
|
28
|
+
--ink-faint: #8a7c6d;
|
|
29
|
+
|
|
30
|
+
--rust: #c2491e;
|
|
31
|
+
--tape: #f2d76e;
|
|
32
|
+
--grass: #4a9462;
|
|
33
|
+
|
|
34
|
+
--font-sans: 'Inter', system-ui, sans-serif;
|
|
35
|
+
--font-mono: 'JetBrains Mono', monospace;
|
|
36
|
+
--font-disp: 'Space Grotesk', sans-serif;
|
|
37
|
+
|
|
38
|
+
--shadow: 3px 3px 0 var(--ink);
|
|
39
|
+
--shadow-hover: 5px 5px 0 var(--ink);
|
|
40
|
+
--radius: 0;
|
|
41
|
+
|
|
42
|
+
/* Fallbacks for existing variables used by page.js */
|
|
43
|
+
--bg: var(--paper);
|
|
44
|
+
--text: var(--ink);
|
|
45
|
+
--text-2: var(--ink-soft);
|
|
46
|
+
--text-3: var(--ink-mute);
|
|
47
|
+
--text-4: var(--ink-faint);
|
|
48
|
+
--border: var(--ink);
|
|
49
|
+
--surface: #fff;
|
|
50
|
+
--surface-hover: var(--paper);
|
|
51
|
+
--green: var(--grass);
|
|
52
|
+
--red: #c54a3a;
|
|
53
|
+
--purple: #5865f2; /* using blurple */
|
|
54
|
+
--amber: var(--tape);
|
|
52
55
|
}
|
|
56
|
+
|
|
53
57
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
54
|
-
::selection { background: rgba(
|
|
55
|
-
html, body { background: var(--
|
|
58
|
+
::selection { background: rgba(26,22,17,0.16); }
|
|
59
|
+
html, body { background: var(--paper); color: var(--ink); }
|
|
56
60
|
body {
|
|
57
|
-
font-family:
|
|
61
|
+
font-family: var(--font-sans);
|
|
58
62
|
font-size: 14px; line-height: 1.5;
|
|
59
|
-
font-feature-settings: 'cv11','ss01';
|
|
60
|
-
-webkit-font-smoothing: antialiased;
|
|
61
|
-
font-variant-numeric: tabular-nums;
|
|
62
63
|
min-height: 100vh;
|
|
64
|
+
background-image: radial-gradient(circle at 1px 1px, rgba(26,22,17,0.07) 1px, transparent 0);
|
|
65
|
+
background-size: 20px 20px;
|
|
63
66
|
}
|
|
64
67
|
.num { font-variant-numeric: tabular-nums; }
|
|
65
68
|
a { color: inherit; text-decoration: none; }
|
|
66
69
|
button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }
|
|
70
|
+
h1, h2, h3 { font-family: var(--font-disp); }
|
|
67
71
|
|
|
68
|
-
.page { max-width:
|
|
72
|
+
.page { max-width: 1000px; margin: 0 auto; padding: 40px 40px 100px; }
|
|
69
73
|
|
|
70
74
|
/* ── Top bar ─────────────────────────────────────────── */
|
|
71
75
|
.topbar {
|
|
72
|
-
display: flex;
|
|
73
|
-
padding-bottom:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
77
|
+
padding-bottom: 24px;
|
|
78
|
+
border-bottom: 2px dashed var(--ink-mute);
|
|
79
|
+
margin-bottom: 40px;
|
|
76
80
|
}
|
|
77
81
|
.brand {
|
|
78
|
-
display: flex; align-items: center; gap:
|
|
79
|
-
font-
|
|
82
|
+
display: flex; align-items: center; gap: 12px;
|
|
83
|
+
font-family: var(--font-disp);
|
|
84
|
+
font-weight: 700; font-size: 24px;
|
|
80
85
|
}
|
|
81
86
|
.brand .mark {
|
|
82
|
-
width:
|
|
87
|
+
width: 32px; height: 32px;
|
|
88
|
+
background: var(--ink); color: var(--paper);
|
|
89
|
+
font-family: var(--font-mono); font-size: 14px;
|
|
83
90
|
display: grid; place-items: center;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
border: 2px solid var(--ink);
|
|
92
|
+
box-shadow: 2px 2px 0 var(--rust);
|
|
93
|
+
transform: rotate(-3deg);
|
|
87
94
|
}
|
|
88
|
-
.brand .sep {
|
|
89
|
-
.brand .meta {
|
|
90
|
-
.top-right { margin-left: auto; display: flex; align-items: center; gap:
|
|
95
|
+
.brand .sep { display: none; }
|
|
96
|
+
.brand .meta { display: none; }
|
|
97
|
+
.top-right { margin-left: auto; display: flex; align-items: center; gap: 16px; }
|
|
91
98
|
|
|
92
99
|
.range-pills {
|
|
93
|
-
display: inline-flex; gap:
|
|
94
|
-
background: var(--surface); border: 1px solid var(--border);
|
|
95
|
-
border-radius: 999px;
|
|
100
|
+
display: inline-flex; gap: 4px;
|
|
96
101
|
}
|
|
97
102
|
.range-pills button {
|
|
98
|
-
font-
|
|
99
|
-
|
|
100
|
-
|
|
103
|
+
font-family: var(--font-mono);
|
|
104
|
+
font-size: 12px; padding: 6px 12px; font-weight: 700;
|
|
105
|
+
border: 2px solid transparent; text-transform: uppercase;
|
|
106
|
+
transition: transform 0.12s;
|
|
107
|
+
}
|
|
108
|
+
.range-pills button:hover { transform: translateY(-2px); }
|
|
109
|
+
.range-pills button.active {
|
|
110
|
+
background: var(--tape);
|
|
111
|
+
border-color: var(--ink);
|
|
112
|
+
box-shadow: 2px 2px 0 var(--ink);
|
|
113
|
+
transform: rotate(2deg);
|
|
101
114
|
}
|
|
102
|
-
.range-pills button:hover { color: var(--text); }
|
|
103
|
-
.range-pills button.active { background: var(--text); color: var(--bg); }
|
|
104
115
|
|
|
105
116
|
.status {
|
|
106
117
|
display: inline-flex; align-items: center; gap: 8px;
|
|
107
|
-
font-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
background: var(--green);
|
|
113
|
-
box-shadow: 0 0 0 3px rgba(74,222,128,0.16);
|
|
114
|
-
animation: pulse 2s ease-in-out infinite;
|
|
115
|
-
}
|
|
116
|
-
.status .dot.idle { background: var(--amber); box-shadow: 0 0 0 3px rgba(251,191,36,0.16); animation: none; }
|
|
117
|
-
.status .dot.stale { background: var(--text-4); box-shadow: none; animation: none; }
|
|
118
|
-
@keyframes pulse {
|
|
119
|
-
0%,100% { box-shadow: 0 0 0 3px rgba(74,222,128,0.16); }
|
|
120
|
-
50% { box-shadow: 0 0 0 6px rgba(74,222,128,0.04); }
|
|
118
|
+
font-family: var(--font-mono);
|
|
119
|
+
font-size: 12px; font-weight: 700; text-transform: uppercase;
|
|
120
|
+
background: var(--grass); color: var(--paper);
|
|
121
|
+
padding: 6px 12px; border: 2px solid var(--ink); box-shadow: 2px 2px 0 var(--ink);
|
|
122
|
+
transform: rotate(-2deg);
|
|
121
123
|
}
|
|
122
|
-
.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
.theme-btn:hover { color: var(--text); }
|
|
127
|
-
.model { font-size: 13px; color: var(--text-3); }
|
|
124
|
+
.status .dot { display: none; }
|
|
125
|
+
.theme-btn { display: none; } /* Hide theme toggle, we only use paper now */
|
|
126
|
+
.model { font-family: var(--font-mono); font-size: 12px; font-weight: 700; background: #fff; padding: 4px 8px; border: 2px solid var(--ink); box-shadow: 2px 2px 0 var(--ink); }
|
|
128
127
|
|
|
129
128
|
/* ── Live rail ───────────────────────────────────────── */
|
|
130
129
|
.live-rail {
|
|
131
|
-
display: grid; grid-template-columns:
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
display: grid; grid-template-columns: 80px 1fr auto; gap: 24px; align-items: center;
|
|
131
|
+
background: #fff; padding: 24px;
|
|
132
|
+
border: 2px solid var(--ink); box-shadow: var(--shadow);
|
|
133
|
+
margin-bottom: 48px; position: relative;
|
|
134
|
+
transform: rotate(-0.5deg); transition: transform 0.2s, box-shadow 0.2s;
|
|
135
|
+
}
|
|
136
|
+
.live-rail:hover { transform: rotate(0deg) translate(-2px, -2px); box-shadow: var(--shadow-hover); }
|
|
137
|
+
.live-rail::before {
|
|
138
|
+
content: "LIVE PREVIEW"; position: absolute; top: -12px; right: 24px;
|
|
139
|
+
background: var(--tape); font-family: var(--font-mono); font-weight: 700; font-size: 11px;
|
|
140
|
+
padding: 4px 10px; border: 2px solid var(--ink); box-shadow: 2px 2px 0 var(--ink); transform: rotate(3deg);
|
|
134
141
|
}
|
|
135
142
|
.live-rail .avatar {
|
|
136
|
-
width:
|
|
137
|
-
|
|
138
|
-
overflow: hidden; position: relative;
|
|
143
|
+
width: 80px; height: 80px; background: var(--paper-dark);
|
|
144
|
+
border: 2px solid var(--ink); box-shadow: 2px 2px 0 var(--ink) inset; overflow: hidden;
|
|
139
145
|
}
|
|
140
146
|
.live-rail .avatar img { width: 100%; height: 100%; object-fit: cover; }
|
|
141
|
-
.live-rail .frame-app {
|
|
142
|
-
.live-rail .frame-details { font-
|
|
143
|
-
.live-rail .frame-state { font-size: 13px; color: var(--
|
|
144
|
-
.live-rail .right { text-align: right;
|
|
145
|
-
.live-rail .right .frame-num {
|
|
146
|
-
.live-rail .right .elapsed { font-
|
|
147
|
+
.live-rail .frame-app { display: none; }
|
|
148
|
+
.live-rail .frame-details { font-family: var(--font-disp); font-size: 22px; font-weight: 700; margin-bottom: 4px; }
|
|
149
|
+
.live-rail .frame-state { font-family: var(--font-mono); font-size: 13px; color: var(--ink-mute); }
|
|
150
|
+
.live-rail .right { text-align: right; border-left: 2px dashed var(--ink); padding-left: 24px; }
|
|
151
|
+
.live-rail .right .frame-num { font-family: var(--font-mono); font-size: 11px; font-weight: 700; color: var(--ink-mute); margin-bottom: 8px; text-transform: uppercase; }
|
|
152
|
+
.live-rail .right .elapsed { font-family: var(--font-mono); font-size: 24px; font-weight: 700; }
|
|
147
153
|
|
|
148
154
|
/* ── Hero ────────────────────────────────────────────── */
|
|
149
155
|
.hero {
|
|
150
156
|
display: grid; grid-template-columns: 1fr 1.4fr; gap: 56px;
|
|
151
|
-
align-items: end; margin-bottom:
|
|
157
|
+
align-items: end; margin-bottom: 48px;
|
|
152
158
|
}
|
|
153
159
|
.hero .eyebrow {
|
|
154
|
-
font-size: 12px; color: var(--
|
|
155
|
-
text-transform: uppercase;
|
|
156
|
-
|
|
160
|
+
font-family: var(--font-mono); font-size: 12px; font-weight: 700; color: var(--ink-mute);
|
|
161
|
+
text-transform: uppercase; margin-bottom: 12px;
|
|
162
|
+
border-bottom: 2px solid var(--rust); display: inline-block; padding-bottom: 4px;
|
|
157
163
|
}
|
|
158
|
-
.hero .figure { font-size: 86px; font-weight:
|
|
159
|
-
.hero .unit { font-size:
|
|
160
|
-
.hero .caption { margin-top: 20px; color: var(--
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
.chart-block .chart-
|
|
164
|
-
.chart-block .chart-
|
|
165
|
-
.chart-block .chart-side
|
|
166
|
-
.chart-
|
|
167
|
-
.chart-wrap { position: relative; height: 130px; }
|
|
164
|
+
.hero .figure { font-family: var(--font-disp); font-size: 86px; font-weight: 700; line-height: 0.92; letter-spacing: -0.05em; }
|
|
165
|
+
.hero .unit { font-family: var(--font-mono); font-size: 24px; color: var(--ink-mute); margin-left: 10px; font-weight: 700; }
|
|
166
|
+
.hero .caption { margin-top: 20px; font-family: var(--font-mono); font-size: 13px; color: var(--ink-soft); max-width: 380px; }
|
|
167
|
+
|
|
168
|
+
.chart-block .chart-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 12px; font-family: var(--font-mono); font-weight: 700; }
|
|
169
|
+
.chart-block .chart-title { font-size: 12px; color: var(--ink-mute); text-transform: uppercase; }
|
|
170
|
+
.chart-block .chart-side { font-size: 12px; color: var(--ink-mute); }
|
|
171
|
+
.chart-block .chart-side strong { color: var(--ink); }
|
|
172
|
+
.chart-wrap { position: relative; height: 130px; background: #fff; border: 2px solid var(--ink); box-shadow: var(--shadow); padding: 12px; }
|
|
168
173
|
svg.chart { width: 100%; height: 100%; overflow: visible; }
|
|
169
|
-
svg.chart .grid { stroke: var(--
|
|
174
|
+
svg.chart .grid { stroke: var(--ink-faint); stroke-width: 1; stroke-dasharray: 2 2; }
|
|
170
175
|
svg.chart .area { fill: url(#whiteGrad); }
|
|
171
|
-
svg.chart .line { fill: none; stroke: var(--
|
|
172
|
-
svg.chart .dot { fill: var(--
|
|
173
|
-
svg.chart .ax { fill: var(--
|
|
174
|
-
|
|
175
|
-
/* ── Insights strip ─────────────────────────────────── */
|
|
176
|
-
.insights {
|
|
177
|
-
background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
|
|
178
|
-
padding: 16px 20px; margin-bottom: 28px;
|
|
179
|
-
display: grid; gap: 6px;
|
|
180
|
-
}
|
|
181
|
-
.insights .insight {
|
|
182
|
-
display: flex; align-items: baseline; gap: 10px;
|
|
183
|
-
font-size: 13px; color: var(--text-2); line-height: 1.45;
|
|
184
|
-
}
|
|
185
|
-
.insights .insight::before {
|
|
186
|
-
content: '→'; color: var(--text-4); flex-shrink: 0; font-size: 13px;
|
|
187
|
-
}
|
|
176
|
+
svg.chart .line { fill: none; stroke: var(--rust); stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; }
|
|
177
|
+
svg.chart .dot { fill: var(--rust); stroke: #fff; stroke-width: 2px; }
|
|
178
|
+
svg.chart .ax { fill: var(--ink-mute); font-size: 10px; font-family: var(--font-mono); font-weight: 700; }
|
|
188
179
|
|
|
189
180
|
/* ── Stat cards ──────────────────────────────────────── */
|
|
190
181
|
.stat-row {
|
|
191
|
-
display: grid; grid-template-columns: repeat(4, 1fr); gap:
|
|
192
|
-
margin-bottom:
|
|
182
|
+
display: grid; grid-template-columns: repeat(4, 1fr); gap: 24px;
|
|
183
|
+
margin-bottom: 48px;
|
|
193
184
|
}
|
|
194
185
|
.stat-card {
|
|
195
|
-
background:
|
|
196
|
-
|
|
197
|
-
transition:
|
|
186
|
+
background: #fff; border: 2px solid var(--ink); padding: 24px;
|
|
187
|
+
box-shadow: var(--shadow); position: relative;
|
|
188
|
+
transition: transform 0.2s, box-shadow 0.2s;
|
|
198
189
|
}
|
|
199
|
-
.stat-card:hover {
|
|
200
|
-
.stat-card
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
.stat-card .meta { margin-top: 10px; font-size: 12px; color: var(--text-2); display: flex; align-items: center; gap: 8px; }
|
|
204
|
-
.delta { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 500; padding: 2px 6px; background: rgba(255,255,255,0.04); border-radius: 4px; }
|
|
205
|
-
.delta.up { color: var(--green); background: rgba(74,222,128,0.08); }
|
|
206
|
-
.delta.down { color: var(--red); background: rgba(248,113,113,0.08); }
|
|
207
|
-
.delta.flat { color: var(--text-3); }
|
|
208
|
-
|
|
209
|
-
/* ── Section heading ─────────────────────────────────── */
|
|
210
|
-
.section-head {
|
|
211
|
-
display: flex; align-items: baseline; justify-content: space-between;
|
|
212
|
-
margin-bottom: 16px;
|
|
213
|
-
}
|
|
214
|
-
.section-head h2 { font-size: 13px; font-weight: 500; color: var(--text-3); text-transform: uppercase; letter-spacing: 0.12em; }
|
|
215
|
-
.section-head .right { font-size: 12px; color: var(--text-3); }
|
|
216
|
-
section { margin-bottom: 28px; }
|
|
217
|
-
|
|
218
|
-
/* ── Leaderboards ────────────────────────────────────── */
|
|
219
|
-
.lb-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
|
|
220
|
-
.lb { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
|
|
221
|
-
.lb-h { display: flex; justify-content: space-between; align-items: baseline; padding: 14px 16px 10px; }
|
|
222
|
-
.lb-h .t { font-size: 13px; font-weight: 500; }
|
|
223
|
-
.lb-h .s { font-size: 11px; color: var(--text-3); text-transform: uppercase; letter-spacing: 0.08em; }
|
|
224
|
-
.lb table { width: 100%; border-collapse: collapse; }
|
|
225
|
-
.lb td { padding: 7px 16px; font-size: 12.5px; }
|
|
226
|
-
.lb tr { border-top: 1px solid var(--border); transition: background 0.12s; }
|
|
227
|
-
.lb tr:hover td { background: rgba(255,255,255,0.025); }
|
|
228
|
-
.lb tr.clickable td { cursor: pointer; }
|
|
229
|
-
.lb td.name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 1px; }
|
|
230
|
-
.lb td.val { color: var(--text-2); text-align: right; white-space: nowrap; }
|
|
231
|
-
.lb td.val .u { color: var(--text-3); margin-left: 3px; font-size: 11px; }
|
|
232
|
-
.lb td.name .ico { width: 12px; height: 12px; border-radius: 2px; vertical-align: -2px; margin-right: 7px; opacity: 0.7; }
|
|
233
|
-
|
|
234
|
-
/* ── Cost & languages ────────────────────────────────── */
|
|
235
|
-
.split-row { display: grid; grid-template-columns: 1.2fr 1fr; gap: 12px; margin-bottom: 28px; }
|
|
236
|
-
.card {
|
|
237
|
-
background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
|
|
238
|
-
padding: 20px 22px;
|
|
190
|
+
.stat-card:hover { transform: translate(-2px, -2px); box-shadow: var(--shadow-hover); }
|
|
191
|
+
.stat-card::before {
|
|
192
|
+
content: ''; position: absolute; top: -8px; left: 50%; transform: translateX(-50%);
|
|
193
|
+
width: 40px; height: 16px; background: rgba(0,0,0,0.05); /* tape piece effect */
|
|
239
194
|
}
|
|
240
|
-
.card
|
|
241
|
-
.card
|
|
242
|
-
.card
|
|
195
|
+
.stat-card .label { font-family: var(--font-mono); font-size: 11px; font-weight: 700; color: var(--ink-mute); text-transform: uppercase; border-bottom: 2px solid var(--rust); display: inline-block; padding-bottom: 4px; }
|
|
196
|
+
.stat-card .value { margin-top: 16px; display: flex; align-items: baseline; gap: 6px; font-family: var(--font-disp); font-size: 36px; font-weight: 700; line-height: 1; }
|
|
197
|
+
.stat-card .value .unit { font-family: var(--font-mono); font-size: 16px; color: var(--ink-mute); font-weight: 700; }
|
|
198
|
+
.stat-card .meta { margin-top: 12px; font-family: var(--font-mono); font-size: 12px; color: var(--ink-mute); font-weight: 500; }
|
|
199
|
+
.delta { font-weight: 700; }
|
|
200
|
+
.delta.up { color: var(--grass); }
|
|
201
|
+
.delta.down { color: var(--rust); }
|
|
202
|
+
.delta.flat { color: var(--ink-mute); }
|
|
203
|
+
|
|
204
|
+
/* ── Split: cost + languages ─────────────────────── */
|
|
205
|
+
.split-row { display: grid; grid-template-columns: 1.2fr 1fr; gap: 24px; margin-bottom: 48px; }
|
|
206
|
+
.card { background: #fff; border: 2px solid var(--ink); padding: 24px; box-shadow: var(--shadow); position: relative; }
|
|
207
|
+
.card-h { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 20px; font-family: var(--font-mono); font-weight: 700; }
|
|
208
|
+
.card-h h3 { font-size: 14px; text-transform: uppercase; border-bottom: 2px solid var(--rust); display: inline-block; padding-bottom: 4px; }
|
|
209
|
+
.card-h .meta { font-size: 11px; color: var(--ink-mute); }
|
|
243
210
|
|
|
244
211
|
.cost-grid { display: grid; grid-template-columns: 1.2fr 1fr; gap: 24px; align-items: center; }
|
|
245
|
-
.cost-figure { font-
|
|
246
|
-
.cost-sub { color: var(--
|
|
247
|
-
.cost-bars { display: grid; gap:
|
|
212
|
+
.cost-figure { font-family: var(--font-disp); font-size: 48px; font-weight: 700; line-height: 1; }
|
|
213
|
+
.cost-sub { font-family: var(--font-mono); color: var(--ink-mute); font-size: 12px; margin-top: 8px; font-weight: 700; }
|
|
214
|
+
.cost-bars { display: grid; gap: 12px; font-family: var(--font-mono); font-size: 12px; font-weight: 700; }
|
|
248
215
|
.cost-bar { display: grid; grid-template-columns: 60px 1fr auto; gap: 8px; align-items: center; }
|
|
249
|
-
.cost-bar .name { color: var(--
|
|
250
|
-
.cost-bar .track { height:
|
|
251
|
-
.cost-bar .fill { height: 100%; background: var(--purple); }
|
|
252
|
-
.cost-bar .val { color: var(--
|
|
253
|
-
|
|
254
|
-
.lang-stack { display: flex; height:
|
|
255
|
-
.lang-stack > span { display: block; }
|
|
256
|
-
.lang-
|
|
257
|
-
.lang-list
|
|
258
|
-
.lang-list .
|
|
259
|
-
.lang-list .
|
|
260
|
-
.lang-list .val { color: var(--
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
.churn-
|
|
264
|
-
.churn-spark
|
|
265
|
-
.churn-spark .
|
|
266
|
-
.churn-
|
|
267
|
-
.churn-numbers
|
|
268
|
-
.churn-numbers .
|
|
269
|
-
.churn-numbers .
|
|
270
|
-
.churn-numbers .
|
|
271
|
-
.churn-numbers .
|
|
216
|
+
.cost-bar .name { color: var(--ink-soft); }
|
|
217
|
+
.cost-bar .track { height: 8px; background: var(--paper-dark); border: 1.5px solid var(--ink); overflow: hidden; }
|
|
218
|
+
.cost-bar .fill { height: 100%; background: var(--purple); border-right: 1.5px solid var(--ink); }
|
|
219
|
+
.cost-bar .val { color: var(--ink); }
|
|
220
|
+
|
|
221
|
+
.lang-stack { display: flex; height: 16px; background: var(--paper-dark); border: 2px solid var(--ink); margin-bottom: 20px; box-shadow: 2px 2px 0 var(--ink); }
|
|
222
|
+
.lang-stack > span { display: block; border-right: 1.5px solid var(--ink); }
|
|
223
|
+
.lang-stack > span:last-child { border-right: none; }
|
|
224
|
+
.lang-list { display: grid; gap: 8px; font-family: var(--font-mono); font-size: 12px; font-weight: 700; }
|
|
225
|
+
.lang-list .row { display: grid; grid-template-columns: 14px 1fr auto; gap: 10px; align-items: center; }
|
|
226
|
+
.lang-list .swatch { width: 14px; height: 14px; border: 2px solid var(--ink); }
|
|
227
|
+
.lang-list .val { color: var(--ink-mute); }
|
|
228
|
+
|
|
229
|
+
/* ── Code churn ──────────────────────────────────── */
|
|
230
|
+
.churn-row { display: grid; grid-template-columns: 1fr 200px; gap: 32px; align-items: center; }
|
|
231
|
+
.churn-spark svg { width: 100%; height: 60px; display: block; overflow: visible; }
|
|
232
|
+
.churn-spark .add { fill: var(--grass); stroke: var(--ink); stroke-width: 1.5; }
|
|
233
|
+
.churn-spark .rem { fill: var(--rust); stroke: var(--ink); stroke-width: 1.5; }
|
|
234
|
+
.churn-numbers { display: grid; gap: 8px; font-family: var(--font-mono); font-size: 13px; font-weight: 700; }
|
|
235
|
+
.churn-numbers .row { display: flex; justify-content: space-between; padding-bottom: 4px; border-bottom: 1.5px dashed var(--ink-faint); }
|
|
236
|
+
.churn-numbers .row:last-child { border-bottom: none; }
|
|
237
|
+
.churn-numbers .label { color: var(--ink-mute); }
|
|
238
|
+
.churn-numbers .added { color: var(--grass); }
|
|
239
|
+
.churn-numbers .removed { color: var(--rust); }
|
|
240
|
+
.churn-numbers .net { color: var(--ink); }
|
|
241
|
+
|
|
242
|
+
/* ── Section heading ─────────────────────────────────── */
|
|
243
|
+
.section-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 20px; font-family: var(--font-mono); font-weight: 700; }
|
|
244
|
+
.section-head h2 { font-size: 16px; text-transform: uppercase; background: var(--tape); display: inline-block; padding: 4px 12px; border: 2px solid var(--ink); box-shadow: 2px 2px 0 var(--ink); transform: rotate(-1deg); }
|
|
245
|
+
.section-head .right { font-size: 12px; color: var(--ink-mute); }
|
|
246
|
+
section { margin-bottom: 48px; }
|
|
247
|
+
|
|
248
|
+
/* ── Leaderboards ────────────────────────────────────── */
|
|
249
|
+
.lb-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }
|
|
250
|
+
.lb { background: #fff; border: 2px solid var(--ink); box-shadow: var(--shadow); position: relative; }
|
|
251
|
+
.lb-h { display: flex; justify-content: space-between; align-items: baseline; padding: 16px 20px 12px; font-family: var(--font-mono); font-weight: 700; border-bottom: 2px solid var(--ink); background: var(--paper-dark); }
|
|
252
|
+
.lb-h .t { font-size: 13px; text-transform: uppercase; }
|
|
253
|
+
.lb-h .s { font-size: 11px; color: var(--ink-mute); }
|
|
254
|
+
.lb table { width: 100%; border-collapse: collapse; font-family: var(--font-mono); font-weight: 600; font-size: 12px; }
|
|
255
|
+
.lb td { padding: 10px 20px; border-bottom: 1.5px dashed var(--ink-faint); }
|
|
256
|
+
.lb tr:last-child td { border-bottom: none; }
|
|
257
|
+
.lb tr:hover td { background: var(--paper); }
|
|
258
|
+
.lb td.name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 1px; color: var(--ink); }
|
|
259
|
+
.lb td.val { color: var(--ink-soft); text-align: right; white-space: nowrap; }
|
|
260
|
+
.lb td.name .ico { width: 12px; height: 12px; border: 1.5px solid var(--ink); margin-right: 8px; vertical-align: -2px; }
|
|
272
261
|
|
|
273
262
|
/* ── Discord card ────────────────────────────────────── */
|
|
274
|
-
.discord {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
.
|
|
279
|
-
.
|
|
280
|
-
.
|
|
281
|
-
.live-frame {
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
.
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
293
|
-
.
|
|
294
|
-
.
|
|
295
|
-
.
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
.
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}
|
|
303
|
-
.
|
|
304
|
-
.
|
|
305
|
-
.
|
|
306
|
-
.
|
|
307
|
-
|
|
308
|
-
.
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
padding: 12px 14px;
|
|
315
|
-
opacity: 0.32;
|
|
316
|
-
transition: opacity 0.18s, border-color 0.18s, background 0.18s;
|
|
317
|
-
}
|
|
318
|
-
.achievement.unlocked { opacity: 1; border-color: var(--border-strong); }
|
|
319
|
-
.achievement .ico { font-size: 18px; margin-bottom: 6px; display: block; }
|
|
320
|
-
.achievement .t { font-size: 12px; font-weight: 500; }
|
|
321
|
-
.achievement .s { font-size: 10.5px; color: var(--text-3); margin-top: 2px; }
|
|
263
|
+
.discord { background: #fff; border: 2px solid var(--ink); padding: 32px; box-shadow: var(--shadow); position: relative; }
|
|
264
|
+
.discord-h { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 24px; font-family: var(--font-mono); font-weight: 700; }
|
|
265
|
+
.discord-h .t { font-size: 14px; text-transform: uppercase; border-bottom: 2px solid var(--rust); padding-bottom: 4px; }
|
|
266
|
+
.live-frame { padding: 24px 0; border-top: 2px solid var(--ink); border-bottom: 2px dashed var(--ink); }
|
|
267
|
+
.live-frame .label-tag { font-family: var(--font-mono); font-size: 11px; font-weight: 700; background: var(--grass); color: #fff; padding: 4px 8px; border: 1.5px solid var(--ink); box-shadow: 2px 2px 0 var(--ink); display: inline-block; margin-bottom: 12px; transform: rotate(-2deg); }
|
|
268
|
+
.live-frame .label-tag::before { display: none; }
|
|
269
|
+
.live-frame .details { font-family: var(--font-disp); font-size: 24px; font-weight: 700; margin-bottom: 4px; }
|
|
270
|
+
.live-frame .state { font-family: var(--font-mono); font-size: 13px; color: var(--ink-soft); font-weight: 600; }
|
|
271
|
+
.rotation-list { list-style: none; margin-top: 24px; display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px 24px; font-family: var(--font-mono); font-size: 12px; font-weight: 700; }
|
|
272
|
+
.rotation-list li { display: flex; align-items: center; gap: 10px; color: var(--ink-soft); }
|
|
273
|
+
.rotation-list li .pip { width: 8px; height: 8px; background: var(--paper-dark); border: 1.5px solid var(--ink); flex-shrink: 0; }
|
|
274
|
+
.rotation-list li.live .pip { background: var(--grass); }
|
|
275
|
+
.rotation-list li.current { color: var(--ink); }
|
|
276
|
+
.rotation-list li.current .pip { background: var(--ink); box-shadow: 2px 2px 0 var(--ink); }
|
|
277
|
+
|
|
278
|
+
/* ── Achievements, Insights, Modals ─────────────────── */
|
|
279
|
+
.achievements { display: grid; grid-template-columns: repeat(6, 1fr); gap: 16px; margin-bottom: 48px; }
|
|
280
|
+
.achievement { background: #fff; border: 2px dashed var(--ink-faint); padding: 16px; text-align: center; opacity: 0.5; transition: all 0.2s; }
|
|
281
|
+
.achievement.unlocked { opacity: 1; border: 2px solid var(--ink); box-shadow: var(--shadow); transform: rotate(1deg); }
|
|
282
|
+
.achievement .ico { font-size: 24px; margin-bottom: 8px; display: block; }
|
|
283
|
+
.achievement .t { font-family: var(--font-mono); font-size: 12px; font-weight: 700; text-transform: uppercase; margin-bottom: 4px; }
|
|
284
|
+
.achievement .s { font-size: 11px; color: var(--ink-mute); }
|
|
285
|
+
|
|
286
|
+
.insights { background: #fff; border: 2px solid var(--ink); padding: 24px; margin-bottom: 48px; box-shadow: var(--shadow); display: grid; gap: 12px; }
|
|
287
|
+
.insights .insight { display: flex; align-items: baseline; gap: 12px; font-family: var(--font-mono); font-size: 13px; font-weight: 600; }
|
|
288
|
+
.insights .insight::before { content: '→'; color: var(--rust); font-weight: 700; }
|
|
289
|
+
|
|
290
|
+
.scrim { position: fixed; inset: 0; background: rgba(26,22,17,0.4); backdrop-filter: blur(2px); display: none; z-index: 50; }
|
|
291
|
+
.scrim.open { display: block; }
|
|
292
|
+
.drawer { position: fixed; top: 0; right: 0; bottom: 0; width: 480px; max-width: 100%; background: var(--paper); border-left: 2px solid var(--ink); transform: translateX(100%); transition: transform 0.2s ease; z-index: 60; padding: 40px; overflow-y: auto; box-shadow: -10px 0 30px rgba(0,0,0,0.1); }
|
|
293
|
+
.drawer.open { transform: translateX(0); }
|
|
294
|
+
.drawer h3 { font-family: var(--font-disp); font-size: 28px; margin-bottom: 8px; }
|
|
295
|
+
.drawer .sub { font-family: var(--font-mono); color: var(--ink-mute); font-weight: 700; margin-bottom: 32px; }
|
|
296
|
+
|
|
297
|
+
.modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0.96); background: var(--paper); border: 2px solid var(--ink); padding: 40px; box-shadow: 10px 10px 0 var(--ink); z-index: 60; opacity: 0; pointer-events: none; transition: all 0.2s; }
|
|
298
|
+
.modal.open { opacity: 1; transform: translate(-50%, -50%) scale(1); pointer-events: auto; }
|
|
299
|
+
.modal h4 { font-family: var(--font-disp); font-size: 24px; margin-bottom: 8px; }
|
|
300
|
+
|
|
301
|
+
footer { margin-top: 60px; padding-top: 32px; border-top: 2px dashed var(--ink); display: flex; justify-content: space-between; align-items: center; font-family: var(--font-mono); font-weight: 700; font-size: 12px; color: var(--ink-mute); }
|
|
302
|
+
footer .pulse-dot { background: var(--grass); border: 1.5px solid var(--ink); }
|
|
322
303
|
|
|
323
304
|
/* ── Heatmap ─────────────────────────────────────────── */
|
|
324
305
|
.heatmap-card { padding: 20px 22px; }
|
|
@@ -328,55 +309,16 @@ section { margin-bottom: 28px; }
|
|
|
328
309
|
display: grid; grid-auto-flow: column; grid-template-rows: repeat(7, 12px); gap: 3px;
|
|
329
310
|
font-size: 0;
|
|
330
311
|
}
|
|
331
|
-
.heatmap .cell { width: 12px; height: 12px; border-radius: 2px; background: rgba(
|
|
312
|
+
.heatmap .cell { width: 12px; height: 12px; border-radius: 2px; background: rgba(0,0,0,0.06); cursor: pointer; transition: transform 0.1s; }
|
|
332
313
|
.heatmap .cell:hover { transform: scale(1.4); outline: 1px solid var(--text); }
|
|
333
314
|
|
|
334
|
-
/* ── Drawer / modal ─────────────────────────────────── */
|
|
335
|
-
.scrim { position: fixed; inset: 0; background: rgba(0,0,0,0.55); display: none; z-index: 50; }
|
|
336
|
-
.scrim.open { display: block; }
|
|
337
|
-
.drawer {
|
|
338
|
-
position: fixed; top: 0; right: 0; bottom: 0; width: 480px; max-width: 100%;
|
|
339
|
-
background: var(--bg); border-left: 1px solid var(--border);
|
|
340
|
-
transform: translateX(100%); transition: transform 0.22s ease;
|
|
341
|
-
z-index: 60; padding: 32px 28px; overflow-y: auto;
|
|
342
|
-
}
|
|
343
|
-
.drawer.open { transform: translateX(0); }
|
|
344
|
-
.drawer .close { position: absolute; top: 20px; right: 22px; font-size: 22px; color: var(--text-3); }
|
|
345
|
-
.drawer h3 { font-size: 22px; font-weight: 600; letter-spacing: -0.01em; margin-bottom: 6px; }
|
|
346
|
-
.drawer .sub { color: var(--text-3); font-size: 13px; margin-bottom: 22px; }
|
|
347
|
-
.drawer .grid { display: grid; gap: 12px; }
|
|
348
|
-
.drawer .kv { display: flex; justify-content: space-between; font-size: 13px; padding: 8px 0; border-bottom: 1px solid var(--border); }
|
|
349
|
-
.drawer .kv .k { color: var(--text-3); }
|
|
350
|
-
.drawer .kv .v { color: var(--text); font-weight: 500; }
|
|
351
|
-
|
|
352
|
-
.modal {
|
|
353
|
-
position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0.96);
|
|
354
|
-
background: var(--bg); border: 1px solid var(--border-strong); border-radius: 14px;
|
|
355
|
-
padding: 28px 32px; min-width: 360px; max-width: 480px;
|
|
356
|
-
z-index: 60; opacity: 0; pointer-events: none; transition: opacity 0.18s, transform 0.18s;
|
|
357
|
-
}
|
|
358
|
-
.modal.open { opacity: 1; transform: translate(-50%, -50%) scale(1); pointer-events: auto; }
|
|
359
|
-
.modal .close { position: absolute; top: 14px; right: 16px; font-size: 22px; color: var(--text-3); }
|
|
360
|
-
.modal h4 { font-size: 18px; font-weight: 600; margin-bottom: 6px; }
|
|
361
|
-
.modal .sub { color: var(--text-3); font-size: 12px; margin-bottom: 18px; }
|
|
362
|
-
|
|
363
|
-
footer {
|
|
364
|
-
margin-top: 36px; padding-top: 22px;
|
|
365
|
-
border-top: 1px solid var(--border);
|
|
366
|
-
display: flex; justify-content: space-between; align-items: center;
|
|
367
|
-
font-size: 12px; color: var(--text-3);
|
|
368
|
-
}
|
|
369
|
-
footer .pulse { display: inline-flex; align-items: center; gap: 6px; }
|
|
370
|
-
footer .pulse-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--green); opacity: 0.6; }
|
|
371
|
-
footer a:hover { color: var(--text-2); }
|
|
372
|
-
|
|
373
315
|
/* ── Help overlay ────────────────────────────────────── */
|
|
374
|
-
.help { position: fixed; inset: 0; background: rgba(
|
|
316
|
+
.help { position: fixed; inset: 0; background: rgba(26,22,17,0.7); display: none; z-index: 70; align-items: center; justify-content: center; }
|
|
375
317
|
.help.open { display: flex; }
|
|
376
|
-
.help-card { background: var(--
|
|
377
|
-
.help-card h4 { font-size:
|
|
378
|
-
.help-card .kbd { display: inline-block; padding: 2px 6px; border:
|
|
379
|
-
.help-card .row { display: flex; padding:
|
|
318
|
+
.help-card { background: var(--paper); border: 2px solid var(--ink); box-shadow: var(--shadow); padding: 28px 32px; max-width: 420px; width: 90%; }
|
|
319
|
+
.help-card h4 { font-family: var(--font-disp); font-size: 20px; margin-bottom: 16px; }
|
|
320
|
+
.help-card .kbd { display: inline-block; padding: 2px 6px; border: 1.5px solid var(--ink); background: #fff; font-size: 11px; font-family: var(--font-mono); margin-right: 8px; font-weight: 700; box-shadow: 1.5px 1.5px 0 var(--ink); }
|
|
321
|
+
.help-card .row { display: flex; padding: 8px 0; border-top: 1.5px dashed var(--ink-faint); font-family: var(--font-mono); font-size: 12px; font-weight: 700; color: var(--ink-soft); }
|
|
380
322
|
.help-card .row:first-of-type { border-top: 0; }
|
|
381
323
|
.help-card .keys { width: 110px; }
|
|
382
324
|
|