@ijfw/memory-server 1.3.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/bin/ijfw +27 -0
- package/bin/ijfw-dashboard +180 -0
- package/bin/ijfw-dispatch-plan +41 -0
- package/bin/ijfw-memorize +273 -0
- package/bin/ijfw-memory +51 -0
- package/fixtures/demo-target.js +28 -0
- package/package.json +53 -0
- package/src/api-client.js +190 -0
- package/src/audit-roster.js +315 -0
- package/src/caps.js +37 -0
- package/src/cold-scan-runner.mjs +37 -0
- package/src/compute/edges.js +155 -0
- package/src/compute/extract.js +560 -0
- package/src/compute/fts5.js +420 -0
- package/src/compute/graph-auto-index.js +191 -0
- package/src/compute/graph-lock.js +114 -0
- package/src/compute/index.js +18 -0
- package/src/compute/migration-runner.js +116 -0
- package/src/compute/migrations/001-initial.js +23 -0
- package/src/compute/migrations/002-porter-stemming-source.js +139 -0
- package/src/compute/migrations/003-tier-semantic.js +69 -0
- package/src/compute/migrations/004-kg-tables.js +83 -0
- package/src/compute/migrations/005-stale-candidate.js +72 -0
- package/src/compute/python-resolver.js +106 -0
- package/src/compute/runner-vm.js +185 -0
- package/src/compute/runner.js +416 -0
- package/src/compute/sandbox-detect.js +122 -0
- package/src/compute/sandbox-linux.js +164 -0
- package/src/compute/sandbox-macos.js +167 -0
- package/src/compute/sandbox-windows.js +63 -0
- package/src/compute/schema.sql +118 -0
- package/src/compute/staleness.js +239 -0
- package/src/compute/synonyms.js +367 -0
- package/src/compute/traverse.js +180 -0
- package/src/cost/aggregator.js +229 -0
- package/src/cost/pricing.js +134 -0
- package/src/cost/readers/claude.js +179 -0
- package/src/cost/readers/codex.js +131 -0
- package/src/cost/readers/gemini.js +111 -0
- package/src/cost/savings.js +243 -0
- package/src/cross-dispatcher.js +437 -0
- package/src/cross-orchestrator-cli.js +1885 -0
- package/src/cross-orchestrator.js +598 -0
- package/src/cross-project-search.js +114 -0
- package/src/dashboard-client.html +1180 -0
- package/src/dashboard-server.js +895 -0
- package/src/design-companion.js +81 -0
- package/src/dispatch/colon-syntax.js +732 -0
- package/src/dispatch-planner.js +235 -0
- package/src/dream/cooldown.js +105 -0
- package/src/dream/runner.mjs +373 -0
- package/src/dream/staleness-wiring.js +195 -0
- package/src/feedback-detector.js +57 -0
- package/src/hero-line.js +115 -0
- package/src/importers/claude-mem.js +152 -0
- package/src/importers/cli.js +311 -0
- package/src/importers/common.js +84 -0
- package/src/importers/discover.js +235 -0
- package/src/importers/rtk.js +107 -0
- package/src/intent-router.js +221 -0
- package/src/lib/atomic-io.js +201 -0
- package/src/lib/cache.js +33 -0
- package/src/lib/npm-view.js +104 -0
- package/src/lib/status-card.js +95 -0
- package/src/lib/token.js +85 -0
- package/src/memory/fts5.js +349 -0
- package/src/memory/migration-runner.js +116 -0
- package/src/memory/migrations/001-fts5-init.js +26 -0
- package/src/memory/migrations/002-tier-semantic.js +60 -0
- package/src/memory/migrations/003-stale-candidate.js +60 -0
- package/src/memory/reader.js +300 -0
- package/src/memory/recall-counter.js +76 -0
- package/src/memory/schema.sql +79 -0
- package/src/memory/search.js +431 -0
- package/src/memory/staleness.js +237 -0
- package/src/memory/tier-promotion.js +377 -0
- package/src/memory/tokenize.js +63 -0
- package/src/project-type-detector.js +866 -0
- package/src/prompt-check.js +171 -0
- package/src/ralph-allowlist.js +88 -0
- package/src/receipts.js +129 -0
- package/src/redactor.js +107 -0
- package/src/sandbox.js +275 -0
- package/src/sanitizer.js +69 -0
- package/src/scan-resume.js +167 -0
- package/src/schema.js +82 -0
- package/src/search-bm25.js +108 -0
- package/src/server.js +1414 -0
- package/src/swarm-config.js +80 -0
- package/src/trident/dispatch.js +211 -0
- package/src/trident/lens-health.js +253 -0
- package/src/update-apply.js +79 -0
- package/src/update-check.js +136 -0
- package/src/vectors.js +178 -0
- package/templates/design/bento-grid.md +84 -0
- package/templates/design/brutalist-luxe.md +82 -0
- package/templates/design/cinematic-dark.md +82 -0
- package/templates/design/data-dense-dashboard.md +88 -0
- package/templates/design/editorial-warm.md +81 -0
- package/templates/design/glassmorphic.md +84 -0
- package/templates/design/magazine-editorial.md +84 -0
- package/templates/design/maximalist-vibrant.md +85 -0
- package/templates/design/neo-swiss-tech.md +85 -0
- package/templates/design/swiss-minimal.md +80 -0
- package/templates/design/terminal-native.md +83 -0
- package/templates/design/warm-organic.md +84 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// MCP tool: ijfw_update_check
|
|
2
|
+
//
|
|
3
|
+
// Returns { current, latest, available, confirmation_token?, expires_at?, changelog_url, instruction? }
|
|
4
|
+
// If update is available, issues a 5-min confirmation token. Token must be
|
|
5
|
+
// consumed via terminal-side `ijfw update --confirm <token>` -- see v3 sec 16.
|
|
6
|
+
//
|
|
7
|
+
// Re-entrancy guard: if state.json.last_applied_version == cached last_latest_seen,
|
|
8
|
+
// returns available:false (just-updated suppression).
|
|
9
|
+
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { homedir } from 'node:os';
|
|
12
|
+
import { readSafe, writeAtomic } from './lib/atomic-io.js';
|
|
13
|
+
import { npmView, compareSemver } from './lib/npm-view.js';
|
|
14
|
+
import { issueToken, writePendingSentinel, readPendingSentinel } from './lib/token.js';
|
|
15
|
+
|
|
16
|
+
const PKG = '@ijfw/install';
|
|
17
|
+
const REPO = 'therealseandonahoe/ijfw';
|
|
18
|
+
|
|
19
|
+
function ijfwHome() {
|
|
20
|
+
return process.env.IJFW_HOME || join(homedir(), '.ijfw');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function readInstalledVersion() {
|
|
24
|
+
const sp = readSafe(join(ijfwHome(), 'state.json'));
|
|
25
|
+
if (sp.ok && sp.data && typeof sp.data.installed_version === 'string') {
|
|
26
|
+
return sp.data.installed_version;
|
|
27
|
+
}
|
|
28
|
+
return '0.0.0';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function readLastApplied() {
|
|
32
|
+
const sp = readSafe(join(ijfwHome(), 'state.json'));
|
|
33
|
+
return (sp.ok && sp.data && sp.data.last_applied_version) || null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function readCachedCheck() {
|
|
37
|
+
return readSafe(join(ijfwHome(), 'cache', 'update-check.json'));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function writeCachedCheck(data) {
|
|
41
|
+
return writeAtomic(join(ijfwHome(), 'cache', 'update-check.json'), {
|
|
42
|
+
schema_version: 1,
|
|
43
|
+
last_check: Math.floor(Date.now() / 1000),
|
|
44
|
+
...data,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function ijfwUpdateCheck(args = {}) {
|
|
49
|
+
const sessionId = args.session_id || process.env.IJFW_SESSION_ID || 'default-session';
|
|
50
|
+
const force = args.force === true;
|
|
51
|
+
|
|
52
|
+
const current = readInstalledVersion();
|
|
53
|
+
const lastApplied = readLastApplied();
|
|
54
|
+
const cached = readCachedCheck();
|
|
55
|
+
|
|
56
|
+
let latest = null;
|
|
57
|
+
if (!force && cached.ok && cached.data && typeof cached.data.last_latest_seen === 'string') {
|
|
58
|
+
// Use cached value if fresh enough (24h)
|
|
59
|
+
const ageSec = Math.floor(Date.now() / 1000) - (cached.data.last_check || 0);
|
|
60
|
+
if (ageSec < 24 * 3600 && ageSec >= 0) {
|
|
61
|
+
latest = cached.data.last_latest_seen;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (!latest) {
|
|
65
|
+
const r = await npmView(PKG);
|
|
66
|
+
if (!r.ok) {
|
|
67
|
+
return {
|
|
68
|
+
current,
|
|
69
|
+
latest: null,
|
|
70
|
+
available: null,
|
|
71
|
+
reachable: false,
|
|
72
|
+
error: r.error,
|
|
73
|
+
message: r.message || 'update check failed; will retry next session',
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
latest = r.version;
|
|
77
|
+
try { writeCachedCheck({ last_latest_seen: latest, last_failure: null }); } catch { /* */ }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Re-entrancy: if we just applied this version, don't nudge again
|
|
81
|
+
if (lastApplied && compareSemver(lastApplied, latest) >= 0) {
|
|
82
|
+
return { current, latest, available: false, reachable: true, reason: 'up-to-date' };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const cmp = compareSemver(current, latest);
|
|
86
|
+
const available = cmp < 0;
|
|
87
|
+
|
|
88
|
+
const result = {
|
|
89
|
+
current,
|
|
90
|
+
latest,
|
|
91
|
+
available,
|
|
92
|
+
reachable: true,
|
|
93
|
+
changelog_url: `https://github.com/${REPO}/releases/tag/v${latest}`,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
if (available) {
|
|
97
|
+
const tok = issueToken(sessionId, latest);
|
|
98
|
+
// Write the pending sentinel so the user can run `ijfw update --confirm
|
|
99
|
+
// <token>` in one step. The terminal command itself is the air-gap.
|
|
100
|
+
try { writePendingSentinel(sessionId, latest, tok.token); } catch { /* best-effort; the post-read below covers concurrent failures */ }
|
|
101
|
+
// Concurrent _check calls within the same session race on the token-file
|
|
102
|
+
// + sentinel writes. The last writer wins for both, so reflecting the
|
|
103
|
+
// sentinel's actual content in our response guarantees the returned
|
|
104
|
+
// token matches what `--confirm` will accept.
|
|
105
|
+
let finalToken = tok.token;
|
|
106
|
+
let finalTarget = latest;
|
|
107
|
+
const post = readPendingSentinel(sessionId);
|
|
108
|
+
if (post.ok && post.data && post.data.token) {
|
|
109
|
+
finalToken = post.data.token;
|
|
110
|
+
if (post.data.target_version) finalTarget = post.data.target_version;
|
|
111
|
+
}
|
|
112
|
+
result.confirmation_token = finalToken;
|
|
113
|
+
result.target_version = finalTarget;
|
|
114
|
+
result.expires_at = tok.expires_at;
|
|
115
|
+
result.instruction =
|
|
116
|
+
`To proceed, run in your TERMINAL: ijfw update --confirm ${finalToken}\n` +
|
|
117
|
+
`Token expires in 5 minutes. The MCP tool cannot execute the update directly.`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export const TOOL_DEF = {
|
|
124
|
+
name: 'ijfw_update_check',
|
|
125
|
+
description:
|
|
126
|
+
'Check if an IJFW update is available. Issues a confirmation token; the user must run ' +
|
|
127
|
+
"'ijfw update --confirm <token>' in their terminal to actually update. The model cannot " +
|
|
128
|
+
'execute updates directly -- this air-gaps prompt injection from code execution.',
|
|
129
|
+
inputSchema: {
|
|
130
|
+
type: 'object',
|
|
131
|
+
properties: {
|
|
132
|
+
force: { type: 'boolean', description: 'Bypass cache, fetch from npm now' },
|
|
133
|
+
session_id: { type: 'string', description: 'Session ID for token scoping (optional)' },
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
};
|
package/src/vectors.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// --- Vector embeddings (W3.3 / H5a-b-c) ---
|
|
2
|
+
//
|
|
3
|
+
// Thin wrapper around @xenova/transformers. Lazily imports the library on
|
|
4
|
+
// first use so the zero-deps default install path is unaffected -- users who
|
|
5
|
+
// don't enable vectors never pay the ~5MB bundle cost, and the 23MB model
|
|
6
|
+
// download only happens when the first embedding query fires.
|
|
7
|
+
//
|
|
8
|
+
// Environment control:
|
|
9
|
+
// IJFW_VECTORS=off -- disable vectors entirely (BM25-only)
|
|
10
|
+
// IJFW_VECTORS=on -- enable (default if the library is present)
|
|
11
|
+
// IJFW_VECTORS_MODEL -- override the embedding model (default: Xenova/all-MiniLM-L6-v2, ~23MB)
|
|
12
|
+
//
|
|
13
|
+
// Fallback: if @xenova/transformers isn't installed, vectors silently
|
|
14
|
+
// disable and callers get an `{ available: false, reason }` from getEmbedder().
|
|
15
|
+
|
|
16
|
+
const DEFAULT_MODEL = 'Xenova/all-MiniLM-L6-v2';
|
|
17
|
+
|
|
18
|
+
// X3/S8 -- model integrity pin. When IJFW_VECTORS_MODEL_SHA256 is set, we
|
|
19
|
+
// SHA-256 the loaded model.onnx after download and refuse the embedder if
|
|
20
|
+
// the hash doesn't match. Empty (default) allows any -- documented as opt-in
|
|
21
|
+
// in NO_TELEMETRY.md. Implemented in Phase 6 after the audit found the var
|
|
22
|
+
// was read but never enforced.
|
|
23
|
+
|
|
24
|
+
let _pipelinePromise = null;
|
|
25
|
+
|
|
26
|
+
// R2-B -- locate the actual ONNX file the pipeline loaded. transformers.js
|
|
27
|
+
// uses several cache layouts (HuggingFace-style models--{org}--{name}
|
|
28
|
+
// snapshots; flat cacheDir; explicit localModelPath). Scan candidates,
|
|
29
|
+
// return the first that exists.
|
|
30
|
+
async function resolveModelFile(env, modelId) {
|
|
31
|
+
const { existsSync, readdirSync, statSync } = await import('node:fs');
|
|
32
|
+
const { join: pjoin } = await import('node:path');
|
|
33
|
+
const roots = [];
|
|
34
|
+
if (env && env.localModelPath) roots.push(env.localModelPath);
|
|
35
|
+
if (env && env.cacheDir) roots.push(env.cacheDir);
|
|
36
|
+
if (process.env.IJFW_VECTORS_CACHE) roots.push(process.env.IJFW_VECTORS_CACHE);
|
|
37
|
+
if (process.env.HOME) roots.push(pjoin(process.env.HOME, '.cache', 'huggingface'));
|
|
38
|
+
|
|
39
|
+
const filenames = ['model.onnx', 'model_quantized.onnx', 'model_fp16.onnx'];
|
|
40
|
+
const modelSlugs = [
|
|
41
|
+
modelId,
|
|
42
|
+
modelId.replace('/', '_'),
|
|
43
|
+
'models--' + modelId.replace('/', '--'),
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
for (const root of roots) {
|
|
47
|
+
if (!root || !existsSync(root)) continue;
|
|
48
|
+
// Direct layout: {root}/{slug}/onnx/{file}
|
|
49
|
+
for (const slug of modelSlugs) {
|
|
50
|
+
for (const f of filenames) {
|
|
51
|
+
const p = pjoin(root, slug, 'onnx', f);
|
|
52
|
+
if (existsSync(p)) return p;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// HF snapshots: {root}/models--{org}--{name}/snapshots/{rev}/onnx/{file}
|
|
56
|
+
const hfDir = pjoin(root, 'models--' + modelId.replace('/', '--'));
|
|
57
|
+
if (existsSync(hfDir) && statSync(hfDir).isDirectory()) {
|
|
58
|
+
const snapshotsDir = pjoin(hfDir, 'snapshots');
|
|
59
|
+
if (existsSync(snapshotsDir)) {
|
|
60
|
+
for (const rev of readdirSync(snapshotsDir)) {
|
|
61
|
+
for (const f of filenames) {
|
|
62
|
+
const p = pjoin(snapshotsDir, rev, 'onnx', f);
|
|
63
|
+
if (existsSync(p)) return p;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function verifyModelSha(env, modelId) {
|
|
73
|
+
const expected = process.env.IJFW_VECTORS_MODEL_SHA256;
|
|
74
|
+
if (!expected) return { ok: true }; // no pin configured, skip verification
|
|
75
|
+
try {
|
|
76
|
+
const { createReadStream } = await import('node:fs');
|
|
77
|
+
const { createHash } = await import('node:crypto');
|
|
78
|
+
const modelPath = await resolveModelFile(env, modelId);
|
|
79
|
+
if (!modelPath) {
|
|
80
|
+
// R2-B -- fail OPEN with a clear reason rather than closed. A path-guess
|
|
81
|
+
// miss should not disable a working embedder; surface the lack of
|
|
82
|
+
// verification so the user can set IJFW_VECTORS_CACHE explicitly.
|
|
83
|
+
process.stderr.write(
|
|
84
|
+
`IJFW: SHA verification skipped -- couldn't locate ONNX for ${modelId}. ` +
|
|
85
|
+
`Set IJFW_VECTORS_CACHE to the cache root or clear IJFW_VECTORS_MODEL_SHA256.\n`
|
|
86
|
+
);
|
|
87
|
+
return { ok: true, skipped: true };
|
|
88
|
+
}
|
|
89
|
+
await new Promise((resolve, reject) => {
|
|
90
|
+
const h = createHash('sha256');
|
|
91
|
+
const s = createReadStream(modelPath);
|
|
92
|
+
s.on('error', reject);
|
|
93
|
+
s.on('data', (c) => h.update(c));
|
|
94
|
+
s.on('end', () => {
|
|
95
|
+
const got = h.digest('hex');
|
|
96
|
+
if (got === expected.toLowerCase()) resolve();
|
|
97
|
+
else reject(new Error(`sha256 mismatch at ${modelPath}: expected ${expected}, got ${got}`));
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
return { ok: true, verified: true };
|
|
101
|
+
} catch (e) {
|
|
102
|
+
// Hash mismatch IS a closed-fail (user pinned; we can't trust this model).
|
|
103
|
+
return { ok: false, reason: `sha-verify-failed: ${e.message}` };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function loadPipeline() {
|
|
108
|
+
if (_pipelinePromise) return _pipelinePromise;
|
|
109
|
+
_pipelinePromise = (async () => {
|
|
110
|
+
try {
|
|
111
|
+
const lib = await import('@xenova/transformers');
|
|
112
|
+
const { pipeline, env } = lib;
|
|
113
|
+
// Models cache locally under $XDG_CACHE_HOME or ~/.cache/xenova/.
|
|
114
|
+
env.localModelPath = process.env.IJFW_VECTORS_CACHE || undefined;
|
|
115
|
+
env.allowRemoteModels = true;
|
|
116
|
+
const model = process.env.IJFW_VECTORS_MODEL || DEFAULT_MODEL;
|
|
117
|
+
const extractor = await pipeline('feature-extraction', model);
|
|
118
|
+
// X3/S8 -- verify pinned SHA256 after load (post-download if remote).
|
|
119
|
+
const sha = await verifyModelSha(env, model);
|
|
120
|
+
if (!sha.ok) return { ok: false, reason: sha.reason };
|
|
121
|
+
return { ok: true, extractor, model };
|
|
122
|
+
} catch (e) {
|
|
123
|
+
return { ok: false, reason: e.code === 'ERR_MODULE_NOT_FOUND'
|
|
124
|
+
? 'transformers-not-installed'
|
|
125
|
+
: `load-failed: ${e.message}` };
|
|
126
|
+
}
|
|
127
|
+
})();
|
|
128
|
+
return _pipelinePromise;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function vectorsEnabled() {
|
|
132
|
+
const v = (process.env.IJFW_VECTORS || 'on').toLowerCase();
|
|
133
|
+
return v !== 'off' && v !== '0' && v !== 'false';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Returns { available: true, embed(text) → Float32Array } or
|
|
137
|
+
// { available: false, reason }.
|
|
138
|
+
export async function getEmbedder() {
|
|
139
|
+
if (!vectorsEnabled()) return { available: false, reason: 'disabled-by-env' };
|
|
140
|
+
const loaded = await loadPipeline();
|
|
141
|
+
if (!loaded.ok) return { available: false, reason: loaded.reason };
|
|
142
|
+
return {
|
|
143
|
+
available: true,
|
|
144
|
+
model: loaded.model,
|
|
145
|
+
embed: async (text) => {
|
|
146
|
+
const out = await loaded.extractor(text, { pooling: 'mean', normalize: true });
|
|
147
|
+
return Array.from(out.data);
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Cosine similarity on two equal-length Float32 arrays (or plain arrays).
|
|
153
|
+
// Both inputs should already be L2-normalized (our embedder's normalize: true
|
|
154
|
+
// guarantees that) so this reduces to a dot product.
|
|
155
|
+
export function cosine(a, b) {
|
|
156
|
+
if (!a || !b || a.length !== b.length) return 0;
|
|
157
|
+
let s = 0;
|
|
158
|
+
for (let i = 0; i < a.length; i++) s += a[i] * b[i];
|
|
159
|
+
return s;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Hybrid rerank: BM25 scores + vector cosine. Mixes with weights.
|
|
163
|
+
// bm25Results: [{ id, score, ... }]
|
|
164
|
+
// vectorMatches: Map<id, cosine>
|
|
165
|
+
// Returns merged, resorted list.
|
|
166
|
+
export function hybridRerank(bm25Results, vectorScores, opts = {}) {
|
|
167
|
+
const wBm25 = opts.wBm25 ?? 0.6;
|
|
168
|
+
const wVec = opts.wVec ?? 0.4;
|
|
169
|
+
// Normalize BM25 scores to 0..1 by dividing by max.
|
|
170
|
+
const maxB = Math.max(0.0001, ...bm25Results.map(r => r.score));
|
|
171
|
+
return bm25Results
|
|
172
|
+
.map(r => {
|
|
173
|
+
const vec = vectorScores.get(r.id) ?? 0;
|
|
174
|
+
const merged = (r.score / maxB) * wBm25 + vec * wVec;
|
|
175
|
+
return { ...r, bm25_score: r.score, vector_score: vec, score: merged };
|
|
176
|
+
})
|
|
177
|
+
.sort((a, b) => b.score - a.score);
|
|
178
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Bento Grid - DESIGN.md
|
|
2
|
+
|
|
3
|
+
## 1. Visual Theme & Atmosphere
|
|
4
|
+
Card grid with varied sizes as the primary design language - the aesthetic of modern portfolio sites, SaaS landing pages, and personal product showcases. Visual hierarchy is established through card size rather than typographic scale or color. A 3-column grid where some cards span 2 columns, some span 2 rows, and feature cards command both. Neutral backgrounds keep the cards themselves as the composition. Use for landing pages, portfolio sites, feature showcases, personal sites, and any layout where the arrangement of content is itself the story.
|
|
5
|
+
|
|
6
|
+
## 2. Color Palette & Roles
|
|
7
|
+
- `--color-bg`: #0F0F12 (page background - dark neutral)
|
|
8
|
+
- `--color-surface`: #18181C (standard card)
|
|
9
|
+
- `--color-surface-alt`: #1E1E24 (alternate card for variety)
|
|
10
|
+
- `--color-surface-light`: #F4F4F6 (light card variant for contrast inversion)
|
|
11
|
+
- `--color-border`: #2C2C34 (card borders, dividers)
|
|
12
|
+
- `--color-text-primary`: #EEEEF2 (headings, primary content)
|
|
13
|
+
- `--color-text-secondary`: #88889A (supporting text, labels)
|
|
14
|
+
- `--color-text-on-light`: #18181C (text on light cards)
|
|
15
|
+
- `--color-accent`: #6F6FFF (CTAs, highlights - periwinkle)
|
|
16
|
+
- `--color-accent-hover`: #8F8FFF (interactive accent state)
|
|
17
|
+
- `--color-accent-on-light`: #4040CC (accent on light card surfaces)
|
|
18
|
+
- `--color-separator`: #232328 (between grid cells, hairline rules)
|
|
19
|
+
|
|
20
|
+
## 3. Typography Rules
|
|
21
|
+
- **Display font**: Inter - Google Fonts - weights 300, 400, 600, 700
|
|
22
|
+
- **Body font**: Inter - Google Fonts - weights 400, 500
|
|
23
|
+
- **Mono font**: JetBrains Mono - for stats, metrics, code callouts inside cards
|
|
24
|
+
- **Scale**: Display 56px / H1 40px / H2 26px / H3 18px / Body 15px / Small 12px
|
|
25
|
+
- **Line height**: 1.6 for body / 1.1 for display
|
|
26
|
+
- **Letter spacing**: 0 for body / -0.025em for display
|
|
27
|
+
- **Font loading**: `@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono&display=swap')`
|
|
28
|
+
|
|
29
|
+
## 4. Component Stylings
|
|
30
|
+
### Buttons
|
|
31
|
+
Primary: `background: --color-accent`, white text, `font-weight: 600`, `border-radius: 10px`, `padding: 10px 22px`. Hover: `--color-accent-hover`. Secondary: transparent, `border: 1px solid --color-border`, `color: --color-text-primary`, `border-radius: 10px`. Inside dark cards: secondary button uses `border-color: rgba(255,255,255,0.15)`. Inside light cards: use `--color-accent-on-light`.
|
|
32
|
+
|
|
33
|
+
### Cards
|
|
34
|
+
Standard: `background: --color-surface`, `border: 1px solid --color-border`, `border-radius: 16px`, `padding: 28px`, `overflow: hidden`. Span-2 (wide): same styles, `grid-column: span 2`. Span-2 (tall): `grid-row: span 2`. Feature card (2x2): both spans. Light card: `background: --color-surface-light`, `color: --color-text-on-light`. Hover on all: `border-color: rgba(111,111,255,0.3)`, `box-shadow: 0 0 0 1px rgba(111,111,255,0.15)`.
|
|
35
|
+
|
|
36
|
+
### Navigation
|
|
37
|
+
Height 60px, `background: rgba(15,15,18,0.8)`, `backdrop-filter: blur(12px)`, `border-bottom: 1px solid --color-border`. Sticky. Logo: Inter weight 700. Nav items: 14px, `color: --color-text-secondary`. Active: `color: --color-text-primary`. CTA button right-aligned.
|
|
38
|
+
|
|
39
|
+
### Inputs
|
|
40
|
+
`background: --color-surface-alt`, `border: 1px solid --color-border`, `border-radius: 10px`, `padding: 10px 14px`, `font-size: 14px`, `color: --color-text-primary`. Focus: `border-color: --color-accent`, `box-shadow: 0 0 0 3px rgba(111,111,255,0.15)`. Placeholder: `--color-text-secondary`.
|
|
41
|
+
|
|
42
|
+
### Badges / Chips
|
|
43
|
+
`background: rgba(111,111,255,0.12)`, `border: 1px solid rgba(111,111,255,0.25)`, `color: --color-accent-hover`, `border-radius: 20px`, `padding: 3px 10px`, `font-size: 12px`, `font-weight: 500`. On light cards: `background: rgba(64,64,204,0.08)`, `border-color: rgba(64,64,204,0.2)`, `color: --color-accent-on-light`.
|
|
44
|
+
|
|
45
|
+
## 5. Layout Principles
|
|
46
|
+
- **Grid**: 3-column bento with `gap: 16px`; CSS grid with `grid-template-columns: repeat(3, 1fr)`
|
|
47
|
+
- **Max width**: 1200px
|
|
48
|
+
- **Section padding**: 80px vertical for page sections; cards are flush within the grid
|
|
49
|
+
- **Spacing scale**: 4 / 8 / 12 / 16 / 24 / 28 / 32 / 48 / 64 / 80px
|
|
50
|
+
- **Whitespace philosophy**: structured - the gaps between cards are the whitespace; inside cards is purposeful density
|
|
51
|
+
|
|
52
|
+
## 6. Depth & Elevation
|
|
53
|
+
- **Surface hierarchy**: Level 0 = `--color-bg`, Level 1 = card (`--color-surface`), Level 2 = modal/popover
|
|
54
|
+
- **Shadow tokens**: resting = none (border only); hover = `0 4px 20px rgba(0,0,0,0.4)`; modal = `0 24px 64px rgba(0,0,0,0.6)`
|
|
55
|
+
- **Border usage**: 1px on every card; the border is what gives the bento grid its grid legibility
|
|
56
|
+
|
|
57
|
+
## 7. Do's and Don'ts
|
|
58
|
+
**Do:**
|
|
59
|
+
- Design card contents individually - each card is a mini composition with its own hierarchy
|
|
60
|
+
- Use span-2 and span-2-row assignments to create visual rhythm and feature emphasis
|
|
61
|
+
- Mix dark and light cards to create contrast and visual anchoring points in the grid
|
|
62
|
+
- Keep the gap consistent (16px) - irregular gaps break the bento grid logic
|
|
63
|
+
- Let large metric numbers or illustrations fill the card edge-to-edge on tall cards
|
|
64
|
+
|
|
65
|
+
**Don't:**
|
|
66
|
+
- Use the same size for all cards - uniform grids lose the bento benefit entirely
|
|
67
|
+
- Add thick outer padding around the grid; the card borders define the structure
|
|
68
|
+
- Place navigation or chrome inside the bento grid area
|
|
69
|
+
- Use more than two distinct card backgrounds in the same grid section
|
|
70
|
+
- Apply heavy shadows to resting cards - the hover state needs contrast headroom
|
|
71
|
+
|
|
72
|
+
## 8. Responsive Behavior
|
|
73
|
+
- **Mobile breakpoint**: 375px
|
|
74
|
+
- **Tablet breakpoint**: 768px
|
|
75
|
+
- **Touch targets**: minimum 44px
|
|
76
|
+
- **Typography scaling**: Display drops to 36px / H1 to 26px on mobile
|
|
77
|
+
- **Layout collapse**: 3-col grid → 2-col at 768px → 1-col at 375px; span overrides reset (all cards become 1-col at mobile); gap reduces to 10px
|
|
78
|
+
|
|
79
|
+
## 9. Agent Prompt Guide
|
|
80
|
+
Use these prompts with Claude Design or ijfw-design to stay on-system:
|
|
81
|
+
|
|
82
|
+
- **New screen**: "Build a [screen type] following the Bento Grid DESIGN.md. Match the color tokens and type scale exactly."
|
|
83
|
+
- **Component**: "Add a [component] that fits the Bento Grid aesthetic - reference section 4 for styling rules."
|
|
84
|
+
- **Variant**: "Create a [light/dark/compact] variant of this screen. Keep the same token names, adjust the values."
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Brutalist Luxe - DESIGN.md
|
|
2
|
+
|
|
3
|
+
## 1. Visual Theme & Atmosphere
|
|
4
|
+
Raw brutalism stripped of ugliness and dressed in restraint - the visual language of a Balenciaga campaign or a prestige print magazine that knows the rules and ignores them deliberately. Maximum contrast, visible structure, zero rounding. The grid is exposed, type is oversized, one bold accent color detonates against the monochrome field. Use for fashion, editorial, portfolio, culture, and any brand that positions itself as a category of one.
|
|
5
|
+
|
|
6
|
+
## 2. Color Palette & Roles
|
|
7
|
+
- `--color-bg`: #F5F5F5 (page background - paper white with texture suggestion)
|
|
8
|
+
- `--color-surface`: #FFFFFF (card/panel surface)
|
|
9
|
+
- `--color-surface-dark`: #0A0A0A (inverted section surface)
|
|
10
|
+
- `--color-border`: #0A0A0A (dividers, input borders - hard black)
|
|
11
|
+
- `--color-text-primary`: #0A0A0A (headings, primary content)
|
|
12
|
+
- `--color-text-inverse`: #F5F5F5 (text on dark surfaces)
|
|
13
|
+
- `--color-text-secondary`: #555555 (supporting text, labels)
|
|
14
|
+
- `--color-accent`: #E8FF00 (CTAs, highlights - electric yellow)
|
|
15
|
+
- `--color-accent-hover`: #D4E800 (interactive accent state)
|
|
16
|
+
- `--color-accent-on-dark`: #E8FF00 (accent on dark surfaces - same, still works)
|
|
17
|
+
|
|
18
|
+
## 3. Typography Rules
|
|
19
|
+
- **Display font**: Bebas Neue - Google Fonts - weight 400 (this face has no variants; use size for hierarchy)
|
|
20
|
+
- **Body font**: Inter - Google Fonts - weights 400, 500, 600
|
|
21
|
+
- **Mono font**: JetBrains Mono - for data, code, technical detail
|
|
22
|
+
- **Scale**: Display 96px / H1 56px / H2 36px / H3 24px / Body 16px / Small 13px
|
|
23
|
+
- **Line height**: 1.5 for body / 1.0 for display (Bebas Neue is tight by design)
|
|
24
|
+
- **Letter spacing**: 0 for body / 0.03em for display - Bebas benefits from slight tracking
|
|
25
|
+
- **Font loading**: `@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Inter:wght@400;500;600&family=JetBrains+Mono&display=swap')`
|
|
26
|
+
|
|
27
|
+
## 4. Component Stylings
|
|
28
|
+
### Buttons
|
|
29
|
+
Primary: `background: --color-accent`, `color: #0A0A0A`, `font-family: Inter`, `font-weight: 600`, `border-radius: 0`, `padding: 14px 28px`, `border: 2px solid #0A0A0A`, uppercase, `letter-spacing: 0.06em`. Hover: `background: --color-accent-hover`. Secondary: `background: transparent`, `border: 2px solid #0A0A0A`, `color: #0A0A0A`. No softening anywhere.
|
|
30
|
+
|
|
31
|
+
### Cards
|
|
32
|
+
`background: --color-surface`, `border: 2px solid --color-border`, `border-radius: 0`, `padding: 28px`. No shadow. Optional: thick left bar `border-left: 6px solid --color-accent`. Dark variant: `background: --color-surface-dark`, `color: --color-text-inverse`, `border-color: --color-surface-dark`.
|
|
33
|
+
|
|
34
|
+
### Navigation
|
|
35
|
+
Height 60px, `border-bottom: 2px solid --color-border`, background `--color-bg`. Logo in Bebas Neue 28px. Nav links: Inter `font-weight: 600`, `font-size: 13px`, uppercase, `letter-spacing: 0.08em`. Active: `background: --color-accent`, `color: #0A0A0A`, `padding: 4px 10px`.
|
|
36
|
+
|
|
37
|
+
### Inputs
|
|
38
|
+
`background: --color-bg`, `border: 2px solid --color-border`, `border-radius: 0`, `padding: 11px 12px`, `font-family: Inter`, `font-size: 15px`. Focus: `border-color: #0A0A0A`, `box-shadow: 4px 4px 0 #0A0A0A`. Placeholder: `--color-text-secondary`.
|
|
39
|
+
|
|
40
|
+
### Badges / Chips
|
|
41
|
+
`background: --color-accent`, `color: #0A0A0A`, `border: 2px solid #0A0A0A`, `border-radius: 0`, `padding: 3px 10px`, `font-size: 11px`, `font-family: Inter`, `font-weight: 600`, uppercase, `letter-spacing: 0.06em`. Inverted variant: `background: #0A0A0A`, `color: --color-accent`.
|
|
42
|
+
|
|
43
|
+
## 5. Layout Principles
|
|
44
|
+
- **Grid**: 12-column with 2px visible column rules optional - the structure is the design
|
|
45
|
+
- **Max width**: 1400px
|
|
46
|
+
- **Section padding**: 80px vertical
|
|
47
|
+
- **Spacing scale**: 4 / 8 / 16 / 24 / 32 / 48 / 64 / 80 / 96px
|
|
48
|
+
- **Whitespace philosophy**: intentional asymmetry - some sections are dense, others are a single headline in 40% of the viewport
|
|
49
|
+
|
|
50
|
+
## 6. Depth & Elevation
|
|
51
|
+
- **Surface hierarchy**: Level 0 = `--color-bg`, Level 1 = `--color-surface` (with 2px border), Level 2 = offset shadow only
|
|
52
|
+
- **Shadow tokens**: none (flat) or offset hard shadow `4px 4px 0 #0A0A0A` - no blurred shadows ever
|
|
53
|
+
- **Border usage**: 2px solid black everywhere; borders are structural, not decorative
|
|
54
|
+
|
|
55
|
+
## 7. Do's and Don'ts
|
|
56
|
+
**Do:**
|
|
57
|
+
- Use Bebas Neue at large sizes only (36px+); below that Inter bold takes over
|
|
58
|
+
- Use the electric yellow accent as a weapon - it should feel aggressive when it appears
|
|
59
|
+
- Mix inverted sections (black bg) with light sections for rhythm and drama
|
|
60
|
+
- Let type collide with layout edges; crop is intentional
|
|
61
|
+
- Use the hard 4px offset shadow on interactive cards and focused inputs
|
|
62
|
+
|
|
63
|
+
**Don't:**
|
|
64
|
+
- Round any corner - `border-radius: 0` is a rule, not a suggestion
|
|
65
|
+
- Use more than one accent color; the yellow is the entire accent system
|
|
66
|
+
- Use soft drop shadows (blurred box-shadow); hard offset only
|
|
67
|
+
- Apply subtle animations; transitions are instant or simple slide-in
|
|
68
|
+
- Use imagery with busy compositions - high contrast or solid color photos only
|
|
69
|
+
|
|
70
|
+
## 8. Responsive Behavior
|
|
71
|
+
- **Mobile breakpoint**: 375px
|
|
72
|
+
- **Tablet breakpoint**: 768px
|
|
73
|
+
- **Touch targets**: minimum 44px
|
|
74
|
+
- **Typography scaling**: Display drops to 56px / H1 to 36px on mobile; Bebas Neue stays at all sizes
|
|
75
|
+
- **Layout collapse**: 12-col → single column; column rules removed; padding reduces to 24px; bold borders remain
|
|
76
|
+
|
|
77
|
+
## 9. Agent Prompt Guide
|
|
78
|
+
Use these prompts with Claude Design or ijfw-design to stay on-system:
|
|
79
|
+
|
|
80
|
+
- **New screen**: "Build a [screen type] following the Brutalist Luxe DESIGN.md. Match the color tokens and type scale exactly."
|
|
81
|
+
- **Component**: "Add a [component] that fits the Brutalist Luxe aesthetic - reference section 4 for styling rules."
|
|
82
|
+
- **Variant**: "Create a [light/dark/compact] variant of this screen. Keep the same token names, adjust the values."
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Cinematic Dark - DESIGN.md
|
|
2
|
+
|
|
3
|
+
## 1. Visual Theme & Atmosphere
|
|
4
|
+
Film-inspired luxury dark - the aesthetic of a well-funded production company's website or a prestige tech brand's launch page. Near-absolute black background, cool grey surfaces, silver-white type. Every element is considered and spaced as if a cinematographer approved the composition. Use for flagship product launches, AI products, high-end SaaS, portfolio sites, and anything where first impression must signal quality and ambition.
|
|
5
|
+
|
|
6
|
+
## 2. Color Palette & Roles
|
|
7
|
+
- `--color-bg`: #080808 (page background - near-total black)
|
|
8
|
+
- `--color-surface`: #111114 (card/panel surface - cool dark)
|
|
9
|
+
- `--color-surface-mid`: #1A1A1F (elevated panels, modals)
|
|
10
|
+
- `--color-border`: #242428 (dividers, input borders)
|
|
11
|
+
- `--color-border-subtle`: #1C1C20 (lightest surface separator)
|
|
12
|
+
- `--color-text-primary`: #F0F0F2 (headings, primary content - silver white)
|
|
13
|
+
- `--color-text-secondary`: #6E6E78 (supporting text, labels)
|
|
14
|
+
- `--color-accent`: #D4D4D8 (CTAs, links - cool silver)
|
|
15
|
+
- `--color-accent-hover`: #FFFFFF (interactive accent state - full white)
|
|
16
|
+
- `--color-muted`: #3A3A40 (placeholder text, disabled)
|
|
17
|
+
|
|
18
|
+
## 3. Typography Rules
|
|
19
|
+
- **Display font**: Inter - Google Fonts - weights 200, 300, 400, 600
|
|
20
|
+
- **Body font**: Inter - Google Fonts - weights 300, 400
|
|
21
|
+
- **Mono font**: JetBrains Mono - for data, stats, technical callouts
|
|
22
|
+
- **Scale**: Display 72px / H1 48px / H2 32px / H3 22px / Body 16px / Small 13px
|
|
23
|
+
- **Line height**: 1.65 for body / 1.05 for display
|
|
24
|
+
- **Letter spacing**: 0.01em for body / -0.03em for display / 0.12em for small caps labels
|
|
25
|
+
- **Font loading**: `@import url('https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;600&family=JetBrains+Mono&display=swap')`
|
|
26
|
+
|
|
27
|
+
## 4. Component Stylings
|
|
28
|
+
### Buttons
|
|
29
|
+
Primary: `background: #F0F0F2`, `color: #080808`, `font-weight: 500`, `border-radius: 3px`, `padding: 12px 28px`, `letter-spacing: 0.01em`. Hover: `background: #FFFFFF`. Secondary: transparent, `border: 1px solid --color-border`, `color: --color-text-primary`. No colored accents - this palette is intentionally achromatic.
|
|
30
|
+
|
|
31
|
+
### Cards
|
|
32
|
+
Background `--color-surface`, `border: 1px solid --color-border-subtle`, `border-radius: 6px`, `padding: 32px`. No shadow - dark backgrounds absorb light, borders define. For featured cards: `border-color: --color-border`, subtle inner glow `box-shadow: inset 0 1px 0 rgba(255,255,255,0.04)`.
|
|
33
|
+
|
|
34
|
+
### Navigation
|
|
35
|
+
Height 64px, `border-bottom: 1px solid --color-border-subtle`, background `rgba(8,8,8,0.85)`, `backdrop-filter: blur(12px)`. Sticky. Logo weight 300. Nav items: Inter 14px, `color: --color-text-secondary`. Hover: `color: --color-text-primary`. Active: `color: #FFFFFF`.
|
|
36
|
+
|
|
37
|
+
### Inputs
|
|
38
|
+
`background: --color-surface`, `border: 1px solid --color-border`, `border-radius: 4px`, `padding: 11px 14px`, `font-size: 15px`, `color: --color-text-primary`. Focus: `border-color: #404048`, `box-shadow: 0 0 0 3px rgba(255,255,255,0.04)`. Placeholder: `--color-muted`.
|
|
39
|
+
|
|
40
|
+
### Badges / Chips
|
|
41
|
+
`background: --color-surface-mid`, `border: 1px solid --color-border`, `border-radius: 3px`, `padding: 3px 9px`, `font-size: 11px`, `font-weight: 500`, `color: --color-text-secondary`, `letter-spacing: 0.06em`, uppercase. Understated - status, not decoration.
|
|
42
|
+
|
|
43
|
+
## 5. Layout Principles
|
|
44
|
+
- **Grid**: 12-column, 32px gutters
|
|
45
|
+
- **Max width**: 1440px
|
|
46
|
+
- **Section padding**: 120px vertical
|
|
47
|
+
- **Spacing scale**: 8 / 16 / 24 / 32 / 48 / 64 / 96 / 128 / 160px
|
|
48
|
+
- **Whitespace philosophy**: extreme - cinema uses darkness as framing; UI should too
|
|
49
|
+
|
|
50
|
+
## 6. Depth & Elevation
|
|
51
|
+
- **Surface hierarchy**: Level 0 = `--color-bg`, Level 1 = `--color-surface`, Level 2 = `--color-surface-mid`
|
|
52
|
+
- **Shadow tokens**: low = `0 2px 8px rgba(0,0,0,0.5)`; mid = `0 8px 32px rgba(0,0,0,0.7)`; high = `0 24px 64px rgba(0,0,0,0.9)`
|
|
53
|
+
- **Border usage**: very subtle (1px at 14% opacity on surfaces); let darkness define depth more than borders
|
|
54
|
+
|
|
55
|
+
## 7. Do's and Don'ts
|
|
56
|
+
**Do:**
|
|
57
|
+
- Use Inter at weight 200-300 for large display text - ultra-light type on black is cinematic
|
|
58
|
+
- Let large sections be mostly empty; negative space is composition
|
|
59
|
+
- Use full-bleed sections with edge-to-edge dark backgrounds
|
|
60
|
+
- Treat any imagery as frames - high contrast, desaturated, or masked
|
|
61
|
+
- Apply `backdrop-filter: blur` for sticky nav only; not for cards
|
|
62
|
+
|
|
63
|
+
**Don't:**
|
|
64
|
+
- Introduce any color accent - the achromatic palette is intentional and load-bearing
|
|
65
|
+
- Use shadows that are lighter than the surface (no glows, no coloured light)
|
|
66
|
+
- Round corners aggressively; max 6px maintains the cinematic edge
|
|
67
|
+
- Add animation that feels playful; motion should feel slow and deliberate
|
|
68
|
+
- Use more than three font weights on any single screen
|
|
69
|
+
|
|
70
|
+
## 8. Responsive Behavior
|
|
71
|
+
- **Mobile breakpoint**: 390px
|
|
72
|
+
- **Tablet breakpoint**: 768px
|
|
73
|
+
- **Touch targets**: minimum 44px
|
|
74
|
+
- **Typography scaling**: Display drops to 44px / H1 to 32px on mobile; letter-spacing tightens slightly
|
|
75
|
+
- **Layout collapse**: 12-col → single column; section padding reduces to 64px; nav collapses to hamburger
|
|
76
|
+
|
|
77
|
+
## 9. Agent Prompt Guide
|
|
78
|
+
Use these prompts with Claude Design or ijfw-design to stay on-system:
|
|
79
|
+
|
|
80
|
+
- **New screen**: "Build a [screen type] following the Cinematic Dark DESIGN.md. Match the color tokens and type scale exactly."
|
|
81
|
+
- **Component**: "Add a [component] that fits the Cinematic Dark aesthetic - reference section 4 for styling rules."
|
|
82
|
+
- **Variant**: "Create a [light/dark/compact] variant of this screen. Keep the same token names, adjust the values."
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Data Dense Dashboard - DESIGN.md
|
|
2
|
+
|
|
3
|
+
## 1. Visual Theme & Atmosphere
|
|
4
|
+
Information density first - the aesthetic of tools where the data is the UI. Every pixel that isn't carrying information is a pixel wasted. Compact row heights, monospace data values, status chips with semantic color, and minimal chrome around the content. Dark background keeps the eye on the numbers. Use for monitoring dashboards, analytics platforms, ops tooling, admin panels, and any surface where a user needs to read 40 rows of data without losing their place.
|
|
5
|
+
|
|
6
|
+
## 2. Color Palette & Roles
|
|
7
|
+
- `--color-bg`: #0C0C0F (page background - deep dark)
|
|
8
|
+
- `--color-surface`: #131318 (card/panel surface)
|
|
9
|
+
- `--color-surface-row`: #161619 (table row hover/selected)
|
|
10
|
+
- `--color-border`: #222228 (dividers, table borders)
|
|
11
|
+
- `--color-border-strong`: #2E2E36 (section separators)
|
|
12
|
+
- `--color-text-primary`: #E4E4EF (headings, primary data)
|
|
13
|
+
- `--color-text-secondary`: #6E6E82 (labels, column headers)
|
|
14
|
+
- `--color-text-dim`: #3E3E4E (disabled, empty state)
|
|
15
|
+
- `--color-accent`: #5B6EF5 (active links, primary CTA - indigo)
|
|
16
|
+
- `--color-accent-hover`: #7B8EFF (interactive accent state)
|
|
17
|
+
- `--color-success`: #16A34A
|
|
18
|
+
- `--color-success-bg`: rgba(22,163,74,0.1)
|
|
19
|
+
- `--color-error`: #DC2626
|
|
20
|
+
- `--color-error-bg`: rgba(220,38,38,0.1)
|
|
21
|
+
- `--color-warning`: #D97706
|
|
22
|
+
- `--color-warning-bg`: rgba(217,119,6,0.1)
|
|
23
|
+
|
|
24
|
+
## 3. Typography Rules
|
|
25
|
+
- **Display font**: Inter - Google Fonts - weights 400, 500, 600
|
|
26
|
+
- **Body font**: Inter - Google Fonts - weights 400, 500
|
|
27
|
+
- **Mono font**: JetBrains Mono - weights 400, 500 - all data values, numbers, IDs, timestamps, code
|
|
28
|
+
- **Scale**: Display 32px / H1 24px / H2 18px / H3 14px / Body 13px / Small 11px
|
|
29
|
+
- **Line height**: 1.5 for body / 1.2 for display
|
|
30
|
+
- **Letter spacing**: 0 for body / 0.04em for uppercase column headers
|
|
31
|
+
- **Font loading**: `@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap')`
|
|
32
|
+
|
|
33
|
+
## 4. Component Stylings
|
|
34
|
+
### Buttons
|
|
35
|
+
Primary: `background: --color-accent`, white text, `font-weight: 500`, `border-radius: 5px`, `padding: 6px 14px`, `font-size: 13px`. Hover: `--color-accent-hover`. Secondary: `background: --color-surface`, `border: 1px solid --color-border-strong`, `color: --color-text-primary`, `border-radius: 5px`, same padding. Icon-only buttons are 28x28px, `border-radius: 5px`.
|
|
36
|
+
|
|
37
|
+
### Cards / Panels
|
|
38
|
+
`background: --color-surface`, `border: 1px solid --color-border`, `border-radius: 6px`, `padding: 16px`. Metric card: large JetBrains Mono number top, Inter label below in `--color-text-secondary`. Table panels have `padding: 0` and let the table fill edge to edge inside the border.
|
|
39
|
+
|
|
40
|
+
### Navigation
|
|
41
|
+
Height 44px - compact. `border-bottom: 1px solid --color-border`, background `--color-bg`. Items: Inter 13px, `font-weight: 500`, `color: --color-text-secondary`. Active: `color: --color-text-primary`, left-border indicator `3px solid --color-accent`. Sidebar variant: 220px wide, `border-right: 1px solid --color-border`.
|
|
42
|
+
|
|
43
|
+
### Inputs
|
|
44
|
+
`background: --color-bg`, `border: 1px solid --color-border`, `border-radius: 5px`, `padding: 6px 10px`, `font-size: 13px`, `color: --color-text-primary`. Height 32px - compact. Focus: `border-color: --color-accent`, `box-shadow: 0 0 0 2px rgba(91,110,245,0.15)`. Search inputs prepend a 14px icon with 8px gap.
|
|
45
|
+
|
|
46
|
+
### Badges / Chips
|
|
47
|
+
Status chips: `padding: 2px 7px`, `border-radius: 4px`, `font-size: 11px`, `font-weight: 500`, `font-family: Inter`. Success: `background: --color-success-bg`, `color: #4ADE80`. Error: `background: --color-error-bg`, `color: #F87171`. Warning: `background: --color-warning-bg`, `color: #FCD34D`. Neutral: `background: rgba(110,110,130,0.12)`, `color: --color-text-secondary`.
|
|
48
|
+
|
|
49
|
+
## 5. Layout Principles
|
|
50
|
+
- **Grid**: 12-column, 16px gutters
|
|
51
|
+
- **Max width**: 1600px (dashboards use the full screen)
|
|
52
|
+
- **Section padding**: 24px vertical between panels
|
|
53
|
+
- **Spacing scale**: 2 / 4 / 6 / 8 / 12 / 16 / 24 / 32 / 48px (2px base unit for dense rows)
|
|
54
|
+
- **Whitespace philosophy**: none wasted - every gap is intentional breathing room between data groups
|
|
55
|
+
|
|
56
|
+
## 6. Depth & Elevation
|
|
57
|
+
- **Surface hierarchy**: Level 0 = `#0C0C0F`, Level 1 = `#131318`, Level 2 = `#181820`
|
|
58
|
+
- **Shadow tokens**: low = `0 1px 3px rgba(0,0,0,0.5)`; mid = `0 4px 12px rgba(0,0,0,0.6)`; none on most panels - borders define everything
|
|
59
|
+
- **Border usage**: 1px borders on all panels; hairline 1px `--color-border` between table rows; `--color-border-strong` between logical sections
|
|
60
|
+
|
|
61
|
+
## 7. Do's and Don'ts
|
|
62
|
+
**Do:**
|
|
63
|
+
- Use JetBrains Mono for every numeric value, ID, hash, timestamp, and code string
|
|
64
|
+
- Keep table row height at 36-40px; never pad rows to 52px in a data-dense context
|
|
65
|
+
- Use semantic status colors (`--color-success/error/warning`) consistently across every surface
|
|
66
|
+
- Right-align numeric columns; left-align text columns - this is a non-negotiable data table rule
|
|
67
|
+
- Show column header units (ms, %, $) in `--color-text-secondary` following the label
|
|
68
|
+
|
|
69
|
+
**Don't:**
|
|
70
|
+
- Use large section padding (80px+) - this is a dashboard, not a landing page
|
|
71
|
+
- Apply border-radius beyond 6px; compact aesthetics have sharp but not brutal edges
|
|
72
|
+
- Use gradients or decorative backgrounds behind data panels
|
|
73
|
+
- Show empty cards when data is loading - use skeleton rows at the correct density
|
|
74
|
+
- Mix monospace and proportional fonts on the same data row
|
|
75
|
+
|
|
76
|
+
## 8. Responsive Behavior
|
|
77
|
+
- **Mobile breakpoint**: 375px
|
|
78
|
+
- **Tablet breakpoint**: 1024px (dashboards are desktop-first; tablet is the collapse point)
|
|
79
|
+
- **Touch targets**: minimum 44px
|
|
80
|
+
- **Typography scaling**: all type stays the same; only layout collapses
|
|
81
|
+
- **Layout collapse**: multi-column dashboard → single column stacked panels; sidebar → top nav; tables get horizontal scroll rather than column hiding
|
|
82
|
+
|
|
83
|
+
## 9. Agent Prompt Guide
|
|
84
|
+
Use these prompts with Claude Design or ijfw-design to stay on-system:
|
|
85
|
+
|
|
86
|
+
- **New screen**: "Build a [screen type] following the Data Dense Dashboard DESIGN.md. Match the color tokens and type scale exactly."
|
|
87
|
+
- **Component**: "Add a [component] that fits the Data Dense Dashboard aesthetic - reference section 4 for styling rules."
|
|
88
|
+
- **Variant**: "Create a [light/dark/compact] variant of this screen. Keep the same token names, adjust the values."
|