@tomorrowos/sdk 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/BUILD_GUARDRAILS.md +348 -0
- package/LLM_PROMPT.md +171 -0
- package/PLAYER_INSTALL.md +211 -0
- package/README.md +98 -0
- package/brand.example.json +34 -0
- package/brand.schema.json +154 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +75 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/store/memory-store.d.ts +15 -0
- package/dist/store/memory-store.d.ts.map +1 -0
- package/dist/store/memory-store.js +23 -0
- package/dist/store/types.d.ts +24 -0
- package/dist/store/types.d.ts.map +1 -0
- package/dist/store/types.js +5 -0
- package/dist/tomorrowos.d.ts +62 -0
- package/dist/tomorrowos.d.ts.map +1 -0
- package/dist/tomorrowos.js +371 -0
- package/package.json +43 -0
- package/templates/cms-starter/assets/logo.svg +4 -0
- package/templates/cms-starter/brand.json +24 -0
- package/templates/cms-starter/package.json +20 -0
- package/templates/cms-starter/server.ts +47 -0
- package/templates/cms-starter/tsconfig.json +12 -0
- package/templates/style-tokens/components.md +282 -0
- package/templates/style-tokens/tokens.css +109 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# Component patterns for TomorrowOS CMSs
|
|
2
|
+
|
|
3
|
+
> This is a visual seed, not a component library. It shows the LLM generating a CMS what "good" looks like so the output doesn't default to a 1990s admin panel. Reference these patterns when building UI; don't copy them literally.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Design principles
|
|
8
|
+
|
|
9
|
+
**Restrained.** The CMS is a professional tool, not a consumer app. No gradients, no shadows-on-everything, no animations for their own sake. Information density matters more than visual polish.
|
|
10
|
+
|
|
11
|
+
**Brand-led.** The primary colour from `brand.json` anchors the interface. Everything else is near-neutral. This lets the operator's brand show through instead of competing with it.
|
|
12
|
+
|
|
13
|
+
**Status-first.** An operator glances at a dashboard and needs to know: what's online, what's offline, what needs attention. Colour is used for status (green online, red offline, amber warning), never decoration.
|
|
14
|
+
|
|
15
|
+
**Monospace for technical data.** Device IDs, URLs, IP addresses, timestamps — all monospace. Operators scan these; they should line up visually.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Cards
|
|
20
|
+
|
|
21
|
+
Use for: device rows, content items, recent activity items.
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<article class="card">
|
|
25
|
+
<header class="card-header">
|
|
26
|
+
<span class="status-dot status-online"></span>
|
|
27
|
+
<h3 class="card-title">Melbourne Store — Menu 01</h3>
|
|
28
|
+
</header>
|
|
29
|
+
<dl class="card-meta">
|
|
30
|
+
<dt>Platform</dt><dd>Samsung Tizen 7.0</dd>
|
|
31
|
+
<dt>Last seen</dt><dd>just now</dd>
|
|
32
|
+
<dt>Now playing</dt><dd>Summer menu v3</dd>
|
|
33
|
+
</dl>
|
|
34
|
+
</article>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```css
|
|
38
|
+
.card {
|
|
39
|
+
background: white;
|
|
40
|
+
border: 1px solid var(--color-divider);
|
|
41
|
+
border-radius: var(--radius-md);
|
|
42
|
+
padding: var(--space-4);
|
|
43
|
+
transition: border-color var(--transition-fast);
|
|
44
|
+
}
|
|
45
|
+
.card:hover {
|
|
46
|
+
border-color: var(--color-primary);
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Avoid: drop shadows on cards by default, thick borders, rounded corners larger than `--radius-md`.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Buttons
|
|
55
|
+
|
|
56
|
+
Three variants: primary (brand action), default (neutral action), destructive (dangerous action with confirmation).
|
|
57
|
+
|
|
58
|
+
```html
|
|
59
|
+
<!-- Primary: the main action on a page. One per page max. -->
|
|
60
|
+
<button class="btn btn-primary">Pair screen</button>
|
|
61
|
+
|
|
62
|
+
<!-- Default: most actions -->
|
|
63
|
+
<button class="btn">Clear content</button>
|
|
64
|
+
|
|
65
|
+
<!-- Destructive: deletion, revocation -->
|
|
66
|
+
<button class="btn btn-destructive">Unpair device</button>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```css
|
|
70
|
+
.btn {
|
|
71
|
+
font-family: var(--font-sans);
|
|
72
|
+
font-size: var(--text-sm);
|
|
73
|
+
font-weight: var(--weight-medium);
|
|
74
|
+
padding: var(--space-2) var(--space-4);
|
|
75
|
+
border-radius: var(--radius-md);
|
|
76
|
+
border: 1px solid var(--color-divider);
|
|
77
|
+
background: white;
|
|
78
|
+
color: var(--color-text);
|
|
79
|
+
cursor: pointer;
|
|
80
|
+
transition: all var(--transition-fast);
|
|
81
|
+
}
|
|
82
|
+
.btn:hover { border-color: var(--color-text); }
|
|
83
|
+
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
84
|
+
|
|
85
|
+
.btn-primary {
|
|
86
|
+
background: var(--color-primary);
|
|
87
|
+
border-color: var(--color-primary);
|
|
88
|
+
color: white;
|
|
89
|
+
}
|
|
90
|
+
.btn-primary:hover { filter: brightness(0.92); }
|
|
91
|
+
|
|
92
|
+
.btn-destructive {
|
|
93
|
+
color: var(--color-error);
|
|
94
|
+
border-color: var(--color-error);
|
|
95
|
+
}
|
|
96
|
+
.btn-destructive:hover { background: var(--color-error-bg); }
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Avoid: buttons that don't indicate hover or disabled state, text-only buttons (use a link instead), multiple primary buttons on one page.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Status indicators
|
|
104
|
+
|
|
105
|
+
Small dots or pills indicating device / job / alert state.
|
|
106
|
+
|
|
107
|
+
```html
|
|
108
|
+
<span class="status-dot status-online"></span>
|
|
109
|
+
<span class="status-pill status-pill-warning">3 offline</span>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
```css
|
|
113
|
+
.status-dot {
|
|
114
|
+
display: inline-block;
|
|
115
|
+
width: 8px;
|
|
116
|
+
height: 8px;
|
|
117
|
+
border-radius: 50%;
|
|
118
|
+
margin-right: var(--space-2);
|
|
119
|
+
}
|
|
120
|
+
.status-online { background: var(--color-success); }
|
|
121
|
+
.status-offline { background: var(--color-error); }
|
|
122
|
+
.status-warning { background: var(--color-warning); }
|
|
123
|
+
.status-unknown { background: var(--color-faint); }
|
|
124
|
+
|
|
125
|
+
.status-pill {
|
|
126
|
+
display: inline-flex;
|
|
127
|
+
align-items: center;
|
|
128
|
+
padding: 2px var(--space-2);
|
|
129
|
+
font-size: var(--text-xs);
|
|
130
|
+
font-weight: var(--weight-medium);
|
|
131
|
+
border-radius: var(--radius-pill);
|
|
132
|
+
}
|
|
133
|
+
.status-pill-success { background: var(--color-success-bg); color: var(--color-success); }
|
|
134
|
+
.status-pill-warning { background: var(--color-warning-bg); color: var(--color-warning); }
|
|
135
|
+
.status-pill-error { background: var(--color-error-bg); color: var(--color-error); }
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Tables
|
|
141
|
+
|
|
142
|
+
For lists where many rows need comparing column-by-column.
|
|
143
|
+
|
|
144
|
+
```css
|
|
145
|
+
table.data-table {
|
|
146
|
+
width: 100%;
|
|
147
|
+
border-collapse: collapse;
|
|
148
|
+
font-size: var(--text-sm);
|
|
149
|
+
}
|
|
150
|
+
.data-table th {
|
|
151
|
+
text-align: left;
|
|
152
|
+
font-weight: var(--weight-medium);
|
|
153
|
+
color: var(--color-dim);
|
|
154
|
+
text-transform: uppercase;
|
|
155
|
+
letter-spacing: 0.04em;
|
|
156
|
+
font-size: var(--text-xs);
|
|
157
|
+
padding: var(--space-2) var(--space-3);
|
|
158
|
+
border-bottom: 1px solid var(--color-divider);
|
|
159
|
+
}
|
|
160
|
+
.data-table td {
|
|
161
|
+
padding: var(--space-3);
|
|
162
|
+
border-bottom: 1px solid var(--color-divider);
|
|
163
|
+
}
|
|
164
|
+
.data-table tr:hover td { background: var(--color-mist); }
|
|
165
|
+
.data-table td.mono { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-dim); }
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Use monospace + dim colour for IDs, timestamps, URLs, hashes.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Forms
|
|
173
|
+
|
|
174
|
+
```css
|
|
175
|
+
.form-field {
|
|
176
|
+
display: flex;
|
|
177
|
+
flex-direction: column;
|
|
178
|
+
gap: var(--space-1);
|
|
179
|
+
margin-bottom: var(--space-4);
|
|
180
|
+
}
|
|
181
|
+
.form-label {
|
|
182
|
+
font-size: var(--text-sm);
|
|
183
|
+
font-weight: var(--weight-medium);
|
|
184
|
+
color: var(--color-text);
|
|
185
|
+
}
|
|
186
|
+
.form-input {
|
|
187
|
+
font-family: var(--font-sans);
|
|
188
|
+
font-size: var(--text-base);
|
|
189
|
+
padding: var(--space-2) var(--space-3);
|
|
190
|
+
border: 1px solid var(--color-divider);
|
|
191
|
+
border-radius: var(--radius-md);
|
|
192
|
+
background: white;
|
|
193
|
+
transition: border-color var(--transition-fast);
|
|
194
|
+
}
|
|
195
|
+
.form-input:focus {
|
|
196
|
+
outline: none;
|
|
197
|
+
border-color: var(--color-primary);
|
|
198
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 20%, transparent);
|
|
199
|
+
}
|
|
200
|
+
.form-help {
|
|
201
|
+
font-size: var(--text-xs);
|
|
202
|
+
color: var(--color-dim);
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Page layout
|
|
209
|
+
|
|
210
|
+
Three-section pattern: header, main content, optional right sidebar.
|
|
211
|
+
|
|
212
|
+
```html
|
|
213
|
+
<div class="app-shell">
|
|
214
|
+
<header class="app-header">
|
|
215
|
+
<img src="{brand.logoPath}" alt="{brand.name}" class="app-logo" />
|
|
216
|
+
<nav class="app-nav">
|
|
217
|
+
<a href="/screens">Screens</a>
|
|
218
|
+
<a href="/content">Content</a>
|
|
219
|
+
<a href="/settings">Settings</a>
|
|
220
|
+
</nav>
|
|
221
|
+
</header>
|
|
222
|
+
<main class="app-main">
|
|
223
|
+
<!-- page content -->
|
|
224
|
+
</main>
|
|
225
|
+
</div>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
```css
|
|
229
|
+
.app-header {
|
|
230
|
+
display: flex;
|
|
231
|
+
align-items: center;
|
|
232
|
+
gap: var(--space-6);
|
|
233
|
+
padding: var(--space-3) var(--space-6);
|
|
234
|
+
border-bottom: 1px solid var(--color-divider);
|
|
235
|
+
background: white;
|
|
236
|
+
}
|
|
237
|
+
.app-logo { height: 24px; }
|
|
238
|
+
.app-nav { display: flex; gap: var(--space-4); }
|
|
239
|
+
.app-nav a {
|
|
240
|
+
font-size: var(--text-sm);
|
|
241
|
+
color: var(--color-dim);
|
|
242
|
+
text-decoration: none;
|
|
243
|
+
}
|
|
244
|
+
.app-nav a:hover, .app-nav a.active { color: var(--color-text); }
|
|
245
|
+
.app-main { padding: var(--space-8) var(--space-6); max-width: 1200px; margin: 0 auto; }
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Empty states
|
|
251
|
+
|
|
252
|
+
When a list has no items yet, show an inviting empty state, not a blank page.
|
|
253
|
+
|
|
254
|
+
```html
|
|
255
|
+
<section class="empty-state">
|
|
256
|
+
<svg class="empty-icon">...</svg>
|
|
257
|
+
<h2>No screens paired yet</h2>
|
|
258
|
+
<p>Install the TomorrowOS player on a screen, then pair it using the code shown on the display.</p>
|
|
259
|
+
<a href="/pair" class="btn btn-primary">Pair your first screen</a>
|
|
260
|
+
</section>
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
```css
|
|
264
|
+
.empty-state {
|
|
265
|
+
text-align: center;
|
|
266
|
+
padding: var(--space-16) var(--space-6);
|
|
267
|
+
color: var(--color-dim);
|
|
268
|
+
}
|
|
269
|
+
.empty-state h2 { color: var(--color-text); margin: var(--space-4) 0 var(--space-2); }
|
|
270
|
+
.empty-state .btn { margin-top: var(--space-6); }
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## What to avoid
|
|
276
|
+
|
|
277
|
+
- Glassmorphism / heavy blur / frosted panels — not appropriate for an operations tool
|
|
278
|
+
- Gradients on buttons or cards — the primary colour is a flat brand colour, not a gradient
|
|
279
|
+
- Icon-only buttons without labels — labels are always clearer for operations work
|
|
280
|
+
- More than one "primary" button on a page — primary means *the* action
|
|
281
|
+
- Dark mode by default — operators work in retail / restaurant environments under normal lighting; use light mode unless the user explicitly requests dark
|
|
282
|
+
- Animations longer than 200ms — they slow operators down
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TomorrowOS style tokens
|
|
3
|
+
*
|
|
4
|
+
* These CSS custom properties are the visual seed of the CMS. They are
|
|
5
|
+
* derived from brand.json at server start — the CMS should never hard-code
|
|
6
|
+
* colours, typography, or spacing values.
|
|
7
|
+
*
|
|
8
|
+
* The LLM generating a CMS should reference these variables exclusively
|
|
9
|
+
* and build components that match the patterns in components.md.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
:root {
|
|
13
|
+
/* ═══ Brand ═══ */
|
|
14
|
+
/* These four are overridden by brand.json at build / runtime */
|
|
15
|
+
--color-primary: #FF8A3D; /* brand.primaryColor */
|
|
16
|
+
--color-secondary: #F5F3EF; /* brand.secondaryColor (optional) */
|
|
17
|
+
--color-background: #FAFAF9; /* brand.backgroundColor */
|
|
18
|
+
--color-text: #0A0908; /* brand.textColor */
|
|
19
|
+
|
|
20
|
+
/* ═══ Neutrals (fixed) ═══ */
|
|
21
|
+
--color-ink: #0A0908;
|
|
22
|
+
--color-dim: #706860;
|
|
23
|
+
--color-faint: #C4BEB4;
|
|
24
|
+
--color-mist: #F5F3EF;
|
|
25
|
+
--color-paper: #FAFAF9;
|
|
26
|
+
--color-divider: #E8E5DF;
|
|
27
|
+
|
|
28
|
+
/* ═══ Feedback (fixed) ═══ */
|
|
29
|
+
--color-success: #1D9E75;
|
|
30
|
+
--color-success-bg: #E5F4EC;
|
|
31
|
+
--color-warning: #F0B429;
|
|
32
|
+
--color-warning-bg: #FFF7E5;
|
|
33
|
+
--color-error: #C93333;
|
|
34
|
+
--color-error-bg: #FCE8E8;
|
|
35
|
+
--color-info: #4A90D9;
|
|
36
|
+
--color-info-bg: #E8F1F9;
|
|
37
|
+
|
|
38
|
+
/* ═══ Typography ═══ */
|
|
39
|
+
--font-sans: 'Inter', system-ui, -apple-system, sans-serif; /* brand.fontFamily */
|
|
40
|
+
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
|
|
41
|
+
|
|
42
|
+
--text-xs: 11px;
|
|
43
|
+
--text-sm: 13px;
|
|
44
|
+
--text-base: 15px;
|
|
45
|
+
--text-lg: 18px;
|
|
46
|
+
--text-xl: 22px;
|
|
47
|
+
--text-2xl: 28px;
|
|
48
|
+
--text-3xl: 36px;
|
|
49
|
+
--text-4xl: 48px;
|
|
50
|
+
|
|
51
|
+
--leading-tight: 1.25;
|
|
52
|
+
--leading-normal: 1.5;
|
|
53
|
+
--leading-relaxed: 1.65;
|
|
54
|
+
|
|
55
|
+
--weight-regular: 400;
|
|
56
|
+
--weight-medium: 500;
|
|
57
|
+
--weight-semibold: 600;
|
|
58
|
+
--weight-bold: 700;
|
|
59
|
+
|
|
60
|
+
/* ═══ Spacing ═══ */
|
|
61
|
+
--space-1: 4px;
|
|
62
|
+
--space-2: 8px;
|
|
63
|
+
--space-3: 12px;
|
|
64
|
+
--space-4: 16px;
|
|
65
|
+
--space-5: 20px;
|
|
66
|
+
--space-6: 24px;
|
|
67
|
+
--space-8: 32px;
|
|
68
|
+
--space-10: 40px;
|
|
69
|
+
--space-12: 48px;
|
|
70
|
+
--space-16: 64px;
|
|
71
|
+
--space-20: 80px;
|
|
72
|
+
|
|
73
|
+
/* ═══ Radii ═══ */
|
|
74
|
+
--radius-sm: 4px;
|
|
75
|
+
--radius-md: 6px;
|
|
76
|
+
--radius-lg: 10px;
|
|
77
|
+
--radius-xl: 16px;
|
|
78
|
+
--radius-pill: 9999px;
|
|
79
|
+
|
|
80
|
+
/* ═══ Shadows ═══ */
|
|
81
|
+
--shadow-sm: 0 1px 2px rgba(10, 9, 8, 0.04);
|
|
82
|
+
--shadow-md: 0 2px 8px rgba(10, 9, 8, 0.06);
|
|
83
|
+
--shadow-lg: 0 8px 24px rgba(10, 9, 8, 0.08);
|
|
84
|
+
|
|
85
|
+
/* ═══ Borders ═══ */
|
|
86
|
+
--border-width: 1px;
|
|
87
|
+
--border-width-thick: 2px;
|
|
88
|
+
--border-color: var(--color-divider);
|
|
89
|
+
|
|
90
|
+
/* ═══ Transitions ═══ */
|
|
91
|
+
--transition-fast: 120ms ease;
|
|
92
|
+
--transition-base: 200ms ease;
|
|
93
|
+
--transition-slow: 400ms ease;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Base resets */
|
|
97
|
+
body {
|
|
98
|
+
font-family: var(--font-sans);
|
|
99
|
+
font-size: var(--text-base);
|
|
100
|
+
line-height: var(--leading-normal);
|
|
101
|
+
color: var(--color-text);
|
|
102
|
+
background: var(--color-background);
|
|
103
|
+
margin: 0;
|
|
104
|
+
-webkit-font-smoothing: antialiased;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
code, pre, .mono {
|
|
108
|
+
font-family: var(--font-mono);
|
|
109
|
+
}
|