@cs4alhaider/screenbook 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/CLAUDE.md +185 -0
- package/LICENSE +21 -0
- package/README.md +208 -0
- package/TESTING.md +54 -0
- package/app-bridge.js +131 -0
- package/bridge.js +598 -0
- package/cli/screenbook.js +290 -0
- package/cli/templates.js +139 -0
- package/core/browser.js +108 -0
- package/core/fsx.js +71 -0
- package/core/meta.js +94 -0
- package/core/project.js +482 -0
- package/core/serve.js +224 -0
- package/docs/README.md +21 -0
- package/docs/app-mode-guide.md +79 -0
- package/docs/authoring-guide.md +129 -0
- package/docs/cli.md +78 -0
- package/docs/getting-started.md +88 -0
- package/docs/mcp-agents.md +60 -0
- package/docs/shots/android-dark.png +0 -0
- package/docs/shots/app-mode.png +0 -0
- package/docs/shots/hero-studio.png +0 -0
- package/docs/shots/rtl-arabic.png +0 -0
- package/docs/shots/theme-lab.png +0 -0
- package/docs/shots/theming-noir.png +0 -0
- package/docs/shots/web-dashboard.png +0 -0
- package/docs/studio-guide.md +64 -0
- package/docs/troubleshooting.md +55 -0
- package/examples/qahwa/app.config.json +9 -0
- package/examples/qahwa/features/order/data/scenarios.json +25 -0
- package/examples/qahwa/features/order/data/seed.json +21 -0
- package/examples/qahwa/features/order/feature.json +7 -0
- package/examples/qahwa/features/order/flows.json +16 -0
- package/examples/qahwa/features/order/manifest.json +78 -0
- package/examples/qahwa/features/order/screens/010-menu.html +121 -0
- package/examples/qahwa/features/order/screens/020-drink.html +120 -0
- package/examples/qahwa/features/order/screens/030-cart.html +99 -0
- package/examples/qahwa/features/order/screens/040-status.html +125 -0
- package/examples/qahwa/features/order/screens/050-status-android.html +95 -0
- package/examples/qahwa/features/order/screens/shared.css +146 -0
- package/examples/qahwa/features/roastery/data/scenarios.json +35 -0
- package/examples/qahwa/features/roastery/data/seed.json +17 -0
- package/examples/qahwa/features/roastery/feature.json +7 -0
- package/examples/qahwa/features/roastery/flows.json +14 -0
- package/examples/qahwa/features/roastery/manifest.json +32 -0
- package/examples/qahwa/features/roastery/screens/010-orders.html +103 -0
- package/examples/qahwa/features/roastery/screens/020-menu-editor.html +88 -0
- package/examples/qahwa/features/roastery/screens/shared.css +115 -0
- package/examples/qahwa/review/comments.json +3 -0
- package/examples/qahwa/themes/qahwa.json +43 -0
- package/examples/spa-demo/app/index.html +100 -0
- package/examples/spa-demo/app.config.json +9 -0
- package/examples/spa-demo/features/notes-app/feature.json +11 -0
- package/examples/spa-demo/review/comments.json +3 -0
- package/examples/spa-demo/themes/default.json +15 -0
- package/examples/tokens-lab/app.config.json +9 -0
- package/examples/tokens-lab/features/lab/data/scenarios.json +5 -0
- package/examples/tokens-lab/features/lab/data/seed.json +3 -0
- package/examples/tokens-lab/features/lab/feature.json +7 -0
- package/examples/tokens-lab/features/lab/flows.json +14 -0
- package/examples/tokens-lab/features/lab/manifest.json +33 -0
- package/examples/tokens-lab/features/lab/screens/010-swatches.html +112 -0
- package/examples/tokens-lab/features/lab/screens/020-typography.html +115 -0
- package/examples/tokens-lab/review/comments.json +3 -0
- package/examples/tokens-lab/themes/noir.json +32 -0
- package/examples/tokens-lab/themes/paper.json +32 -0
- package/index.html +50 -0
- package/mcp/server.js +366 -0
- package/package.json +57 -0
- package/shell/flowplayer.js +85 -0
- package/shell/frames.js +108 -0
- package/shell/main.js +397 -0
- package/shell/net.js +126 -0
- package/shell/review.js +213 -0
- package/shell/screenhost.js +380 -0
- package/shell/selftest.js +275 -0
- package/shell/shell.css +952 -0
- package/shell/sidebar.js +112 -0
- package/shell/store.js +119 -0
- package/shell/themelab.js +186 -0
- package/shell/toast.js +15 -0
- package/shell/toolbar.js +128 -0
- package/skill/SKILL.md +52 -0
- package/skill/references/app-mode.md +77 -0
- package/skill/references/bridge-api.md +55 -0
- package/skill/references/cli-mcp.md +53 -0
- package/skill/references/flows.md +44 -0
- package/skill/references/i18n.md +52 -0
- package/skill/references/scenarios.md +45 -0
- package/skill/references/screen-contract.md +83 -0
- package/skill/references/tokens.md +56 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/* Roastery desk — shared web chrome. Tokens only. */
|
|
2
|
+
|
|
3
|
+
* { box-sizing: border-box; }
|
|
4
|
+
html, body { height: 100%; }
|
|
5
|
+
body {
|
|
6
|
+
margin: 0;
|
|
7
|
+
background: var(--color-bg);
|
|
8
|
+
color: var(--color-ink);
|
|
9
|
+
font-family: var(--font-sans);
|
|
10
|
+
-webkit-font-smoothing: antialiased;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.desk { min-height: 100vh; display: flex; flex-direction: column; }
|
|
14
|
+
|
|
15
|
+
.desk-top {
|
|
16
|
+
display: flex;
|
|
17
|
+
align-items: center;
|
|
18
|
+
justify-content: space-between;
|
|
19
|
+
gap: 16px;
|
|
20
|
+
padding: 14px 26px;
|
|
21
|
+
background: var(--color-card);
|
|
22
|
+
border-bottom: 1px solid var(--color-line);
|
|
23
|
+
}
|
|
24
|
+
.desk-top .brand { display: flex; align-items: center; gap: 10px; font-weight: 800; font-size: 15px; }
|
|
25
|
+
.desk-top .brand .mark {
|
|
26
|
+
width: 30px; height: 30px; display: grid; place-items: center;
|
|
27
|
+
background: var(--color-accent); color: var(--color-accent-ink, #fff);
|
|
28
|
+
border-radius: 9px; font-size: 15px;
|
|
29
|
+
}
|
|
30
|
+
.desk-top nav { display: flex; gap: 4px; }
|
|
31
|
+
.desk-top nav button {
|
|
32
|
+
border: 0; background: none;
|
|
33
|
+
padding: 8px 14px; border-radius: 9px;
|
|
34
|
+
font: inherit; font-size: 13px; font-weight: 650;
|
|
35
|
+
color: var(--color-ink-2); cursor: pointer;
|
|
36
|
+
}
|
|
37
|
+
.desk-top nav button.on { background: var(--color-bg); color: var(--color-ink); }
|
|
38
|
+
.desk-top .meta { font-size: 12.5px; color: var(--color-ink-2); }
|
|
39
|
+
|
|
40
|
+
.desk-body { flex: 1; padding: 24px 26px; max-width: 1180px; width: 100%; margin: 0 auto; }
|
|
41
|
+
|
|
42
|
+
.kpis { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-bottom: 18px; }
|
|
43
|
+
.kpi {
|
|
44
|
+
background: var(--color-card);
|
|
45
|
+
border: 1px solid var(--color-line);
|
|
46
|
+
border-radius: var(--radius-card);
|
|
47
|
+
padding: 14px 16px;
|
|
48
|
+
}
|
|
49
|
+
.kpi .v { font-size: 26px; font-weight: 800; font-variant-numeric: tabular-nums; }
|
|
50
|
+
.kpi .l { font-size: 12px; color: var(--color-ink-2); margin-top: 2px; }
|
|
51
|
+
|
|
52
|
+
.panel {
|
|
53
|
+
background: var(--color-card);
|
|
54
|
+
border: 1px solid var(--color-line);
|
|
55
|
+
border-radius: var(--radius-card);
|
|
56
|
+
overflow: hidden;
|
|
57
|
+
}
|
|
58
|
+
.panel .panel-head {
|
|
59
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
60
|
+
padding: 14px 18px;
|
|
61
|
+
border-bottom: 1px solid var(--color-line);
|
|
62
|
+
font-weight: 800; font-size: 14px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
|
|
66
|
+
th {
|
|
67
|
+
text-align: start; padding: 10px 18px;
|
|
68
|
+
font-size: 11px; text-transform: uppercase; letter-spacing: .7px;
|
|
69
|
+
color: var(--color-ink-2);
|
|
70
|
+
border-bottom: 1px solid var(--color-line);
|
|
71
|
+
}
|
|
72
|
+
td { padding: 12px 18px; border-bottom: 1px solid var(--color-line); }
|
|
73
|
+
tr:last-child td { border-bottom: 0; }
|
|
74
|
+
td.num { font-variant-numeric: tabular-nums; font-weight: 650; }
|
|
75
|
+
|
|
76
|
+
.st {
|
|
77
|
+
display: inline-flex; padding: 4px 10px;
|
|
78
|
+
border-radius: 999px;
|
|
79
|
+
font-size: 11px; font-weight: 750; text-transform: uppercase; letter-spacing: .4px;
|
|
80
|
+
}
|
|
81
|
+
.st.queued { background: color-mix(in srgb, var(--color-ink-2) 14%, transparent); color: var(--color-ink-2); }
|
|
82
|
+
.st.brewing { background: color-mix(in srgb, var(--color-warn) 16%, transparent); color: var(--color-warn); }
|
|
83
|
+
.st.ready { background: color-mix(in srgb, var(--color-good) 15%, transparent); color: var(--color-good); }
|
|
84
|
+
.st.done { background: color-mix(in srgb, var(--color-ink-2) 8%, transparent); color: var(--color-ink-2); }
|
|
85
|
+
|
|
86
|
+
.btn-sm {
|
|
87
|
+
border: 1px solid var(--color-line);
|
|
88
|
+
background: var(--color-card);
|
|
89
|
+
color: var(--color-ink);
|
|
90
|
+
border-radius: 9px;
|
|
91
|
+
padding: 7px 12px;
|
|
92
|
+
font: inherit; font-size: 12.5px; font-weight: 650;
|
|
93
|
+
cursor: pointer;
|
|
94
|
+
}
|
|
95
|
+
.btn-sm.primary { background: var(--color-accent); border-color: var(--color-accent); color: var(--color-accent-ink, #fff); }
|
|
96
|
+
|
|
97
|
+
.switch { position: relative; width: 40px; height: 24px; cursor: pointer; }
|
|
98
|
+
.switch input { display: none; }
|
|
99
|
+
.switch i {
|
|
100
|
+
position: absolute; inset: 0;
|
|
101
|
+
background: var(--color-line);
|
|
102
|
+
border-radius: 999px;
|
|
103
|
+
transition: background .15s ease;
|
|
104
|
+
}
|
|
105
|
+
.switch i::after {
|
|
106
|
+
content: ''; position: absolute;
|
|
107
|
+
top: 3px; inset-inline-start: 3px;
|
|
108
|
+
width: 18px; height: 18px;
|
|
109
|
+
border-radius: 50%; background: #fff;
|
|
110
|
+
box-shadow: 0 1px 4px rgba(0,0,0,.25);
|
|
111
|
+
transition: transform .15s ease;
|
|
112
|
+
}
|
|
113
|
+
.switch input:checked + i { background: var(--color-good); }
|
|
114
|
+
.switch input:checked + i::after { transform: translateX(16px); }
|
|
115
|
+
[dir="rtl"] .switch input:checked + i::after { transform: translateX(-16px); }
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "qahwa",
|
|
3
|
+
"title": "Qahwa Roast",
|
|
4
|
+
"modes": {
|
|
5
|
+
"light": {
|
|
6
|
+
"color": {
|
|
7
|
+
"accent": "#A96A3C",
|
|
8
|
+
"accent-ink": "#FFFFFF",
|
|
9
|
+
"bg": "#F6F1EA",
|
|
10
|
+
"card": "#FFFFFF",
|
|
11
|
+
"ink": "#2A2119",
|
|
12
|
+
"ink-2": "#7A6C5D",
|
|
13
|
+
"line": "#EADFD0",
|
|
14
|
+
"good": "#3E7C4F",
|
|
15
|
+
"warn": "#B07A1E"
|
|
16
|
+
},
|
|
17
|
+
"font": {
|
|
18
|
+
"sans": "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
|
19
|
+
"serif": "ui-serif, 'New York', Georgia, serif"
|
|
20
|
+
},
|
|
21
|
+
"radius": {
|
|
22
|
+
"card": "18px",
|
|
23
|
+
"chip": "999px"
|
|
24
|
+
},
|
|
25
|
+
"space": {
|
|
26
|
+
"page": "16px"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"dark": {
|
|
30
|
+
"color": {
|
|
31
|
+
"accent": "#C98A52",
|
|
32
|
+
"accent-ink": "#1C130B",
|
|
33
|
+
"bg": "#14100C",
|
|
34
|
+
"card": "#201914",
|
|
35
|
+
"ink": "#F3EAE0",
|
|
36
|
+
"ink-2": "#B5A695",
|
|
37
|
+
"line": "#33291F",
|
|
38
|
+
"good": "#6CB07E",
|
|
39
|
+
"warn": "#D8A24A"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Notes</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--accent: #5B5BD6; --bg: #F4F3F0; --card: #FFFFFF;
|
|
10
|
+
--ink: #211E1A; --ink-2: #6F6A61; --line: #E3DFD7;
|
|
11
|
+
}
|
|
12
|
+
body.dark {
|
|
13
|
+
--accent: #8484F0; --bg: #161519; --card: #211F26;
|
|
14
|
+
--ink: #EDEBF3; --ink-2: #A7A3B0; --line: #2E2D34;
|
|
15
|
+
}
|
|
16
|
+
* { box-sizing: border-box; }
|
|
17
|
+
body {
|
|
18
|
+
margin: 0; min-height: 100vh;
|
|
19
|
+
background: var(--bg); color: var(--ink);
|
|
20
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
21
|
+
padding: 70px 16px 50px;
|
|
22
|
+
transition: background .2s ease;
|
|
23
|
+
}
|
|
24
|
+
.view { display: none; }
|
|
25
|
+
.view.on { display: block; }
|
|
26
|
+
h1 { font-size: 26px; margin: 0 0 14px; letter-spacing: -0.3px; }
|
|
27
|
+
.card {
|
|
28
|
+
background: var(--card); border: 1px solid var(--line);
|
|
29
|
+
border-radius: 16px; padding: 14px 16px; margin-bottom: 10px; cursor: pointer;
|
|
30
|
+
}
|
|
31
|
+
.card b { display: block; font-size: 15px; }
|
|
32
|
+
.card span { font-size: 12.5px; color: var(--ink-2); }
|
|
33
|
+
.btn {
|
|
34
|
+
display: inline-block; border: 0; padding: 12px 18px;
|
|
35
|
+
border-radius: 999px; background: var(--accent); color: #fff;
|
|
36
|
+
font: inherit; font-size: 14px; font-weight: 700; cursor: pointer;
|
|
37
|
+
}
|
|
38
|
+
.muted { color: var(--ink-2); font-size: 13px; line-height: 1.6; }
|
|
39
|
+
</style>
|
|
40
|
+
</head>
|
|
41
|
+
<body>
|
|
42
|
+
<div class="view" data-view="inbox">
|
|
43
|
+
<h1>Notes</h1>
|
|
44
|
+
<div class="card" onclick="go('note')"><b>Ship ScreenBook app mode</b><span>postMessage adapter · today</span></div>
|
|
45
|
+
<div class="card" onclick="go('note')"><b>Qahwa tasting order</b><span>V60 before the flat white · yesterday</span></div>
|
|
46
|
+
<div class="card" onclick="go('note')"><b>Trip packing list</b><span>Passport, chargers, dates · last week</span></div>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div class="view" data-view="note">
|
|
50
|
+
<h1>Ship ScreenBook app mode</h1>
|
|
51
|
+
<p class="muted">This whole app is ONE page with its own tiny router — yet the ScreenBook
|
|
52
|
+
sidebar lists its views as screens, deep links work, and dark mode follows the shell
|
|
53
|
+
toggle. It registered itself with <b>AppBridge.register()</b> — no driver needed.</p>
|
|
54
|
+
<button class="btn" onclick="go('inbox')">← Back to notes</button>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div class="view" data-view="about">
|
|
58
|
+
<h1>About</h1>
|
|
59
|
+
<p class="muted">A cooperative single-page app: it includes
|
|
60
|
+
<b>engine/app-bridge.js</b> and describes itself. Sealed apps use an injected
|
|
61
|
+
driver instead — same protocol, zero edits to the app.</p>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<!-- the ONE line that makes this a cooperative ScreenBook app -->
|
|
65
|
+
<script src="../engine/app-bridge.js"></script>
|
|
66
|
+
<script>
|
|
67
|
+
var VIEWS = [
|
|
68
|
+
{ id: 'inbox', title: 'Inbox', description: 'The notes list — tap one to open it.' },
|
|
69
|
+
{ id: 'note', title: 'Note detail', description: 'A single note, opened from the inbox.' },
|
|
70
|
+
{ id: 'about', title: 'About', description: 'What a cooperative SPA is.' }
|
|
71
|
+
];
|
|
72
|
+
var current = 'inbox';
|
|
73
|
+
|
|
74
|
+
function go(id) {
|
|
75
|
+
if (!document.querySelector('.view[data-view="' + id + '"]')) return;
|
|
76
|
+
current = id;
|
|
77
|
+
document.querySelectorAll('.view').forEach(function (v) {
|
|
78
|
+
v.classList.toggle('on', v.dataset.view === id);
|
|
79
|
+
});
|
|
80
|
+
AppBridge.navigated(id);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
AppBridge.register({
|
|
84
|
+
title: 'Notes',
|
|
85
|
+
screens: function () { return VIEWS; },
|
|
86
|
+
go: go,
|
|
87
|
+
current: function () { return current; },
|
|
88
|
+
set: function (env) {
|
|
89
|
+
if (env.mode) document.body.classList.toggle('dark', env.mode === 'dark');
|
|
90
|
+
if (env.dir) document.documentElement.setAttribute('dir', env.dir);
|
|
91
|
+
/* cooperative apps may adopt the shell theme's tokens if they want: */
|
|
92
|
+
if (env.vars && env.vars['--color-accent']) {
|
|
93
|
+
document.documentElement.style.setProperty('--accent', env.vars['--color-accent']);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
go('inbox');
|
|
98
|
+
</script>
|
|
99
|
+
</body>
|
|
100
|
+
</html>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "notes-app",
|
|
3
|
+
"title": "Notes (cooperative SPA)",
|
|
4
|
+
"icon": "🗒",
|
|
5
|
+
"description": "A one-page app that includes engine/app-bridge.js itself and registers its own screens — no driver needed.",
|
|
6
|
+
"order": 1,
|
|
7
|
+
"type": "app",
|
|
8
|
+
"src": "app/index.html",
|
|
9
|
+
"device": "ios",
|
|
10
|
+
"chrome": "engine"
|
|
11
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "default",
|
|
3
|
+
"title": "Demo",
|
|
4
|
+
"modes": {
|
|
5
|
+
"light": {
|
|
6
|
+
"color": { "accent": "#5B5BD6", "bg": "#F4F3F0", "card": "#FFFFFF", "ink": "#211E1A", "ink-2": "#6F6A61", "line": "#E3DFD7" },
|
|
7
|
+
"font": { "sans": "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" },
|
|
8
|
+
"radius": { "card": "16px", "chip": "999px" },
|
|
9
|
+
"space": { "page": "16px" }
|
|
10
|
+
},
|
|
11
|
+
"dark": {
|
|
12
|
+
"color": { "accent": "#8484F0", "bg": "#161519", "card": "#211F26", "ink": "#EDEBF3", "ink-2": "#A7A3B0", "line": "#2E2D34" }
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"list": [
|
|
3
|
+
{
|
|
4
|
+
"id": "theme-tour",
|
|
5
|
+
"title": "Theme tour",
|
|
6
|
+
"icon": "🎨",
|
|
7
|
+
"description": "Swatches → typography; flip Theme ▾ and dark/light at each step.",
|
|
8
|
+
"steps": [
|
|
9
|
+
{ "screen": "010-swatches", "note": "Every CSS var the active theme defines, read live from :root. Switch themes — no reload, no CSS edits." },
|
|
10
|
+
{ "screen": "020-typography", "note": "The same tokens driving real components — type scale, buttons, cards, chips." }
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"feature": "lab",
|
|
3
|
+
"generated": "2026-07-18",
|
|
4
|
+
"screens": [
|
|
5
|
+
{
|
|
6
|
+
"id": "010-swatches",
|
|
7
|
+
"file": "screens/010-swatches.html",
|
|
8
|
+
"title": "Swatches",
|
|
9
|
+
"description": "Every color/radius/space/font token of the active theme, read live from :root.",
|
|
10
|
+
"order": 10,
|
|
11
|
+
"device": "none",
|
|
12
|
+
"status": "ready",
|
|
13
|
+
"tags": [
|
|
14
|
+
"tokens"
|
|
15
|
+
],
|
|
16
|
+
"updated": "2026-07-18"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "020-typography",
|
|
20
|
+
"file": "screens/020-typography.html",
|
|
21
|
+
"title": "Type & components",
|
|
22
|
+
"description": "The same tokens driving real UI — type scale, buttons, cards, chips, inputs.",
|
|
23
|
+
"order": 20,
|
|
24
|
+
"device": "web",
|
|
25
|
+
"status": "ready",
|
|
26
|
+
"tags": [
|
|
27
|
+
"tokens",
|
|
28
|
+
"gallery"
|
|
29
|
+
],
|
|
30
|
+
"updated": "2026-07-18"
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Token swatches</title>
|
|
7
|
+
<script type="application/json" id="screen-meta">
|
|
8
|
+
{
|
|
9
|
+
"title": "Swatches",
|
|
10
|
+
"description": "Every color/radius/space/font token of the active theme, read live from :root.",
|
|
11
|
+
"order": 10,
|
|
12
|
+
"device": "none",
|
|
13
|
+
"status": "ready",
|
|
14
|
+
"tags": ["tokens"],
|
|
15
|
+
"updated": "2026-07-18"
|
|
16
|
+
}
|
|
17
|
+
</script>
|
|
18
|
+
<script src="../../../engine/bridge.js"></script>
|
|
19
|
+
<style>
|
|
20
|
+
* { box-sizing: border-box; }
|
|
21
|
+
body {
|
|
22
|
+
margin: 0; min-height: 100vh;
|
|
23
|
+
background: var(--color-bg); color: var(--color-ink);
|
|
24
|
+
font-family: var(--font-sans);
|
|
25
|
+
padding: var(--space-page, 20px);
|
|
26
|
+
-webkit-font-smoothing: antialiased;
|
|
27
|
+
}
|
|
28
|
+
h1 { font-family: var(--font-serif); font-size: 26px; margin: 4px 0 2px; }
|
|
29
|
+
.sub { color: var(--color-ink-2); font-size: 13px; margin-bottom: 18px; }
|
|
30
|
+
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }
|
|
31
|
+
.swatch {
|
|
32
|
+
background: var(--color-card);
|
|
33
|
+
border: 1px solid var(--color-line);
|
|
34
|
+
border-radius: var(--radius-card);
|
|
35
|
+
overflow: hidden;
|
|
36
|
+
}
|
|
37
|
+
.swatch .chipbox { height: 64px; border-bottom: 1px solid var(--color-line); }
|
|
38
|
+
.swatch .label { padding: 9px 11px; }
|
|
39
|
+
.swatch .name { font-size: 12px; font-weight: 700; font-family: ui-monospace, monospace; }
|
|
40
|
+
.swatch .value { font-size: 11px; color: var(--color-ink-2); font-family: ui-monospace, monospace; margin-top: 2px; word-break: break-all; }
|
|
41
|
+
.section { margin: 20px 0 8px; font-size: 11px; font-weight: 800; letter-spacing: .8px; text-transform: uppercase; color: var(--color-ink-2); }
|
|
42
|
+
.meta-line { font-size: 12px; color: var(--color-ink-2); font-family: ui-monospace, monospace; }
|
|
43
|
+
</style>
|
|
44
|
+
</head>
|
|
45
|
+
<body>
|
|
46
|
+
<h1>Token swatches</h1>
|
|
47
|
+
<div class="sub">Read live from <span class="meta-line">:root</span> — flip Theme ▾, dark/light or RTL above and watch this screen repaint. <span class="meta-line" id="env"></span></div>
|
|
48
|
+
<div id="content"></div>
|
|
49
|
+
|
|
50
|
+
<script>
|
|
51
|
+
var COLOR_VARS = ['--color-accent', '--color-bg', '--color-card', '--color-ink', '--color-ink-2', '--color-line'];
|
|
52
|
+
var OTHER_VARS = ['--radius-card', '--radius-chip', '--space-page', '--font-sans', '--font-serif'];
|
|
53
|
+
|
|
54
|
+
function readVar(name) {
|
|
55
|
+
return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function render() {
|
|
59
|
+
var box = document.getElementById('content');
|
|
60
|
+
box.innerHTML = '';
|
|
61
|
+
|
|
62
|
+
var sec1 = document.createElement('div');
|
|
63
|
+
sec1.className = 'section';
|
|
64
|
+
sec1.textContent = 'Color';
|
|
65
|
+
box.appendChild(sec1);
|
|
66
|
+
var grid = document.createElement('div');
|
|
67
|
+
grid.className = 'grid';
|
|
68
|
+
COLOR_VARS.forEach(function (name) {
|
|
69
|
+
var v = readVar(name);
|
|
70
|
+
var el = document.createElement('div');
|
|
71
|
+
el.className = 'swatch';
|
|
72
|
+
el.innerHTML = '<div class="chipbox" style="background:' + v + '"></div>' +
|
|
73
|
+
'<div class="label"><div class="name">' + name + '</div><div class="value">' + (v || '—') + '</div></div>';
|
|
74
|
+
grid.appendChild(el);
|
|
75
|
+
});
|
|
76
|
+
box.appendChild(grid);
|
|
77
|
+
|
|
78
|
+
var sec2 = document.createElement('div');
|
|
79
|
+
sec2.className = 'section';
|
|
80
|
+
sec2.textContent = 'Radius · Space · Type';
|
|
81
|
+
box.appendChild(sec2);
|
|
82
|
+
var grid2 = document.createElement('div');
|
|
83
|
+
grid2.className = 'grid';
|
|
84
|
+
OTHER_VARS.forEach(function (name) {
|
|
85
|
+
var v = readVar(name);
|
|
86
|
+
var preview = name.indexOf('--radius') === 0
|
|
87
|
+
? '<div style="height:100%;display:grid;place-items:center"><div style="width:70%;height:38px;background:var(--color-accent);border-radius:' + v + '"></div></div>'
|
|
88
|
+
: name.indexOf('--space') === 0
|
|
89
|
+
? '<div style="height:100%;display:grid;place-items:center"><div style="width:' + v + ';height:38px;background:var(--color-accent);opacity:.8"></div></div>'
|
|
90
|
+
: '<div style="height:100%;display:grid;place-items:center;font-family:' + v + ';font-size:22px">Aa</div>';
|
|
91
|
+
var el = document.createElement('div');
|
|
92
|
+
el.className = 'swatch';
|
|
93
|
+
el.innerHTML = '<div class="chipbox">' + preview + '</div>' +
|
|
94
|
+
'<div class="label"><div class="name">' + name + '</div><div class="value">' + (v || '—') + '</div></div>';
|
|
95
|
+
grid2.appendChild(el);
|
|
96
|
+
});
|
|
97
|
+
box.appendChild(grid2);
|
|
98
|
+
|
|
99
|
+
document.getElementById('env').textContent =
|
|
100
|
+
'· theme=' + Engine.env.theme + ' · mode=' + document.documentElement.getAttribute('data-mode');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
Engine.ready(function () {
|
|
104
|
+
render();
|
|
105
|
+
/* live theme/mode switches change :root attributes — re-read the values */
|
|
106
|
+
new MutationObserver(render).observe(document.documentElement, {
|
|
107
|
+
attributes: true, attributeFilter: ['data-mode', 'style', 'dir']
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
</script>
|
|
111
|
+
</body>
|
|
112
|
+
</html>
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Type & components</title>
|
|
7
|
+
<script type="application/json" id="screen-meta">
|
|
8
|
+
{
|
|
9
|
+
"title": "Type & components",
|
|
10
|
+
"description": "The same tokens driving real UI — type scale, buttons, cards, chips, inputs.",
|
|
11
|
+
"order": 20,
|
|
12
|
+
"device": "web",
|
|
13
|
+
"status": "ready",
|
|
14
|
+
"tags": ["tokens", "gallery"],
|
|
15
|
+
"updated": "2026-07-18"
|
|
16
|
+
}
|
|
17
|
+
</script>
|
|
18
|
+
<script src="../../../engine/bridge.js"></script>
|
|
19
|
+
<style>
|
|
20
|
+
* { box-sizing: border-box; }
|
|
21
|
+
body {
|
|
22
|
+
margin: 0; min-height: 100vh;
|
|
23
|
+
background: var(--color-bg); color: var(--color-ink);
|
|
24
|
+
font-family: var(--font-sans);
|
|
25
|
+
padding: calc(var(--space-page, 20px) * 2);
|
|
26
|
+
-webkit-font-smoothing: antialiased;
|
|
27
|
+
}
|
|
28
|
+
.cols { display: grid; grid-template-columns: 1.2fr 1fr; gap: 28px; max-width: 1080px; margin: 0 auto; }
|
|
29
|
+
.display { font-family: var(--font-serif); font-size: 44px; line-height: 1.1; letter-spacing: -0.6px; margin: 0; }
|
|
30
|
+
.lede { font-size: 16px; line-height: 1.6; color: var(--color-ink-2); margin: 14px 0 0; }
|
|
31
|
+
.scale { margin-top: 26px; border-top: 1px solid var(--color-line); padding-top: 18px; }
|
|
32
|
+
.scale div { margin-bottom: 10px; }
|
|
33
|
+
.card {
|
|
34
|
+
background: var(--color-card);
|
|
35
|
+
border: 1px solid var(--color-line);
|
|
36
|
+
border-radius: var(--radius-card);
|
|
37
|
+
padding: 20px;
|
|
38
|
+
margin-bottom: 16px;
|
|
39
|
+
}
|
|
40
|
+
.btn {
|
|
41
|
+
border: 0; padding: 12px 20px;
|
|
42
|
+
border-radius: var(--radius-chip);
|
|
43
|
+
background: var(--color-accent); color: #fff;
|
|
44
|
+
font: inherit; font-size: 14px; font-weight: 700;
|
|
45
|
+
cursor: pointer;
|
|
46
|
+
}
|
|
47
|
+
.btn.ghost {
|
|
48
|
+
background: transparent;
|
|
49
|
+
color: var(--color-ink);
|
|
50
|
+
border: 1px solid var(--color-line);
|
|
51
|
+
}
|
|
52
|
+
.chip {
|
|
53
|
+
display: inline-flex; padding: 6px 12px;
|
|
54
|
+
border-radius: var(--radius-chip);
|
|
55
|
+
background: color-mix(in srgb, var(--color-accent) 12%, transparent);
|
|
56
|
+
color: var(--color-accent);
|
|
57
|
+
font-size: 12px; font-weight: 700;
|
|
58
|
+
margin-inline-end: 6px;
|
|
59
|
+
}
|
|
60
|
+
input[type="text"] {
|
|
61
|
+
width: 100%; padding: 11px 12px;
|
|
62
|
+
border: 1px solid var(--color-line);
|
|
63
|
+
border-radius: calc(var(--radius-card) / 1.6);
|
|
64
|
+
background: var(--color-bg); color: var(--color-ink);
|
|
65
|
+
font: inherit; font-size: 14px;
|
|
66
|
+
outline: none;
|
|
67
|
+
}
|
|
68
|
+
input[type="text"]:focus { border-color: var(--color-accent); }
|
|
69
|
+
.label { font-size: 11px; font-weight: 800; letter-spacing: .8px; text-transform: uppercase; color: var(--color-ink-2); margin-bottom: 10px; }
|
|
70
|
+
</style>
|
|
71
|
+
</head>
|
|
72
|
+
<body>
|
|
73
|
+
<div class="cols">
|
|
74
|
+
<div>
|
|
75
|
+
<h1 class="display">Design decides once,<br>tokens repeat it everywhere.</h1>
|
|
76
|
+
<p class="lede">This page has zero hard-coded colors. Switch the theme or mode in the shell — serif voice, palette, radii and spacing all come from the active <b>themes/*.json</b>.</p>
|
|
77
|
+
<div class="scale">
|
|
78
|
+
<div class="label">Type scale</div>
|
|
79
|
+
<div style="font-family:var(--font-serif);font-size:32px">Serif display 32</div>
|
|
80
|
+
<div style="font-size:22px;font-weight:700">Sans headline 22</div>
|
|
81
|
+
<div style="font-size:16px">Body 16 — the quick brown qahwa jumps over the lazy latte.</div>
|
|
82
|
+
<div style="font-size:13px;color:var(--color-ink-2)">Secondary 13 — muted ink-2 for metadata.</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
<div>
|
|
86
|
+
<div class="card">
|
|
87
|
+
<div class="label">Buttons & chips</div>
|
|
88
|
+
<button class="btn" id="cta">Primary action</button>
|
|
89
|
+
<button class="btn ghost" style="margin-inline-start:8px">Secondary</button>
|
|
90
|
+
<div style="margin-top:14px">
|
|
91
|
+
<span class="chip">accent chip</span><span class="chip">radius-chip</span>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
<div class="card">
|
|
95
|
+
<div class="label">Form</div>
|
|
96
|
+
<input type="text" placeholder="Focus me — the ring is --color-accent">
|
|
97
|
+
</div>
|
|
98
|
+
<div class="card">
|
|
99
|
+
<div class="label">Nested card</div>
|
|
100
|
+
<div class="card" style="margin:0">
|
|
101
|
+
<b>--radius-card</b> at every level,<br>
|
|
102
|
+
<span style="color:var(--color-ink-2)">--color-line for every border.</span>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
<script>
|
|
108
|
+
Engine.ready(function () {
|
|
109
|
+
document.getElementById('cta').addEventListener('click', function () {
|
|
110
|
+
Engine.toast('Styled by ' + Engine.env.theme + ' · ' + document.documentElement.getAttribute('data-mode'));
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
</script>
|
|
114
|
+
</body>
|
|
115
|
+
</html>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "noir",
|
|
3
|
+
"title": "Noir",
|
|
4
|
+
"modes": {
|
|
5
|
+
"light": {
|
|
6
|
+
"color": {
|
|
7
|
+
"accent": "#C33149",
|
|
8
|
+
"bg": "#EFEBE4",
|
|
9
|
+
"card": "#FBF9F5",
|
|
10
|
+
"ink": "#211D1D",
|
|
11
|
+
"ink-2": "#7C7370",
|
|
12
|
+
"line": "#DDD5CA"
|
|
13
|
+
},
|
|
14
|
+
"font": {
|
|
15
|
+
"sans": "Avenir Next, -apple-system, 'Segoe UI', sans-serif",
|
|
16
|
+
"serif": "'Iowan Old Style', ui-serif, Georgia, serif"
|
|
17
|
+
},
|
|
18
|
+
"radius": { "card": "4px", "chip": "6px" },
|
|
19
|
+
"space": { "page": "24px" }
|
|
20
|
+
},
|
|
21
|
+
"dark": {
|
|
22
|
+
"color": {
|
|
23
|
+
"accent": "#E4556D",
|
|
24
|
+
"bg": "#0F0D0E",
|
|
25
|
+
"card": "#191617",
|
|
26
|
+
"ink": "#F2EDEA",
|
|
27
|
+
"ink-2": "#9E9491",
|
|
28
|
+
"line": "#2B2627"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|