jsgui3-server 0.0.140 → 0.0.142

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 (32) hide show
  1. package/.github/agents/jsgui3-server.agent.md +699 -0
  2. package/.github/instructions/copilot.instructions.md +180 -0
  3. package/.playwright-mcp/page-2025-11-29T23-39-18-629Z.png +0 -0
  4. package/.playwright-mcp/page-2025-11-29T23-39-31-903Z.png +0 -0
  5. package/.playwright-mcp/page-2025-11-30T00-33-56-265Z.png +0 -0
  6. package/.playwright-mcp/page-2025-11-30T00-34-06-619Z.png +0 -0
  7. package/docs/agent-development-guide.md +108 -4
  8. package/docs/api-reference.md +116 -0
  9. package/docs/controls-development.md +127 -0
  10. package/docs/css/luxuryObsidianCss.js +1203 -0
  11. package/docs/css/obsidian-scrollbars.css +370 -0
  12. package/docs/diagrams/jsgui3-stack.svg +568 -0
  13. package/docs/guides/JSGUI3_UI_ARCHITECTURE_GUIDE.md +2527 -0
  14. package/docs/guides/OBSIDIAN_LUXURY_DESIGN_GUIDE.md +847 -0
  15. package/docs/jsgui3-vs-express-comparison.svg +542 -0
  16. package/docs/jsgui3-vs-nestjs-comparison.svg +550 -0
  17. package/docs/publishers-guide.md +76 -0
  18. package/docs/troubleshooting.md +51 -0
  19. package/examples/controls/15) window, observable SSE/README.md +125 -0
  20. package/examples/controls/15) window, observable SSE/check.js +144 -0
  21. package/examples/controls/15) window, observable SSE/client.js +395 -0
  22. package/examples/controls/15) window, observable SSE/server.js +111 -0
  23. package/http/responders/static/Static_Route_HTTP_Responder.js +16 -16
  24. package/module.js +7 -0
  25. package/package.json +9 -8
  26. package/port-utils.js +112 -0
  27. package/serve-factory.js +27 -5
  28. package/tests/README.md +40 -26
  29. package/tests/examples-controls.e2e.test.js +164 -0
  30. package/tests/observable-sse.test.js +363 -0
  31. package/tests/port-utils.test.js +114 -0
  32. package/tests/test-runner.js +13 -12
@@ -0,0 +1,568 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 2400" width="100%">
2
+ <defs>
3
+ <!-- White Leather Gradients -->
4
+ <linearGradient id="whiteleather" x1="0%" y1="0%" x2="0%" y2="100%">
5
+ <stop offset="0%" style="stop-color:#fefefe"/>
6
+ <stop offset="50%" style="stop-color:#f8f6f3"/>
7
+ <stop offset="100%" style="stop-color:#f0ece6"/>
8
+ </linearGradient>
9
+
10
+ <!-- Obsidian Gradients -->
11
+ <linearGradient id="obsidianDeep" x1="0%" y1="0%" x2="0%" y2="100%">
12
+ <stop offset="0%" style="stop-color:#161b22"/>
13
+ <stop offset="100%" style="stop-color:#0d1117"/>
14
+ </linearGradient>
15
+ <linearGradient id="obsidianCard" x1="0%" y1="0%" x2="0%" y2="100%">
16
+ <stop offset="0%" style="stop-color:#21262d"/>
17
+ <stop offset="100%" style="stop-color:#161b22"/>
18
+ </linearGradient>
19
+ <linearGradient id="obsidianElevated" x1="0%" y1="0%" x2="0%" y2="100%">
20
+ <stop offset="0%" style="stop-color:#30363d"/>
21
+ <stop offset="100%" style="stop-color:#21262d"/>
22
+ </linearGradient>
23
+
24
+ <!-- Gold Gradients -->
25
+ <linearGradient id="goldShine" x1="0%" y1="0%" x2="100%" y2="0%">
26
+ <stop offset="0%" style="stop-color:#b8960f"/>
27
+ <stop offset="50%" style="stop-color:#d4af37"/>
28
+ <stop offset="100%" style="stop-color:#b8960f"/>
29
+ </linearGradient>
30
+ <linearGradient id="goldVertical" x1="0%" y1="0%" x2="0%" y2="100%">
31
+ <stop offset="0%" style="stop-color:#d4af37"/>
32
+ <stop offset="100%" style="stop-color:#b8960f"/>
33
+ </linearGradient>
34
+
35
+ <!-- Jewel Colors -->
36
+ <linearGradient id="sapphire" x1="0%" y1="0%" x2="0%" y2="100%">
37
+ <stop offset="0%" style="stop-color:#60a5fa"/>
38
+ <stop offset="100%" style="stop-color:#3b82f6"/>
39
+ </linearGradient>
40
+ <linearGradient id="emerald" x1="0%" y1="0%" x2="0%" y2="100%">
41
+ <stop offset="0%" style="stop-color:#34d399"/>
42
+ <stop offset="100%" style="stop-color:#10b981"/>
43
+ </linearGradient>
44
+ <linearGradient id="ruby" x1="0%" y1="0%" x2="0%" y2="100%">
45
+ <stop offset="0%" style="stop-color:#f87171"/>
46
+ <stop offset="100%" style="stop-color:#ef4444"/>
47
+ </linearGradient>
48
+ <linearGradient id="amethyst" x1="0%" y1="0%" x2="0%" y2="100%">
49
+ <stop offset="0%" style="stop-color:#c084fc"/>
50
+ <stop offset="100%" style="stop-color:#a855f7"/>
51
+ </linearGradient>
52
+
53
+ <!-- Filters -->
54
+ <filter id="dropShadow" x="-20%" y="-20%" width="140%" height="140%">
55
+ <feDropShadow dx="0" dy="4" stdDeviation="8" flood-color="#000" flood-opacity="0.25"/>
56
+ </filter>
57
+ <filter id="goldGlow" x="-50%" y="-50%" width="200%" height="200%">
58
+ <feGaussianBlur stdDeviation="2" result="blur"/>
59
+ <feFlood flood-color="#c9a227" flood-opacity="0.3"/>
60
+ <feComposite in2="blur" operator="in"/>
61
+ <feMerge>
62
+ <feMergeNode/>
63
+ <feMergeNode in="SourceGraphic"/>
64
+ </feMerge>
65
+ </filter>
66
+ <filter id="textGlow" x="-50%" y="-50%" width="200%" height="200%">
67
+ <feGaussianBlur stdDeviation="1" result="blur"/>
68
+ <feFlood flood-color="#c9a227" flood-opacity="0.5"/>
69
+ <feComposite in2="blur" operator="in"/>
70
+ <feMerge>
71
+ <feMergeNode/>
72
+ <feMergeNode in="SourceGraphic"/>
73
+ </feMerge>
74
+ </filter>
75
+
76
+ <!-- Arrow Marker -->
77
+ <marker id="arrowGold" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
78
+ <path d="M0,0 L0,6 L9,3 z" fill="#c9a227"/>
79
+ </marker>
80
+
81
+ <!-- Clip paths for gold bars -->
82
+ <clipPath id="clipSection1"><rect x="40" y="130" width="820" height="180" rx="12"/></clipPath>
83
+ <clipPath id="clipSection2"><rect x="40" y="350" width="820" height="260" rx="12"/></clipPath>
84
+ <clipPath id="clipSection3"><rect x="40" y="650" width="820" height="200" rx="12"/></clipPath>
85
+ <clipPath id="clipSection4"><rect x="40" y="890" width="820" height="380" rx="12"/></clipPath>
86
+ <clipPath id="clipSection5"><rect x="40" y="1310" width="820" height="200" rx="12"/></clipPath>
87
+ <clipPath id="clipSection6"><rect x="40" y="1550" width="820" height="100" rx="12"/></clipPath>
88
+ <clipPath id="clipSection7"><rect x="40" y="1680" width="820" height="240" rx="12"/></clipPath>
89
+ <clipPath id="clipSection8"><rect x="40" y="1950" width="820" height="260" rx="12"/></clipPath>
90
+ </defs>
91
+
92
+ <!-- Background -->
93
+ <rect width="100%" height="100%" fill="url(#whiteleather)"/>
94
+
95
+ <!-- Gold edge accents -->
96
+ <rect x="0" y="0" width="3" height="100%" fill="url(#goldVertical)"/>
97
+ <rect x="897" y="0" width="3" height="100%" fill="url(#goldVertical)"/>
98
+
99
+ <!-- TITLE -->
100
+ <text x="450" y="55" text-anchor="middle" font-family="Georgia, serif" font-size="36" font-weight="bold" fill="#0d1117" filter="url(#textGlow)">jsgui3 Stack Architecture</text>
101
+ <text x="450" y="85" text-anchor="middle" font-family="Inter, sans-serif" font-size="14" fill="#6e7681" letter-spacing="3">ISOMORPHIC JAVASCRIPT UI FRAMEWORK</text>
102
+
103
+ <!-- Gold decorative line -->
104
+ <line x1="200" y1="105" x2="700" y2="105" stroke="url(#goldShine)" stroke-width="2"/>
105
+ <circle cx="200" cy="105" r="3" fill="#c9a227"/>
106
+ <circle cx="450" cy="105" r="4" fill="#d4af37"/>
107
+ <circle cx="700" cy="105" r="3" fill="#c9a227"/>
108
+
109
+ <!-- SECTION 1: APPLICATION LAYER -->
110
+ <rect x="40" y="130" width="820" height="180" rx="12" fill="url(#obsidianCard)" filter="url(#dropShadow)"/>
111
+ <g clip-path="url(#clipSection1)">
112
+ <rect x="40" y="130" width="820" height="3" fill="url(#goldShine)"/>
113
+ </g>
114
+
115
+ <text x="70" y="162" font-family="Georgia, serif" font-size="18" font-weight="bold" fill="#f0f6fc">Application Layer</text>
116
+ <text x="250" y="162" font-family="Inter, sans-serif" font-size="11" fill="#6e7681">Your custom controls, pages, and business logic</text>
117
+
118
+ <!-- App boxes -->
119
+ <g transform="translate(60, 180)">
120
+ <rect width="180" height="105" rx="8" fill="url(#sapphire)"/>
121
+ <text x="90" y="30" text-anchor="middle" font-family="Georgia, serif" font-size="13" font-weight="bold" fill="#fff">Custom Controls</text>
122
+ <text x="90" y="50" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="10" fill="#e0f2fe">extends Control</text>
123
+ <rect x="15" y="62" width="150" height="28" rx="4" fill="#1e40af" opacity="0.6"/>
124
+ <text x="90" y="81" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" fill="#bfdbfe">MyButton, DataGrid...</text>
125
+ </g>
126
+
127
+ <g transform="translate(260, 180)">
128
+ <rect width="180" height="105" rx="8" fill="url(#emerald)"/>
129
+ <text x="90" y="30" text-anchor="middle" font-family="Georgia, serif" font-size="13" font-weight="bold" fill="#fff">Page Definitions</text>
130
+ <text x="90" y="50" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="10" fill="#d1fae5">Page_Context routes</text>
131
+ <rect x="15" y="62" width="150" height="28" rx="4" fill="#065f46" opacity="0.6"/>
132
+ <text x="90" y="81" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" fill="#a7f3d0">/home, /users/:id</text>
133
+ </g>
134
+
135
+ <g transform="translate(460, 180)">
136
+ <rect width="180" height="105" rx="8" fill="url(#amethyst)"/>
137
+ <text x="90" y="30" text-anchor="middle" font-family="Georgia, serif" font-size="13" font-weight="bold" fill="#fff">Business Logic</text>
138
+ <text x="90" y="50" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="10" fill="#f3e8ff">Services &amp; Models</text>
139
+ <rect x="15" y="62" width="150" height="28" rx="4" fill="#581c87" opacity="0.6"/>
140
+ <text x="90" y="81" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" fill="#e9d5ff">API calls, validation</text>
141
+ </g>
142
+
143
+ <g transform="translate(660, 180)">
144
+ <rect width="180" height="105" rx="8" fill="url(#ruby)"/>
145
+ <text x="90" y="30" text-anchor="middle" font-family="Georgia, serif" font-size="13" font-weight="bold" fill="#fff">Configuration</text>
146
+ <text x="90" y="50" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="10" fill="#fee2e2">App settings</text>
147
+ <rect x="15" y="62" width="150" height="28" rx="4" fill="#991b1b" opacity="0.6"/>
148
+ <text x="90" y="81" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" fill="#fecaca">routes, themes, i18n</text>
149
+ </g>
150
+
151
+ <!-- Arrow to Section 2 -->
152
+ <line x1="450" y1="310" x2="450" y2="340" stroke="#c9a227" stroke-width="2" marker-end="url(#arrowGold)"/>
153
+
154
+ <!-- SECTION 2: jsgui3-server -->
155
+ <rect x="40" y="350" width="820" height="260" rx="12" fill="url(#obsidianCard)" filter="url(#dropShadow)"/>
156
+ <g clip-path="url(#clipSection2)">
157
+ <rect x="40" y="350" width="820" height="3" fill="url(#goldShine)"/>
158
+ </g>
159
+
160
+ <text x="70" y="382" font-family="Georgia, serif" font-size="18" font-weight="bold" fill="#f0f6fc">jsgui3-server</text>
161
+ <rect x="195" y="368" width="55" height="18" rx="9" fill="#c9a227"/>
162
+ <text x="222" y="381" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" font-weight="bold" fill="#0d1117">v0.0.140</text>
163
+ <text x="270" y="382" font-family="Inter, sans-serif" font-size="11" fill="#6e7681">Node.js server runtime with ESBuild bundling</text>
164
+
165
+ <!-- Server core -->
166
+ <g transform="translate(60, 400)">
167
+ <rect width="250" height="185" rx="8" fill="url(#obsidianElevated)"/>
168
+ <text x="125" y="25" text-anchor="middle" font-family="Georgia, serif" font-size="13" font-weight="bold" fill="#c9a227">Server Core</text>
169
+
170
+ <rect x="15" y="40" width="220" height="38" rx="6" fill="#1e3a5f"/>
171
+ <text x="125" y="65" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="11" fill="#60a5fa">Express HTTP Server</text>
172
+
173
+ <rect x="15" y="88" width="220" height="38" rx="6" fill="#1e3a5f"/>
174
+ <text x="125" y="113" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="11" fill="#60a5fa">SSR Rendering Engine</text>
175
+
176
+ <rect x="15" y="136" width="220" height="38" rx="6" fill="#1e3a5f"/>
177
+ <text x="125" y="161" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="11" fill="#60a5fa">ESBuild Bundler</text>
178
+ </g>
179
+
180
+ <!-- Publishers -->
181
+ <g transform="translate(330, 400)">
182
+ <rect width="510" height="185" rx="8" fill="url(#obsidianElevated)"/>
183
+ <text x="255" y="25" text-anchor="middle" font-family="Georgia, serif" font-size="13" font-weight="bold" fill="#c9a227">Publishers (Output Strategies)</text>
184
+
185
+ <rect x="15" y="40" width="150" height="58" rx="6" fill="url(#sapphire)"/>
186
+ <text x="90" y="62" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="10" font-weight="bold" fill="#fff">devserver</text>
187
+ <text x="90" y="80" text-anchor="middle" font-family="Inter, sans-serif" font-size="9" fill="#bfdbfe">Live reload HMR</text>
188
+
189
+ <rect x="175" y="40" width="150" height="58" rx="6" fill="url(#emerald)"/>
190
+ <text x="250" y="62" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="10" font-weight="bold" fill="#fff">build</text>
191
+ <text x="250" y="80" text-anchor="middle" font-family="Inter, sans-serif" font-size="9" fill="#d1fae5">Production bundles</text>
192
+
193
+ <rect x="335" y="40" width="160" height="58" rx="6" fill="url(#amethyst)"/>
194
+ <text x="415" y="62" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="10" font-weight="bold" fill="#fff">staticize</text>
195
+ <text x="415" y="80" text-anchor="middle" font-family="Inter, sans-serif" font-size="9" fill="#f3e8ff">SSG pre-render</text>
196
+
197
+ <rect x="15" y="108" width="150" height="58" rx="6" fill="url(#ruby)"/>
198
+ <text x="90" y="130" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="10" font-weight="bold" fill="#fff">electron</text>
199
+ <text x="90" y="148" text-anchor="middle" font-family="Inter, sans-serif" font-size="9" fill="#fee2e2">Desktop apps</text>
200
+
201
+ <rect x="175" y="108" width="150" height="58" rx="6" fill="#4a5568"/>
202
+ <text x="250" y="130" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="10" font-weight="bold" fill="#fff">custom</text>
203
+ <text x="250" y="148" text-anchor="middle" font-family="Inter, sans-serif" font-size="9" fill="#cbd5e0">Your publisher</text>
204
+
205
+ <text x="415" y="140" text-anchor="middle" font-family="Inter, sans-serif" font-size="9" fill="#6e7681" font-style="italic">+ extensible...</text>
206
+ </g>
207
+
208
+ <!-- Arrow to Section 3 -->
209
+ <line x1="450" y1="610" x2="450" y2="640" stroke="#c9a227" stroke-width="2" marker-end="url(#arrowGold)"/>
210
+
211
+ <!-- SECTION 3: jsgui3-client -->
212
+ <rect x="40" y="650" width="820" height="200" rx="12" fill="url(#obsidianCard)" filter="url(#dropShadow)"/>
213
+ <g clip-path="url(#clipSection3)">
214
+ <rect x="40" y="650" width="820" height="3" fill="url(#goldShine)"/>
215
+ </g>
216
+
217
+ <text x="70" y="682" font-family="Georgia, serif" font-size="18" font-weight="bold" fill="#f0f6fc">jsgui3-client</text>
218
+ <rect x="188" y="668" width="55" height="18" rx="9" fill="#c9a227"/>
219
+ <text x="215" y="681" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" font-weight="bold" fill="#0d1117">v0.0.121</text>
220
+ <text x="265" y="682" font-family="Inter, sans-serif" font-size="11" fill="#6e7681">Browser runtime — DOM binding, events, client-side rendering</text>
221
+
222
+ <!-- Client modules -->
223
+ <g transform="translate(60, 700)">
224
+ <rect width="185" height="125" rx="8" fill="url(#obsidianElevated)"/>
225
+ <rect width="185" height="28" rx="8" fill="url(#sapphire)"/>
226
+ <text x="92" y="19" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="10" font-weight="bold" fill="#fff">Client_Page_Context</text>
227
+ <text x="92" y="52" text-anchor="middle" font-family="Inter, sans-serif" font-size="10" fill="#8b949e">Browser-side page</text>
228
+ <text x="92" y="68" text-anchor="middle" font-family="Inter, sans-serif" font-size="10" fill="#8b949e">management &amp; routing</text>
229
+ <rect x="20" y="82" width="145" height="28" rx="4" fill="#1e3a5f"/>
230
+ <text x="92" y="101" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="8" fill="#60a5fa">extends Page_Context</text>
231
+ </g>
232
+
233
+ <g transform="translate(260, 700)">
234
+ <rect width="185" height="125" rx="8" fill="url(#obsidianElevated)"/>
235
+ <rect width="185" height="28" rx="8" fill="url(#emerald)"/>
236
+ <text x="92" y="19" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="10" font-weight="bold" fill="#fff">HTTP Methods</text>
237
+ <text x="92" y="52" text-anchor="middle" font-family="Inter, sans-serif" font-size="10" fill="#8b949e">Fetch wrapper with</text>
238
+ <text x="92" y="68" text-anchor="middle" font-family="Inter, sans-serif" font-size="10" fill="#8b949e">JSON handling</text>
239
+ <rect x="15" y="82" width="75" height="28" rx="4" fill="#065f46"/>
240
+ <text x="52" y="101" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" fill="#34d399">GET</text>
241
+ <rect x="95" y="82" width="75" height="28" rx="4" fill="#065f46"/>
242
+ <text x="132" y="101" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" fill="#34d399">POST</text>
243
+ </g>
244
+
245
+ <g transform="translate(460, 700)">
246
+ <rect width="185" height="125" rx="8" fill="url(#obsidianElevated)"/>
247
+ <rect width="185" height="28" rx="8" fill="url(#amethyst)"/>
248
+ <text x="92" y="19" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="10" font-weight="bold" fill="#fff">DOM Operations</text>
249
+ <text x="92" y="52" text-anchor="middle" font-family="Inter, sans-serif" font-size="10" fill="#8b949e">Activate, bind,</text>
250
+ <text x="92" y="68" text-anchor="middle" font-family="Inter, sans-serif" font-size="10" fill="#8b949e">event attachment</text>
251
+ <rect x="15" y="82" width="155" height="28" rx="4" fill="#581c87"/>
252
+ <text x="92" y="101" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" fill="#c084fc">ctrl.activate()</text>
253
+ </g>
254
+
255
+ <g transform="translate(660, 700)">
256
+ <rect width="185" height="125" rx="8" fill="url(#obsidianElevated)"/>
257
+ <rect width="185" height="28" rx="8" fill="url(#ruby)"/>
258
+ <text x="92" y="19" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="10" font-weight="bold" fill="#fff">Resource Loading</text>
259
+ <text x="92" y="52" text-anchor="middle" font-family="Inter, sans-serif" font-size="10" fill="#8b949e">Dynamic script/CSS</text>
260
+ <text x="92" y="68" text-anchor="middle" font-family="Inter, sans-serif" font-size="10" fill="#8b949e">injection</text>
261
+ <rect x="15" y="82" width="155" height="28" rx="4" fill="#991b1b"/>
262
+ <text x="92" y="101" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" fill="#f87171">load_script()</text>
263
+ </g>
264
+
265
+ <!-- Arrow to Section 4 -->
266
+ <line x1="450" y1="850" x2="450" y2="880" stroke="#c9a227" stroke-width="2" marker-end="url(#arrowGold)"/>
267
+
268
+ <!-- SECTION 4: jsgui3-html (THE CORE) -->
269
+ <rect x="40" y="890" width="820" height="380" rx="12" fill="url(#obsidianDeep)" filter="url(#dropShadow)"/>
270
+ <rect x="40" y="890" width="820" height="380" rx="12" fill="none" stroke="#c9a227" stroke-width="2"/>
271
+ <g clip-path="url(#clipSection4)">
272
+ <rect x="40" y="890" width="820" height="5" fill="url(#goldShine)"/>
273
+ <rect x="40" y="1265" width="820" height="5" fill="url(#goldShine)"/>
274
+ </g>
275
+
276
+ <text x="450" y="925" text-anchor="middle" font-family="Georgia, serif" font-size="22" font-weight="bold" fill="#c9a227" filter="url(#goldGlow)">jsgui3-html</text>
277
+ <text x="450" y="948" text-anchor="middle" font-family="Georgia, serif" font-size="13" fill="#d4af37">★ THE CORE ★</text>
278
+ <rect x="385" y="955" width="55" height="18" rx="9" fill="#c9a227"/>
279
+ <text x="412" y="968" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" font-weight="bold" fill="#0d1117">v0.0.170</text>
280
+ <text x="450" y="995" text-anchor="middle" font-family="Inter, sans-serif" font-size="11" fill="#8b949e">Isomorphic Control class, Page_Context, component model, MVVM patterns</text>
281
+
282
+ <!-- Core Classes -->
283
+ <g transform="translate(60, 1015)">
284
+ <rect width="245" height="100" rx="8" fill="url(#goldShine)" filter="url(#goldGlow)"/>
285
+ <rect x="3" y="3" width="239" height="94" rx="6" fill="url(#obsidianCard)"/>
286
+ <text x="122" y="28" text-anchor="middle" font-family="Georgia, serif" font-size="15" font-weight="bold" fill="#c9a227">Control</text>
287
+ <text x="122" y="48" text-anchor="middle" font-family="Inter, sans-serif" font-size="10" fill="#8b949e">Base class for all UI components</text>
288
+ <text x="122" y="68" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" fill="#6e7681">add(), _build(), render_html()</text>
289
+ <text x="122" y="85" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" fill="#6e7681">activate(), context, dom</text>
290
+ </g>
291
+
292
+ <g transform="translate(325, 1015)">
293
+ <rect width="245" height="100" rx="8" fill="url(#goldShine)" filter="url(#goldGlow)"/>
294
+ <rect x="3" y="3" width="239" height="94" rx="6" fill="url(#obsidianCard)"/>
295
+ <text x="122" y="28" text-anchor="middle" font-family="Georgia, serif" font-size="15" font-weight="bold" fill="#c9a227">Page_Context</text>
296
+ <text x="122" y="48" text-anchor="middle" font-family="Inter, sans-serif" font-size="10" fill="#8b949e">Request handling &amp; routing</text>
297
+ <text x="122" y="68" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" fill="#6e7681">routes, params, middleware</text>
298
+ <text x="122" y="85" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" fill="#6e7681">SSR + client rehydration</text>
299
+ </g>
300
+
301
+ <g transform="translate(590, 1015)">
302
+ <rect width="245" height="100" rx="8" fill="url(#goldShine)" filter="url(#goldGlow)"/>
303
+ <rect x="3" y="3" width="239" height="94" rx="6" fill="url(#obsidianCard)"/>
304
+ <text x="122" y="28" text-anchor="middle" font-family="Georgia, serif" font-size="15" font-weight="bold" fill="#c9a227">MVVM Bindings</text>
305
+ <text x="122" y="48" text-anchor="middle" font-family="Inter, sans-serif" font-size="10" fill="#8b949e">Reactive data binding</text>
306
+ <text x="122" y="68" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" fill="#6e7681">bound(), .b(), mv_value</text>
307
+ <text x="122" y="85" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" fill="#6e7681">two-way sync model↔view</text>
308
+ </g>
309
+
310
+ <!-- Pattern boxes -->
311
+ <g transform="translate(60, 1135)">
312
+ <rect width="380" height="110" rx="8" fill="url(#obsidianElevated)"/>
313
+ <text x="190" y="25" text-anchor="middle" font-family="Georgia, serif" font-size="12" font-weight="bold" fill="#f0f6fc">Control Lifecycle</text>
314
+
315
+ <rect x="20" y="42" width="72" height="26" rx="4" fill="url(#sapphire)"/>
316
+ <text x="56" y="60" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" fill="#fff">constructor</text>
317
+ <text x="102" y="58" fill="#c9a227" font-size="14">→</text>
318
+
319
+ <rect x="115" y="42" width="55" height="26" rx="4" fill="url(#emerald)"/>
320
+ <text x="142" y="60" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" fill="#fff">_build</text>
321
+ <text x="180" y="58" fill="#c9a227" font-size="14">→</text>
322
+
323
+ <rect x="195" y="42" width="55" height="26" rx="4" fill="url(#amethyst)"/>
324
+ <text x="222" y="60" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" fill="#fff">render</text>
325
+ <text x="260" y="58" fill="#c9a227" font-size="14">→</text>
326
+
327
+ <rect x="275" y="42" width="72" height="26" rx="4" fill="url(#ruby)"/>
328
+ <text x="311" y="60" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" fill="#fff">activate</text>
329
+
330
+ <text x="190" y="88" text-anchor="middle" font-family="Inter, sans-serif" font-size="9" fill="#6e7681">Server: constructor → _build → render_html</text>
331
+ <text x="190" y="102" text-anchor="middle" font-family="Inter, sans-serif" font-size="9" fill="#6e7681">Client: activate() binds to existing DOM</text>
332
+ </g>
333
+
334
+ <g transform="translate(460, 1135)">
335
+ <rect width="380" height="110" rx="8" fill="url(#obsidianElevated)"/>
336
+ <text x="190" y="25" text-anchor="middle" font-family="Georgia, serif" font-size="12" font-weight="bold" fill="#f0f6fc">Key Exports</text>
337
+
338
+ <text x="30" y="52" font-family="JetBrains Mono, monospace" font-size="10" fill="#60a5fa">• Control</text>
339
+ <text x="30" y="70" font-family="JetBrains Mono, monospace" font-size="10" fill="#60a5fa">• Page_Context</text>
340
+ <text x="30" y="88" font-family="JetBrains Mono, monospace" font-size="10" fill="#60a5fa">• jsgui</text>
341
+
342
+ <text x="145" y="52" font-family="JetBrains Mono, monospace" font-size="10" fill="#34d399">• ctrl()</text>
343
+ <text x="145" y="70" font-family="JetBrains Mono, monospace" font-size="10" fill="#34d399">• html_ctrl()</text>
344
+ <text x="145" y="88" font-family="JetBrains Mono, monospace" font-size="10" fill="#34d399">• html_fragment</text>
345
+
346
+ <text x="275" y="52" font-family="JetBrains Mono, monospace" font-size="10" fill="#c084fc">• Selection</text>
347
+ <text x="275" y="70" font-family="JetBrains Mono, monospace" font-size="10" fill="#c084fc">• Collection</text>
348
+ <text x="275" y="88" font-family="JetBrains Mono, monospace" font-size="10" fill="#c084fc">• utils</text>
349
+ </g>
350
+
351
+ <!-- Arrow to Section 5 -->
352
+ <line x1="450" y1="1270" x2="450" y2="1300" stroke="#c9a227" stroke-width="2" marker-end="url(#arrowGold)"/>
353
+
354
+ <!-- SECTION 5: FOUNDATION PACKAGES -->
355
+ <rect x="40" y="1310" width="820" height="200" rx="12" fill="url(#obsidianCard)" filter="url(#dropShadow)"/>
356
+ <g clip-path="url(#clipSection5)">
357
+ <rect x="40" y="1310" width="820" height="3" fill="url(#goldShine)"/>
358
+ </g>
359
+
360
+ <text x="70" y="1342" font-family="Georgia, serif" font-size="18" font-weight="bold" fill="#f0f6fc">Foundation Packages</text>
361
+ <text x="280" y="1342" font-family="Inter, sans-serif" font-size="11" fill="#6e7681">Low-level utilities powering the stack</text>
362
+
363
+ <!-- Package boxes -->
364
+ <g transform="translate(60, 1360)">
365
+ <rect width="150" height="125" rx="8" fill="url(#obsidianElevated)"/>
366
+ <rect width="150" height="26" rx="8" fill="url(#sapphire)"/>
367
+ <text x="75" y="17" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="10" font-weight="bold" fill="#fff">lang-tools</text>
368
+ <rect x="100" y="5" width="42" height="15" rx="7" fill="#1e3a5f"/>
369
+ <text x="121" y="15" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="8" fill="#60a5fa">v0.0.41</text>
370
+ <text x="75" y="48" text-anchor="middle" font-family="Inter, sans-serif" font-size="9" fill="#8b949e">Type checking</text>
371
+ <text x="75" y="63" text-anchor="middle" font-family="Inter, sans-serif" font-size="9" fill="#8b949e">Object utilities</text>
372
+ <text x="75" y="78" text-anchor="middle" font-family="Inter, sans-serif" font-size="9" fill="#8b949e">Evented base</text>
373
+ <rect x="15" y="92" width="120" height="22" rx="4" fill="#1e3a5f"/>
374
+ <text x="75" y="107" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="8" fill="#60a5fa">tof(), fp(), each()</text>
375
+ </g>
376
+
377
+ <g transform="translate(225, 1360)">
378
+ <rect width="150" height="125" rx="8" fill="url(#obsidianElevated)"/>
379
+ <rect width="150" height="26" rx="8" fill="url(#emerald)"/>
380
+ <text x="75" y="17" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="10" font-weight="bold" fill="#fff">obext</text>
381
+ <rect x="100" y="5" width="42" height="15" rx="7" fill="#065f46"/>
382
+ <text x="121" y="15" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="8" fill="#34d399">v0.0.31</text>
383
+ <text x="75" y="48" text-anchor="middle" font-family="Inter, sans-serif" font-size="9" fill="#8b949e">Observable</text>
384
+ <text x="75" y="63" text-anchor="middle" font-family="Inter, sans-serif" font-size="9" fill="#8b949e">extensions</text>
385
+ <text x="75" y="78" text-anchor="middle" font-family="Inter, sans-serif" font-size="9" fill="#8b949e">Reactive signals</text>
386
+ <rect x="15" y="92" width="120" height="22" rx="4" fill="#065f46"/>
387
+ <text x="75" y="107" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="8" fill="#34d399">observe(), emit()</text>
388
+ </g>
389
+
390
+ <g transform="translate(390, 1360)">
391
+ <rect width="150" height="125" rx="8" fill="url(#obsidianElevated)"/>
392
+ <rect width="150" height="26" rx="8" fill="url(#amethyst)"/>
393
+ <text x="75" y="17" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="10" font-weight="bold" fill="#fff">jsgui3-gfx-core</text>
394
+ <rect x="95" y="5" width="48" height="15" rx="7" fill="#581c87"/>
395
+ <text x="119" y="15" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="8" fill="#c084fc">v0.0.24</text>
396
+ <text x="75" y="48" text-anchor="middle" font-family="Inter, sans-serif" font-size="9" fill="#8b949e">Canvas/WebGL</text>
397
+ <text x="75" y="63" text-anchor="middle" font-family="Inter, sans-serif" font-size="9" fill="#8b949e">primitives</text>
398
+ <text x="75" y="78" text-anchor="middle" font-family="Inter, sans-serif" font-size="9" fill="#8b949e">Drawing utils</text>
399
+ <rect x="15" y="92" width="120" height="22" rx="4" fill="#581c87"/>
400
+ <text x="75" y="107" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="8" fill="#c084fc">Point, Rect, Color</text>
401
+ </g>
402
+
403
+ <g transform="translate(555, 1360)">
404
+ <rect width="150" height="125" rx="8" fill="url(#obsidianElevated)"/>
405
+ <rect width="150" height="26" rx="8" fill="url(#ruby)"/>
406
+ <text x="75" y="17" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="10" font-weight="bold" fill="#fff">fnl</text>
407
+ <rect x="100" y="5" width="42" height="15" rx="7" fill="#991b1b"/>
408
+ <text x="121" y="15" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="8" fill="#f87171">v0.0.37</text>
409
+ <text x="75" y="48" text-anchor="middle" font-family="Inter, sans-serif" font-size="9" fill="#8b949e">Functional</text>
410
+ <text x="75" y="63" text-anchor="middle" font-family="Inter, sans-serif" font-size="9" fill="#8b949e">utilities</text>
411
+ <text x="75" y="78" text-anchor="middle" font-family="Inter, sans-serif" font-size="9" fill="#8b949e">Composition</text>
412
+ <rect x="15" y="92" width="120" height="22" rx="4" fill="#991b1b"/>
413
+ <text x="75" y="107" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="8" fill="#f87171">pipe(), compose()</text>
414
+ </g>
415
+
416
+ <g transform="translate(720, 1360)">
417
+ <rect width="120" height="125" rx="8" fill="url(#obsidianElevated)"/>
418
+ <rect width="120" height="26" rx="8" fill="#4a5568"/>
419
+ <text x="60" y="17" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="10" font-weight="bold" fill="#fff">fnlfs</text>
420
+ <text x="60" y="48" text-anchor="middle" font-family="Inter, sans-serif" font-size="9" fill="#8b949e">Async file</text>
421
+ <text x="60" y="63" text-anchor="middle" font-family="Inter, sans-serif" font-size="9" fill="#8b949e">operations</text>
422
+ <text x="60" y="78" text-anchor="middle" font-family="Inter, sans-serif" font-size="9" fill="#8b949e">Node.js fs</text>
423
+ <rect x="10" y="92" width="100" height="22" rx="4" fill="#4a5568"/>
424
+ <text x="60" y="107" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="8" fill="#a0aec0">read(), write()</text>
425
+ </g>
426
+
427
+ <!-- Arrow to Section 6 -->
428
+ <line x1="450" y1="1510" x2="450" y2="1540" stroke="#c9a227" stroke-width="2" marker-end="url(#arrowGold)"/>
429
+
430
+ <!-- SECTION 6: NODE.JS RUNTIME -->
431
+ <rect x="40" y="1550" width="820" height="100" rx="12" fill="#161b22" filter="url(#dropShadow)"/>
432
+ <g clip-path="url(#clipSection6)">
433
+ <rect x="40" y="1550" width="820" height="3" fill="url(#goldShine)"/>
434
+ </g>
435
+
436
+ <circle cx="115" cy="1600" r="28" fill="#3c873a"/>
437
+ <text x="115" y="1607" text-anchor="middle" font-family="Georgia, serif" font-size="16" font-weight="bold" fill="#fff">N</text>
438
+
439
+ <text x="170" y="1588" font-family="Georgia, serif" font-size="18" font-weight="bold" fill="#f0f6fc">Node.js Runtime</text>
440
+ <text x="340" y="1588" font-family="Inter, sans-serif" font-size="11" fill="#6e7681">v15+ required | ES Modules | V8 Engine</text>
441
+ <text x="170" y="1615" font-family="Inter, sans-serif" font-size="10" fill="#8b949e">The foundation upon which the entire jsgui3 stack executes. Provides async I/O,</text>
442
+ <text x="170" y="1632" font-family="Inter, sans-serif" font-size="10" fill="#8b949e">module resolution, and the JavaScript runtime for both server and build tools.</text>
443
+
444
+ <!-- SECTION 7: ISOMORPHIC DATA FLOW -->
445
+ <rect x="40" y="1680" width="820" height="240" rx="12" fill="url(#obsidianCard)" filter="url(#dropShadow)"/>
446
+ <rect x="40" y="1680" width="820" height="240" rx="12" fill="none" stroke="#c9a227" stroke-width="1"/>
447
+ <g clip-path="url(#clipSection7)">
448
+ <rect x="40" y="1680" width="820" height="3" fill="url(#goldShine)"/>
449
+ </g>
450
+
451
+ <text x="450" y="1712" text-anchor="middle" font-family="Georgia, serif" font-size="18" font-weight="bold" fill="#c9a227" filter="url(#textGlow)">Isomorphic Data Flow</text>
452
+ <text x="450" y="1735" text-anchor="middle" font-family="Inter, sans-serif" font-size="11" fill="#6e7681">The same Control code renders on server AND activates on client</text>
453
+
454
+ <!-- Flow diagram -->
455
+ <g transform="translate(60, 1755)">
456
+ <rect width="200" height="145" rx="8" fill="url(#obsidianElevated)"/>
457
+ <rect width="200" height="28" rx="8" fill="url(#sapphire)"/>
458
+ <text x="100" y="19" text-anchor="middle" font-family="Georgia, serif" font-size="11" font-weight="bold" fill="#fff">SERVER</text>
459
+
460
+ <rect x="15" y="38" width="170" height="26" rx="4" fill="#1e3a5f"/>
461
+ <text x="100" y="56" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" fill="#60a5fa">1. constructor(spec)</text>
462
+
463
+ <rect x="15" y="70" width="170" height="26" rx="4" fill="#1e3a5f"/>
464
+ <text x="100" y="88" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" fill="#60a5fa">2. _build()</text>
465
+
466
+ <rect x="15" y="102" width="170" height="26" rx="4" fill="#1e3a5f"/>
467
+ <text x="100" y="120" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" fill="#60a5fa">3. render_html()</text>
468
+
469
+ <text x="100" y="140" text-anchor="middle" font-family="Inter, sans-serif" font-size="9" fill="#6e7681">→ HTML string sent</text>
470
+ </g>
471
+
472
+ <path d="M 270 1835 L 330 1835" stroke="#c9a227" stroke-width="2" marker-end="url(#arrowGold)"/>
473
+
474
+ <g transform="translate(340, 1755)">
475
+ <rect width="120" height="145" rx="8" fill="url(#obsidianElevated)"/>
476
+ <rect width="120" height="28" rx="8" fill="#4a5568"/>
477
+ <text x="60" y="19" text-anchor="middle" font-family="Georgia, serif" font-size="11" font-weight="bold" fill="#fff">NETWORK</text>
478
+ <text x="60" y="58" text-anchor="middle" font-family="Inter, sans-serif" font-size="10" fill="#8b949e">HTTP</text>
479
+ <text x="60" y="78" text-anchor="middle" font-family="Inter, sans-serif" font-size="10" fill="#8b949e">Response</text>
480
+ <text x="60" y="98" text-anchor="middle" font-family="Inter, sans-serif" font-size="10" fill="#8b949e">+</text>
481
+ <text x="60" y="118" text-anchor="middle" font-family="Inter, sans-serif" font-size="10" fill="#8b949e">JS Bundle</text>
482
+ </g>
483
+
484
+ <path d="M 470 1835 L 530 1835" stroke="#c9a227" stroke-width="2" marker-end="url(#arrowGold)"/>
485
+
486
+ <g transform="translate(540, 1755)">
487
+ <rect width="200" height="145" rx="8" fill="url(#obsidianElevated)"/>
488
+ <rect width="200" height="28" rx="8" fill="url(#emerald)"/>
489
+ <text x="100" y="19" text-anchor="middle" font-family="Georgia, serif" font-size="11" font-weight="bold" fill="#fff">CLIENT</text>
490
+
491
+ <rect x="15" y="38" width="170" height="26" rx="4" fill="#065f46"/>
492
+ <text x="100" y="56" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" fill="#34d399">1. Parse HTML (browser)</text>
493
+
494
+ <rect x="15" y="70" width="170" height="26" rx="4" fill="#065f46"/>
495
+ <text x="100" y="88" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" fill="#34d399">2. Load JS bundle</text>
496
+
497
+ <rect x="15" y="102" width="170" height="26" rx="4" fill="#065f46"/>
498
+ <text x="100" y="120" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" fill="#34d399">3. ctrl.activate()</text>
499
+
500
+ <text x="100" y="140" text-anchor="middle" font-family="Inter, sans-serif" font-size="9" fill="#6e7681">→ Interactive!</text>
501
+ </g>
502
+
503
+ <rect x="760" y="1810" width="90" height="55" rx="6" fill="url(#goldShine)"/>
504
+ <text x="805" y="1832" text-anchor="middle" font-family="Georgia, serif" font-size="11" font-weight="bold" fill="#0d1117">Same</text>
505
+ <text x="805" y="1852" text-anchor="middle" font-family="Georgia, serif" font-size="11" font-weight="bold" fill="#0d1117">Code!</text>
506
+
507
+ <!-- SECTION 8: KEY CONVENTIONS -->
508
+ <rect x="40" y="1950" width="820" height="260" rx="12" fill="url(#obsidianCard)" filter="url(#dropShadow)"/>
509
+ <g clip-path="url(#clipSection8)">
510
+ <rect x="40" y="1950" width="820" height="3" fill="url(#goldShine)"/>
511
+ </g>
512
+
513
+ <text x="70" y="1982" font-family="Georgia, serif" font-size="18" font-weight="bold" fill="#f0f6fc">Key Conventions</text>
514
+ <text x="245" y="1982" font-family="Inter, sans-serif" font-size="11" fill="#6e7681">Essential patterns for working with jsgui3</text>
515
+
516
+ <!-- Convention cards -->
517
+ <g transform="translate(60, 2000)">
518
+ <rect width="380" height="92" rx="8" fill="url(#obsidianElevated)"/>
519
+ <rect width="380" height="26" rx="8" fill="url(#sapphire)"/>
520
+ <text x="15" y="17" font-family="Georgia, serif" font-size="11" font-weight="bold" fill="#fff">Context Threading</text>
521
+ <text x="15" y="46" font-family="Inter, sans-serif" font-size="10" fill="#8b949e">Every Control receives</text>
522
+ <text x="130" y="46" font-family="JetBrains Mono, monospace" font-size="10" fill="#60a5fa">{ context: this.context }</text>
523
+ <text x="15" y="64" font-family="Inter, sans-serif" font-size="10" fill="#8b949e">Enables resource sharing across the component tree</text>
524
+ <text x="15" y="82" font-family="JetBrains Mono, monospace" font-size="9" fill="#6e7681">this.context.page | this.context.app | this.context.config</text>
525
+ </g>
526
+
527
+ <g transform="translate(460, 2000)">
528
+ <rect width="380" height="92" rx="8" fill="url(#obsidianElevated)"/>
529
+ <rect width="380" height="26" rx="8" fill="url(#emerald)"/>
530
+ <text x="15" y="17" font-family="Georgia, serif" font-size="11" font-weight="bold" fill="#fff">Underscore Prefix Convention</text>
531
+ <text x="15" y="46" font-family="JetBrains Mono, monospace" font-size="10" fill="#34d399">_build()</text>
532
+ <text x="75" y="46" font-family="Inter, sans-serif" font-size="10" fill="#8b949e">→ Protected (override in subclasses)</text>
533
+ <text x="15" y="64" font-family="JetBrains Mono, monospace" font-size="10" fill="#34d399">__internal()</text>
534
+ <text x="95" y="64" font-family="Inter, sans-serif" font-size="10" fill="#8b949e">→ Private (framework internal)</text>
535
+ <text x="15" y="82" font-family="Inter, sans-serif" font-size="9" fill="#6e7681">No prefix = public API</text>
536
+ </g>
537
+
538
+ <g transform="translate(60, 2102)">
539
+ <rect width="380" height="92" rx="8" fill="url(#obsidianElevated)"/>
540
+ <rect width="380" height="26" rx="8" fill="url(#amethyst)"/>
541
+ <text x="15" y="17" font-family="Georgia, serif" font-size="11" font-weight="bold" fill="#fff">Spec Object Pattern</text>
542
+ <text x="15" y="44" font-family="JetBrains Mono, monospace" font-size="9" fill="#c084fc">new MyControl({ context, text: 'Hello', onClick: fn })</text>
543
+ <text x="15" y="62" font-family="Inter, sans-serif" font-size="10" fill="#8b949e">All configuration via single spec object</text>
544
+ <text x="15" y="82" font-family="Inter, sans-serif" font-size="9" fill="#6e7681">Destructure in constructor: const { context, text } = spec;</text>
545
+ </g>
546
+
547
+ <g transform="translate(460, 2102)">
548
+ <rect width="380" height="92" rx="8" fill="url(#obsidianElevated)"/>
549
+ <rect width="380" height="26" rx="8" fill="url(#ruby)"/>
550
+ <text x="15" y="17" font-family="Georgia, serif" font-size="11" font-weight="bold" fill="#fff">HTML Generation</text>
551
+ <text x="15" y="44" font-family="JetBrains Mono, monospace" font-size="9" fill="#f87171">ctrl.render_html()</text>
552
+ <text x="130" y="44" font-family="Inter, sans-serif" font-size="10" fill="#8b949e">→ Returns HTML string</text>
553
+ <text x="15" y="62" font-family="JetBrains Mono, monospace" font-size="9" fill="#f87171">html_fragment`&lt;div&gt;${child}&lt;/div&gt;`</text>
554
+ <text x="15" y="82" font-family="Inter, sans-serif" font-size="9" fill="#6e7681">Template literals for safe HTML composition</text>
555
+ </g>
556
+
557
+ <!-- FOOTER -->
558
+ <line x1="200" y1="2240" x2="700" y2="2240" stroke="url(#goldShine)" stroke-width="2"/>
559
+ <circle cx="200" cy="2240" r="3" fill="#c9a227"/>
560
+ <circle cx="450" cy="2240" r="4" fill="#d4af37"/>
561
+ <circle cx="700" cy="2240" r="3" fill="#c9a227"/>
562
+
563
+ <text x="450" y="2270" text-anchor="middle" font-family="Georgia, serif" font-size="14" fill="#0d1117">jsgui3 — Isomorphic JavaScript UI Framework</text>
564
+ <text x="450" y="2295" text-anchor="middle" font-family="Inter, sans-serif" font-size="11" fill="#6e7681">Server-side rendering • Client-side activation • Same codebase</text>
565
+ <text x="450" y="2330" text-anchor="middle" font-family="Inter, sans-serif" font-size="10" fill="#8b949e">npm: jsgui3-server | jsgui3-client | jsgui3-html | lang-tools</text>
566
+ <text x="450" y="2365" text-anchor="middle" font-family="Georgia, serif" font-size="12" fill="#c9a227" filter="url(#textGlow)">★</text>
567
+
568
+ </svg>