@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.
@@ -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
+ }