@everystate/examples 1.0.0 → 1.0.1

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.
Files changed (152) hide show
  1. package/README.md +15 -0
  2. package/everyState-core/001-counter/README.md +44 -0
  3. package/everyState-core/001-counter/index.html +79 -0
  4. package/everyState-core/002-counter-improved/README.md +44 -0
  5. package/everyState-core/002-counter-improved/index.html +83 -0
  6. package/everyState-core/003-input-reactive/README.md +44 -0
  7. package/everyState-core/003-input-reactive/index.html +68 -0
  8. package/everyState-core/004-computed-state/README.md +45 -0
  9. package/everyState-core/004-computed-state/index.html +83 -0
  10. package/everyState-core/005-conditional-rendering/README.md +42 -0
  11. package/everyState-core/005-conditional-rendering/index.html +68 -0
  12. package/everyState-core/006-list-rendering/README.md +49 -0
  13. package/everyState-core/006-list-rendering/index.html +92 -0
  14. package/everyState-core/007-form-validation/README.md +52 -0
  15. package/everyState-core/007-form-validation/index.html +108 -0
  16. package/everyState-core/008-undo-redo/README.md +70 -0
  17. package/everyState-core/008-undo-redo/index.html +133 -0
  18. package/everyState-core/009-localStorage-side-effects/README.md +72 -0
  19. package/everyState-core/009-localStorage-side-effects/index.html +80 -0
  20. package/everyState-core/010-decoupled-components/README.md +74 -0
  21. package/everyState-core/010-decoupled-components/index.html +117 -0
  22. package/everyState-core/011-async-patterns/README.md +98 -0
  23. package/everyState-core/011-async-patterns/index.html +132 -0
  24. package/everyState-css/001-stateDrivenCSS/index.html +377 -0
  25. package/everyState-css/002-cssV2FullDemo/index.html +630 -0
  26. package/everyState-view/001/counter/index.css +31 -0
  27. package/everyState-view/001/counter/index.html +50 -0
  28. package/everyState-view/002/datatable/index.css +70 -0
  29. package/everyState-view/002/datatable/index.html +118 -0
  30. package/everyState-view/003/todo/index.css +260 -0
  31. package/everyState-view/003/todo/index.html +218 -0
  32. package/everyState-view/003-input-reactive/README.md +44 -0
  33. package/everyState-view/003-input-reactive/index.html +68 -0
  34. package/everyState-view/004/quotesFetcher/index.css +124 -0
  35. package/everyState-view/004/quotesFetcher/index.html +108 -0
  36. package/everyState-view/004_01/quotesFetcher/app.js +32 -0
  37. package/everyState-view/004_01/quotesFetcher/components/appHeader/appSubtitle.js +2 -0
  38. package/everyState-view/004_01/quotesFetcher/components/appHeader/appTitle.js +2 -0
  39. package/everyState-view/004_01/quotesFetcher/components/appHeader.js +9 -0
  40. package/everyState-view/004_01/quotesFetcher/components/historyHeading.js +2 -0
  41. package/everyState-view/004_01/quotesFetcher/components/historyList/histAuthor.js +2 -0
  42. package/everyState-view/004_01/quotesFetcher/components/historyList/histQuote.js +2 -0
  43. package/everyState-view/004_01/quotesFetcher/components/historyList.js +14 -0
  44. package/everyState-view/004_01/quotesFetcher/components/quoteCard/fetchButton.js +2 -0
  45. package/everyState-view/004_01/quotesFetcher/components/quoteCard/quoteAuthor.js +2 -0
  46. package/everyState-view/004_01/quotesFetcher/components/quoteCard/quoteMessage.js +2 -0
  47. package/everyState-view/004_01/quotesFetcher/components/quoteCard/quoteText.js +2 -0
  48. package/everyState-view/004_01/quotesFetcher/components/quoteCard.js +11 -0
  49. package/everyState-view/004_01/quotesFetcher/index.css +124 -0
  50. package/everyState-view/004_01/quotesFetcher/index.html +23 -0
  51. package/everyState-view/004_01/quotesFetcher/store.js +35 -0
  52. package/everyState-view/004_02/quotesFetcher/app.js +20 -0
  53. package/everyState-view/004_02/quotesFetcher/components.js +46 -0
  54. package/everyState-view/004_02/quotesFetcher/index.css +124 -0
  55. package/everyState-view/004_02/quotesFetcher/index.html +23 -0
  56. package/everyState-view/004_02/quotesFetcher/store.js +35 -0
  57. package/everyState-view/004_03/quotesFetcher/actions.js +27 -0
  58. package/everyState-view/004_03/quotesFetcher/app.js +19 -0
  59. package/everyState-view/004_03/quotesFetcher/components.js +28 -0
  60. package/everyState-view/004_03/quotesFetcher/index.css +124 -0
  61. package/everyState-view/004_03/quotesFetcher/index.html +23 -0
  62. package/everyState-view/004_03/quotesFetcher/resolve.js +34 -0
  63. package/everyState-view/004_03/quotesFetcher/store.js +11 -0
  64. package/everyState-view/004_04/quotesFetcher/actions.js +66 -0
  65. package/everyState-view/004_04/quotesFetcher/app.js +24 -0
  66. package/everyState-view/004_04/quotesFetcher/components/archive.js +40 -0
  67. package/everyState-view/004_04/quotesFetcher/components/fetcher.js +29 -0
  68. package/everyState-view/004_04/quotesFetcher/components.js +20 -0
  69. package/everyState-view/004_04/quotesFetcher/index.css +283 -0
  70. package/everyState-view/004_04/quotesFetcher/index.html +24 -0
  71. package/everyState-view/004_04/quotesFetcher/resolve.js +34 -0
  72. package/everyState-view/004_04/quotesFetcher/store.js +21 -0
  73. package/everyState-view/004_04/statedump.json +826 -0
  74. package/everyState-view/004_05/quoteExplorer/actions.js +58 -0
  75. package/everyState-view/004_05/quoteExplorer/app.js +27 -0
  76. package/everyState-view/004_05/quoteExplorer/components.js +83 -0
  77. package/everyState-view/004_05/quoteExplorer/index.css +231 -0
  78. package/everyState-view/004_05/quoteExplorer/index.html +23 -0
  79. package/everyState-view/004_05/quoteExplorer/resolve.js +50 -0
  80. package/everyState-view/004_05/quoteExplorer/store.js +33 -0
  81. package/everyState-view/004_06/quoteExplorer/actions.js +21 -0
  82. package/everyState-view/004_06/quoteExplorer/app.js +44 -0
  83. package/everyState-view/004_06/quoteExplorer/components.js +80 -0
  84. package/everyState-view/004_06/quoteExplorer/derived.js +43 -0
  85. package/everyState-view/004_06/quoteExplorer/index.css +346 -0
  86. package/everyState-view/004_06/quoteExplorer/index.html +25 -0
  87. package/everyState-view/004_06/quoteExplorer/intents.js +44 -0
  88. package/everyState-view/004_06/quoteExplorer/policies.js +25 -0
  89. package/everyState-view/004_06/quoteExplorer/resolve.js +51 -0
  90. package/everyState-view/004_06/quoteExplorer/store.js +44 -0
  91. package/everyState-view/004_07/quoteExplorer/app.js +47 -0
  92. package/everyState-view/004_07/quoteExplorer/components.js +85 -0
  93. package/everyState-view/004_07/quoteExplorer/derived.js +43 -0
  94. package/everyState-view/004_07/quoteExplorer/index.css +346 -0
  95. package/everyState-view/004_07/quoteExplorer/index.html +25 -0
  96. package/everyState-view/004_07/quoteExplorer/intents.js +51 -0
  97. package/everyState-view/004_07/quoteExplorer/policies.js +21 -0
  98. package/everyState-view/004_07/quoteExplorer/resolve.js +39 -0
  99. package/everyState-view/004_07/quoteExplorer/store.js +44 -0
  100. package/everyState-view/004_08/quoteExplorer/app.js +78 -0
  101. package/everyState-view/004_08/quoteExplorer/components.js +85 -0
  102. package/everyState-view/004_08/quoteExplorer/derived.js +43 -0
  103. package/everyState-view/004_08/quoteExplorer/index.css +346 -0
  104. package/everyState-view/004_08/quoteExplorer/index.html +25 -0
  105. package/everyState-view/004_08/quoteExplorer/intents.js +51 -0
  106. package/everyState-view/004_08/quoteExplorer/policies.js +21 -0
  107. package/everyState-view/004_08/quoteExplorer/resolve.js +39 -0
  108. package/everyState-view/004_08/quoteExplorer/store.js +44 -0
  109. package/everyState-view/004_08_V2/app.js +78 -0
  110. package/everyState-view/004_08_V2/components/appDetail.js +8 -0
  111. package/everyState-view/004_08_V2/components/appDetailBar.js +7 -0
  112. package/everyState-view/004_08_V2/components/appDetailBarClose.js +8 -0
  113. package/everyState-view/004_08_V2/components/appDetailBarCount.js +7 -0
  114. package/everyState-view/004_08_V2/components/appDetailBarHeading.js +7 -0
  115. package/everyState-view/004_08_V2/components/appDetailQuotes.js +15 -0
  116. package/everyState-view/004_08_V2/components/appHeader.js +7 -0
  117. package/everyState-view/004_08_V2/components/appHeaderSubtitle.js +7 -0
  118. package/everyState-view/004_08_V2/components/appHeaderTitle.js +7 -0
  119. package/everyState-view/004_08_V2/components/appLog.js +7 -0
  120. package/everyState-view/004_08_V2/components/appLogHeading.js +7 -0
  121. package/everyState-view/004_08_V2/components/appLogList.js +16 -0
  122. package/everyState-view/004_08_V2/components/appSearch.js +7 -0
  123. package/everyState-view/004_08_V2/components/appSearchInput.js +9 -0
  124. package/everyState-view/004_08_V2/components/appStats.js +7 -0
  125. package/everyState-view/004_08_V2/components/appStatsContent.js +8 -0
  126. package/everyState-view/004_08_V2/components/appStatsContentFavcount.js +7 -0
  127. package/everyState-view/004_08_V2/components/appStatsContentText.js +7 -0
  128. package/everyState-view/004_08_V2/components/appStatsToggle.js +8 -0
  129. package/everyState-view/004_08_V2/components/appTags.js +7 -0
  130. package/everyState-view/004_08_V2/components/appTagsLabel.js +7 -0
  131. package/everyState-view/004_08_V2/components/appTagsRow.js +15 -0
  132. package/everyState-view/004_08_V2/components/index.js +59 -0
  133. package/everyState-view/004_08_V2/components/utils/css.js +88 -0
  134. package/everyState-view/004_08_V2/components/utils/elements.js +87 -0
  135. package/everyState-view/004_08_V2/components.js +79 -0
  136. package/everyState-view/004_08_V2/derived.js +43 -0
  137. package/everyState-view/004_08_V2/index.css +350 -0
  138. package/everyState-view/004_08_V2/index.html +25 -0
  139. package/everyState-view/004_08_V2/intents.js +51 -0
  140. package/everyState-view/004_08_V2/policies.js +21 -0
  141. package/everyState-view/004_08_V2/resolve.js +39 -0
  142. package/everyState-view/004_08_V2/store.js +44 -0
  143. package/everyState-view/006/api-datatable/index.css +388 -0
  144. package/everyState-view/006/api-datatable/index.html +355 -0
  145. package/everyState-view/007/apiUsers/index.html +307 -0
  146. package/everyState-view/007-form-validation/README.md +52 -0
  147. package/everyState-view/007-form-validation/index.html +108 -0
  148. package/everyState-view/010-decoupled-components/README.md +74 -0
  149. package/everyState-view/010-decoupled-components/index.html +117 -0
  150. package/everyState-view/index.html +36 -0
  151. package/index.js +0 -5
  152. package/package.json +2 -4
@@ -0,0 +1,377 @@
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.0">
6
+ <title>001 State-Driven CSS - @everystate/css</title>
7
+ <style>
8
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
9
+ html, body { height: 100%; }
10
+ body { font-family: system-ui, -apple-system, sans-serif; transition: background 0.3s, color 0.3s; }
11
+ </style>
12
+ <script type="importmap">
13
+ {
14
+ "imports": {
15
+ "@everystate/core": "../../../everystate-core/index.js",
16
+ "@everystate/perf": "../../../everystate-perf/index.js"
17
+ }
18
+ }
19
+ </script>
20
+ </head>
21
+ <body>
22
+
23
+ <div class="page">
24
+ <header class="header">
25
+ <h1>State-Driven CSS</h1>
26
+ <p>Every style on this page lives in an EveryState store. No Tailwind. No CSS-in-JS. Just paths.</p>
27
+ </header>
28
+
29
+ <section class="controls">
30
+ <h2>1. Theme Swap (one <code>store.set</code> call)</h2>
31
+ <div class="btn-row">
32
+ <button onclick="setTheme('light')">Light</button>
33
+ <button onclick="setTheme('dark')">Dark</button>
34
+ <button onclick="setTheme('ocean')">Ocean</button>
35
+ </div>
36
+
37
+ <h2>2. Live Token Edit</h2>
38
+ <p>Change the primary color token - every component using it updates:</p>
39
+ <div class="btn-row">
40
+ <input type="color" id="primaryPicker" value="#3b82f6" oninput="setToken(this.value)">
41
+ <span id="tokenLabel">#3b82f6</span>
42
+ </div>
43
+
44
+ <h2>3. State-Conditional Styles</h2>
45
+ <p>Toggle user plan - styles react to application state, not CSS classes:</p>
46
+ <div class="btn-row">
47
+ <button onclick="setPlan('free')">Free Plan</button>
48
+ <button onclick="setPlan('pro')">Pro Plan</button>
49
+ </div>
50
+ </section>
51
+
52
+ <section class="cards">
53
+ <div class="card">
54
+ <h3>Card A</h3>
55
+ <p>Hover me. My hover color comes from <code>css.card.hover.background</code>.</p>
56
+ </div>
57
+ <div class="card">
58
+ <h3>Card B</h3>
59
+ <p>My background, padding, border-radius - all state paths.</p>
60
+ </div>
61
+ <div class="card premium">
62
+ <h3>Premium Feature</h3>
63
+ <p>Only visible on Pro plan. Controlled by <code>css.premium.display</code>.</p>
64
+ </div>
65
+ </section>
66
+
67
+ <section class="controls">
68
+ <h2>4. Style Inspector</h2>
69
+ <p>The entire stylesheet as a state tree:</p>
70
+ <pre id="inspector"></pre>
71
+ </section>
72
+
73
+ <section class="controls">
74
+ <h2>5. Change Log (wildcard subscription on <code>css.*</code>)</h2>
75
+ <pre id="changelog"></pre>
76
+ </section>
77
+
78
+ <section class="controls">
79
+ <h2>6. Event Sequence Tests</h2>
80
+ <button onclick="runTests()">Run Tests</button>
81
+ <pre id="testLog"></pre>
82
+ </section>
83
+ </div>
84
+
85
+ <script type="module">
86
+ import { createEveryState } from '@everystate/core';
87
+ import { createPerfMonitor, mountOverlay } from '@everystate/perf';
88
+
89
+ // ============================================================
90
+ // Inline Style Engine (from @everystate/css)
91
+ // ============================================================
92
+ const CSS_PROPERTIES = new Set([
93
+ 'background','color','padding','margin','border','borderRadius',
94
+ 'boxShadow','fontSize','fontWeight','fontFamily','lineHeight',
95
+ 'letterSpacing','textAlign','textDecoration','textTransform',
96
+ 'display','position','top','right','bottom','left','zIndex',
97
+ 'width','height','minWidth','maxWidth','minHeight','maxHeight',
98
+ 'overflow','opacity','cursor','transition','transform',
99
+ 'gap','flexDirection','alignItems','justifyContent','flexWrap',
100
+ 'gridTemplateColumns','gridTemplateRows','gridGap',
101
+ 'borderColor','borderWidth','borderStyle','outline',
102
+ 'backgroundImage','backgroundSize','backgroundPosition',
103
+ 'whiteSpace','wordBreak','overflowWrap',
104
+ ]);
105
+
106
+ const PSEUDO_CLASSES = new Set([
107
+ 'hover','focus','active','disabled','visited',
108
+ 'firstChild','lastChild','nthChild',
109
+ ]);
110
+
111
+ function camelToKebab(s) {
112
+ return s.replace(/([A-Z])/g, '-$1').toLowerCase();
113
+ }
114
+
115
+ function createStyleEngine(store, { namespace = 'css', id = 'everystate-css' } = {}) {
116
+ const sheet = document.createElement('style');
117
+ sheet.id = id;
118
+ document.head.appendChild(sheet);
119
+ const ruleMap = new Map();
120
+
121
+ function upsertRule(selector, prop, value) {
122
+ if (!ruleMap.has(selector)) {
123
+ const idx = sheet.sheet.insertRule(`${selector} {}`, sheet.sheet.cssRules.length);
124
+ ruleMap.set(selector, { index: idx, rule: sheet.sheet.cssRules[idx] });
125
+ }
126
+ ruleMap.get(selector).rule.style.setProperty(camelToKebab(prop), value);
127
+ }
128
+
129
+ function pathToCSS(fullPath, value) {
130
+ const path = fullPath.startsWith(namespace + '.')
131
+ ? fullPath.slice(namespace.length + 1)
132
+ : fullPath;
133
+ const segments = path.split('.');
134
+ const prop = segments[segments.length - 1];
135
+ if (!CSS_PROPERTIES.has(prop)) return;
136
+ const selectorParts = [];
137
+ let pseudo = '';
138
+ for (let i = 0; i < segments.length - 1; i++) {
139
+ const seg = segments[i];
140
+ if (PSEUDO_CLASSES.has(seg)) { pseudo = ':' + seg; }
141
+ else { selectorParts.push('.' + seg); }
142
+ }
143
+ const selector = (selectorParts.length ? selectorParts.join('') : ':root') + pseudo;
144
+ upsertRule(selector, prop, value);
145
+ }
146
+
147
+ store.subscribe(`${namespace}.*`, ({ path, value }) => {
148
+ if (typeof value === 'string' || typeof value === 'number') {
149
+ pathToCSS(path, String(value));
150
+ } else if (typeof value === 'object' && value !== null) {
151
+ walkLeaves(path, value);
152
+ }
153
+ });
154
+
155
+ function walkLeaves(prefix, obj) {
156
+ for (const [k, v] of Object.entries(obj)) {
157
+ const p = `${prefix}.${k}`;
158
+ if (typeof v === 'object' && v !== null) walkLeaves(p, v);
159
+ else pathToCSS(p, String(v));
160
+ }
161
+ }
162
+
163
+ return { sheet, upsertRule, pathToCSS };
164
+ }
165
+
166
+ // ============================================================
167
+ // Themes
168
+ // ============================================================
169
+ const themes = {
170
+ light: {
171
+ page: { background: '#f8fafc', color: '#1e293b', padding: '2rem 2rem 4rem', maxWidth: '800px', margin: '0 auto' },
172
+ header: { padding: '2rem 0', borderBottom: '1px solid #e2e8f0', marginBottom: '2rem' },
173
+ card: {
174
+ background: '#ffffff', color: '#1e293b', padding: '1.5rem', borderRadius: '0.75rem',
175
+ boxShadow: '0 1px 3px rgba(0,0,0,0.08)', transition: 'box-shadow 0.2s, transform 0.2s', cursor: 'pointer',
176
+ hover: { boxShadow: '0 8px 25px rgba(0,0,0,0.12)', transform: 'translateY(-2px)' }
177
+ },
178
+ cards: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: '1rem', marginBottom: '2rem' },
179
+ controls: { marginBottom: '2rem' },
180
+ },
181
+ dark: {
182
+ page: { background: '#0f172a', color: '#e2e8f0', padding: '2rem 2rem 4rem', maxWidth: '800px', margin: '0 auto' },
183
+ header: { padding: '2rem 0', borderBottom: '1px solid #334155', marginBottom: '2rem' },
184
+ card: {
185
+ background: '#1e293b', color: '#e2e8f0', padding: '1.5rem', borderRadius: '0.75rem',
186
+ boxShadow: '0 1px 3px rgba(0,0,0,0.3)', transition: 'box-shadow 0.2s, transform 0.2s', cursor: 'pointer',
187
+ hover: { boxShadow: '0 8px 25px rgba(0,0,0,0.5)', transform: 'translateY(-2px)' }
188
+ },
189
+ cards: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: '1rem', marginBottom: '2rem' },
190
+ controls: { marginBottom: '2rem' },
191
+ },
192
+ ocean: {
193
+ page: { background: '#0c4a6e', color: '#e0f2fe', padding: '2rem 2rem 4rem', maxWidth: '800px', margin: '0 auto' },
194
+ header: { padding: '2rem 0', borderBottom: '1px solid #0369a1', marginBottom: '2rem' },
195
+ card: {
196
+ background: '#075985', color: '#e0f2fe', padding: '1.5rem', borderRadius: '0.75rem',
197
+ boxShadow: '0 1px 3px rgba(0,0,0,0.2)', transition: 'box-shadow 0.2s, transform 0.2s', cursor: 'pointer',
198
+ hover: { boxShadow: '0 8px 25px rgba(0,0,0,0.35)', transform: 'translateY(-2px)' }
199
+ },
200
+ cards: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: '1rem', marginBottom: '2rem' },
201
+ controls: { marginBottom: '2rem' },
202
+ },
203
+ };
204
+
205
+ // ============================================================
206
+ // Boot: store -> perf -> engine -> subscriptions (correct order)
207
+ // ============================================================
208
+ const store = createEveryState();
209
+
210
+ // Perf monitor wraps store BEFORE any subscriptions
211
+ const perf = createPerfMonitor(store);
212
+ mountOverlay(perf, document.body);
213
+
214
+ const engine = createStyleEngine(store);
215
+
216
+ // Fullscreen body styles (theme controls html+body, not just .page)
217
+ function syncBody() {
218
+ const bg = store.get('css.page.background');
219
+ const fg = store.get('css.page.color');
220
+ if (bg) document.body.style.background = bg;
221
+ if (fg) document.body.style.color = fg;
222
+ }
223
+ store.subscribe('css.*', ({ path }) => {
224
+ if (path === 'css.page.background' || path === 'css.page.color' || path === 'css.page') syncBody();
225
+ });
226
+
227
+ // Controls typography
228
+ store.set('css.controls.h2.fontSize', '1.1rem');
229
+ store.set('css.controls.h2.marginBottom', '0.5rem');
230
+ store.set('css.controls.h2.marginTop', '1rem');
231
+ store.set('css.controls.p.marginBottom', '0.5rem');
232
+ store.set('css.controls.p.fontSize', '0.9rem');
233
+ store.set('css.controls.p.opacity', '0.8');
234
+
235
+ // Button row
236
+ store.set('css.btn-row', { display: 'flex', gap: '0.5rem', alignItems: 'center', flexWrap: 'wrap', marginBottom: '1rem' });
237
+
238
+ // Button styles
239
+ store.set('css.btn-row.button.padding', '0.5rem 1rem');
240
+ store.set('css.btn-row.button.borderRadius', '0.5rem');
241
+ store.set('css.btn-row.button.border', '1px solid rgba(128,128,128,0.3)');
242
+ store.set('css.btn-row.button.cursor', 'pointer');
243
+ store.set('css.btn-row.button.fontSize', '0.9rem');
244
+ store.set('css.btn-row.button.background', 'rgba(128,128,128,0.15)');
245
+ store.set('css.btn-row.button.color', 'inherit');
246
+ store.set('css.btn-row.button.transition', 'background 0.2s');
247
+ store.set('css.btn-row.button.hover.background', 'rgba(128,128,128,0.3)');
248
+
249
+ // Inspector pre
250
+ store.set('css.controls.pre.background', 'rgba(0,0,0,0.08)');
251
+ store.set('css.controls.pre.padding', '1rem');
252
+ store.set('css.controls.pre.borderRadius', '0.5rem');
253
+ store.set('css.controls.pre.fontSize', '0.8rem');
254
+ store.set('css.controls.pre.overflow', 'auto');
255
+ store.set('css.controls.pre.maxHeight', '300px');
256
+ store.set('css.controls.pre.whiteSpace', 'pre-wrap');
257
+ store.set('css.controls.pre.border', '1px solid rgba(128,128,128,0.15)');
258
+
259
+ // Premium: hidden by default
260
+ store.set('css.premium.display', 'none');
261
+
262
+ // Apply light theme first
263
+ let currentTheme = 'light';
264
+ function applyTheme(name) {
265
+ currentTheme = name;
266
+ const t = themes[name];
267
+ for (const [component, styles] of Object.entries(t)) {
268
+ store.set(`css.${component}`, styles);
269
+ }
270
+ }
271
+ applyTheme('light');
272
+ syncBody();
273
+
274
+ // ============================================================
275
+ // User actions
276
+ // ============================================================
277
+ let changeLog = [];
278
+
279
+ store.subscribe('css.*', ({ path, value }) => {
280
+ if (typeof value === 'string' || typeof value === 'number') {
281
+ changeLog.unshift(`${path} = ${value}`);
282
+ if (changeLog.length > 30) changeLog.length = 30;
283
+ document.getElementById('changelog').textContent = changeLog.join('\n');
284
+ }
285
+ });
286
+
287
+ function updateInspector() {
288
+ document.getElementById('inspector').textContent = JSON.stringify(store.get('css'), null, 2);
289
+ }
290
+ store.subscribe('css.*', () => requestAnimationFrame(updateInspector));
291
+ updateInspector();
292
+
293
+ window.setTheme = function(name) { applyTheme(name); };
294
+
295
+ window.setToken = function(hex) {
296
+ document.getElementById('tokenLabel').textContent = hex;
297
+ store.set('css.card.background', hex);
298
+ store.set('css.btn-row.button.hover.background', hex + '44');
299
+ };
300
+
301
+ window.setPlan = function(plan) {
302
+ store.set('css.premium.display', plan === 'pro' ? 'block' : 'none');
303
+ };
304
+
305
+ // ============================================================
306
+ // Event Sequence Tests
307
+ // ============================================================
308
+ window.runTests = function() {
309
+ const log = document.getElementById('testLog');
310
+ const results = [];
311
+ let pass = 0, fail = 0;
312
+
313
+ function assert(label, condition) {
314
+ if (condition) { pass++; results.push(' OK ' + label); }
315
+ else { fail++; results.push(' FAIL ' + label); }
316
+ }
317
+
318
+ // Test 1: theme swap fires wildcard
319
+ {
320
+ let fired = false;
321
+ const unsub = store.subscribe('css.*', () => { fired = true; });
322
+ store.set('css.page.background', '#000');
323
+ assert('theme swap fires css.* wildcard', fired);
324
+ unsub();
325
+ applyTheme(currentTheme);
326
+ }
327
+
328
+ // Test 2: set/get round-trip
329
+ {
330
+ store.set('css.card.padding', '2rem');
331
+ assert('set/get round-trip', store.get('css.card.padding') === '2rem');
332
+ applyTheme(currentTheme);
333
+ }
334
+
335
+ // Test 3: subtree set populates children
336
+ {
337
+ store.set('css.card', { background: '#aaa', color: '#111' });
338
+ assert('subtree set: background', store.get('css.card.background') === '#aaa');
339
+ assert('subtree set: color', store.get('css.card.color') === '#111');
340
+ applyTheme(currentTheme);
341
+ }
342
+
343
+ // Test 4: premium toggle
344
+ {
345
+ store.set('css.premium.display', 'block');
346
+ assert('premium visible on pro', store.get('css.premium.display') === 'block');
347
+ store.set('css.premium.display', 'none');
348
+ assert('premium hidden on free', store.get('css.premium.display') === 'none');
349
+ }
350
+
351
+ // Test 5: subscriber fires in order
352
+ {
353
+ const seq = [];
354
+ const u1 = store.subscribe('css.card.padding', () => seq.push('A'));
355
+ const u2 = store.subscribe('css.*', () => seq.push('B'));
356
+ store.set('css.card.padding', '99px');
357
+ assert('exact fires before wildcard', seq[0] === 'A' && seq[1] === 'B');
358
+ u1(); u2();
359
+ applyTheme(currentTheme);
360
+ }
361
+
362
+ // Test 6: unsubscribe stops notifications
363
+ {
364
+ let count = 0;
365
+ const unsub = store.subscribe('css.page.color', () => count++);
366
+ store.set('css.page.color', '#000');
367
+ unsub();
368
+ store.set('css.page.color', '#fff');
369
+ assert('unsubscribe stops notifications', count === 1);
370
+ applyTheme(currentTheme);
371
+ }
372
+
373
+ log.textContent = results.join('\n') + `\n\n${pass} passed, ${fail} failed`;
374
+ };
375
+ </script>
376
+ </body>
377
+ </html>