tjs-lang 0.2.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.
Files changed (91) hide show
  1. package/CONTEXT.md +594 -0
  2. package/LICENSE +190 -0
  3. package/README.md +220 -0
  4. package/bin/benchmarks.ts +351 -0
  5. package/bin/dev.ts +205 -0
  6. package/bin/docs.js +170 -0
  7. package/bin/install-cursor.sh +71 -0
  8. package/bin/install-vscode.sh +71 -0
  9. package/bin/select-local-models.d.ts +1 -0
  10. package/bin/select-local-models.js +28 -0
  11. package/bin/select-local-models.ts +31 -0
  12. package/demo/autocomplete.test.ts +232 -0
  13. package/demo/docs.json +186 -0
  14. package/demo/examples.test.ts +598 -0
  15. package/demo/index.html +91 -0
  16. package/demo/src/autocomplete.ts +482 -0
  17. package/demo/src/capabilities.ts +859 -0
  18. package/demo/src/demo-nav.ts +2097 -0
  19. package/demo/src/examples.test.ts +161 -0
  20. package/demo/src/examples.ts +476 -0
  21. package/demo/src/imports.test.ts +196 -0
  22. package/demo/src/imports.ts +421 -0
  23. package/demo/src/index.ts +639 -0
  24. package/demo/src/module-store.ts +635 -0
  25. package/demo/src/module-sw.ts +132 -0
  26. package/demo/src/playground.ts +949 -0
  27. package/demo/src/service-host.ts +389 -0
  28. package/demo/src/settings.ts +440 -0
  29. package/demo/src/style.ts +280 -0
  30. package/demo/src/tjs-playground.ts +1605 -0
  31. package/demo/src/ts-examples.ts +478 -0
  32. package/demo/src/ts-playground.ts +1092 -0
  33. package/demo/static/favicon.svg +30 -0
  34. package/demo/static/photo-1.jpg +0 -0
  35. package/demo/static/photo-2.jpg +0 -0
  36. package/demo/static/texts/ai-history.txt +9 -0
  37. package/demo/static/texts/coffee-origins.txt +9 -0
  38. package/demo/static/texts/renewable-energy.txt +9 -0
  39. package/dist/index.js +256 -0
  40. package/dist/index.js.map +37 -0
  41. package/dist/tjs-batteries.js +4 -0
  42. package/dist/tjs-batteries.js.map +15 -0
  43. package/dist/tjs-full.js +256 -0
  44. package/dist/tjs-full.js.map +37 -0
  45. package/dist/tjs-transpiler.js +220 -0
  46. package/dist/tjs-transpiler.js.map +21 -0
  47. package/dist/tjs-vm.js +4 -0
  48. package/dist/tjs-vm.js.map +14 -0
  49. package/docs/CNAME +1 -0
  50. package/docs/favicon.svg +30 -0
  51. package/docs/index.html +91 -0
  52. package/docs/index.js +10468 -0
  53. package/docs/index.js.map +92 -0
  54. package/docs/photo-1.jpg +0 -0
  55. package/docs/photo-1.webp +0 -0
  56. package/docs/photo-2.jpg +0 -0
  57. package/docs/photo-2.webp +0 -0
  58. package/docs/texts/ai-history.txt +9 -0
  59. package/docs/texts/coffee-origins.txt +9 -0
  60. package/docs/texts/renewable-energy.txt +9 -0
  61. package/docs/tjs-lang.svg +31 -0
  62. package/docs/tosijs-agent.svg +31 -0
  63. package/editors/README.md +325 -0
  64. package/editors/ace/ajs-mode.js +328 -0
  65. package/editors/ace/ajs-mode.ts +269 -0
  66. package/editors/ajs-syntax.ts +212 -0
  67. package/editors/build-grammars.ts +510 -0
  68. package/editors/codemirror/ajs-language.js +287 -0
  69. package/editors/codemirror/ajs-language.ts +1447 -0
  70. package/editors/codemirror/autocomplete.test.ts +531 -0
  71. package/editors/codemirror/component.ts +404 -0
  72. package/editors/monaco/ajs-monarch.js +243 -0
  73. package/editors/monaco/ajs-monarch.ts +225 -0
  74. package/editors/tjs-syntax.ts +115 -0
  75. package/editors/vscode/language-configuration.json +37 -0
  76. package/editors/vscode/package.json +65 -0
  77. package/editors/vscode/syntaxes/ajs-injection.tmLanguage.json +107 -0
  78. package/editors/vscode/syntaxes/ajs.tmLanguage.json +252 -0
  79. package/editors/vscode/syntaxes/tjs.tmLanguage.json +333 -0
  80. package/package.json +83 -0
  81. package/src/cli/commands/check.ts +41 -0
  82. package/src/cli/commands/convert.ts +133 -0
  83. package/src/cli/commands/emit.ts +260 -0
  84. package/src/cli/commands/run.ts +68 -0
  85. package/src/cli/commands/test.ts +194 -0
  86. package/src/cli/commands/types.ts +20 -0
  87. package/src/cli/create-app.ts +236 -0
  88. package/src/cli/playground.ts +250 -0
  89. package/src/cli/tjs.ts +166 -0
  90. package/src/cli/tjsx.ts +160 -0
  91. package/tjs-lang.svg +31 -0
@@ -0,0 +1,440 @@
1
+ /*
2
+ * settings.ts - Settings dialog for API keys and preferences
3
+ */
4
+
5
+ import { elements, vars, Component, StyleSheet } from 'tosijs'
6
+
7
+ import { icons } from 'tosijs-ui'
8
+
9
+ import {
10
+ rescanLocalModels,
11
+ checkServerLoad,
12
+ getPendingRequests,
13
+ } from './capabilities'
14
+
15
+ const { div, button, span, label, input, h2, p, select, option } = elements
16
+
17
+ // Settings dialog styles
18
+ StyleSheet('settings-styles', {
19
+ '.settings-overlay': {
20
+ position: 'fixed',
21
+ top: 0,
22
+ left: 0,
23
+ right: 0,
24
+ bottom: 0,
25
+ background: 'rgba(0, 0, 0, 0.5)',
26
+ display: 'flex',
27
+ alignItems: 'center',
28
+ justifyContent: 'center',
29
+ zIndex: 1000,
30
+ },
31
+
32
+ '.settings-dialog': {
33
+ background: vars.background,
34
+ borderRadius: vars.borderRadius,
35
+ padding: vars.spacing200,
36
+ maxWidth: '500px',
37
+ width: '90%',
38
+ maxHeight: '80vh',
39
+ overflow: 'auto',
40
+ boxShadow: '0 10px 40px rgba(0, 0, 0, 0.3)',
41
+ },
42
+
43
+ '.settings-header': {
44
+ display: 'flex',
45
+ alignItems: 'center',
46
+ justifyContent: 'space-between',
47
+ marginBottom: vars.spacing,
48
+ },
49
+
50
+ '.settings-header h2': {
51
+ margin: 0,
52
+ fontSize: '1.25em',
53
+ },
54
+
55
+ '.settings-section': {
56
+ marginBottom: vars.spacing200,
57
+ },
58
+
59
+ '.settings-section h3': {
60
+ fontSize: '1em',
61
+ marginBottom: vars.spacing,
62
+ color: vars.brandColor,
63
+ },
64
+
65
+ '.settings-field': {
66
+ marginBottom: vars.spacing,
67
+ },
68
+
69
+ '.settings-field label': {
70
+ display: 'block',
71
+ marginBottom: '4px',
72
+ fontSize: '0.9em',
73
+ fontWeight: 500,
74
+ },
75
+
76
+ '.settings-field input': {
77
+ width: '100%',
78
+ padding: '8px 12px',
79
+ border: `1px solid ${vars.codeBorder}`,
80
+ borderRadius: vars.borderRadius,
81
+ background: vars.background,
82
+ color: vars.textColor,
83
+ fontSize: 'inherit',
84
+ },
85
+
86
+ '.settings-field input:focus': {
87
+ outline: 'none',
88
+ borderColor: vars.brandColor,
89
+ boxShadow: `0 0 0 2px rgba(99, 102, 241, 0.2)`,
90
+ },
91
+
92
+ '.settings-field .hint': {
93
+ fontSize: '0.8em',
94
+ color: vars.textColor,
95
+ opacity: 0.7,
96
+ marginTop: '4px',
97
+ },
98
+
99
+ '.settings-actions': {
100
+ display: 'flex',
101
+ justifyContent: 'flex-end',
102
+ gap: vars.spacing,
103
+ marginTop: vars.spacing200,
104
+ paddingTop: vars.spacing,
105
+ borderTop: `1px solid ${vars.codeBorder}`,
106
+ },
107
+
108
+ '.settings-btn': {
109
+ padding: '8px 16px',
110
+ borderRadius: vars.borderRadius,
111
+ border: 'none',
112
+ cursor: 'pointer',
113
+ fontWeight: 500,
114
+ },
115
+
116
+ '.settings-btn.primary': {
117
+ background: vars.brandColor,
118
+ color: 'white',
119
+ },
120
+
121
+ '.settings-btn.secondary': {
122
+ background: vars.codeBackground,
123
+ color: vars.textColor,
124
+ },
125
+ })
126
+
127
+ export type LLMProvider =
128
+ | 'auto'
129
+ | 'custom'
130
+ | 'openai'
131
+ | 'anthropic'
132
+ | 'deepseek'
133
+
134
+ export interface SettingsData {
135
+ preferredProvider: LLMProvider
136
+ openaiKey: string
137
+ anthropicKey: string
138
+ deepseekKey: string
139
+ customLlmUrl: string
140
+ }
141
+
142
+ export function showSettingsDialog(
143
+ currentSettings: SettingsData,
144
+ onSave: (settings: SettingsData) => void
145
+ ): void {
146
+ const overlay = div({ class: 'settings-overlay' })
147
+
148
+ const providerSelect = select(
149
+ { style: { width: '100%', padding: '8px 12px', borderRadius: '6px' } },
150
+ option(
151
+ { value: 'auto', selected: currentSettings.preferredProvider === 'auto' },
152
+ 'First Available'
153
+ ),
154
+ option(
155
+ {
156
+ value: 'custom',
157
+ selected: currentSettings.preferredProvider === 'custom',
158
+ },
159
+ 'Custom Endpoint (LM Studio, Ollama)'
160
+ ),
161
+ option(
162
+ {
163
+ value: 'openai',
164
+ selected: currentSettings.preferredProvider === 'openai',
165
+ },
166
+ 'OpenAI'
167
+ ),
168
+ option(
169
+ {
170
+ value: 'anthropic',
171
+ selected: currentSettings.preferredProvider === 'anthropic',
172
+ },
173
+ 'Anthropic'
174
+ ),
175
+ option(
176
+ {
177
+ value: 'deepseek',
178
+ selected: currentSettings.preferredProvider === 'deepseek',
179
+ },
180
+ 'Deepseek'
181
+ )
182
+ )
183
+
184
+ const openaiInput = input({
185
+ type: 'password',
186
+ placeholder: 'sk-...',
187
+ value: currentSettings.openaiKey,
188
+ autocomplete: 'off',
189
+ })
190
+
191
+ const anthropicInput = input({
192
+ type: 'password',
193
+ placeholder: 'sk-ant-...',
194
+ value: currentSettings.anthropicKey,
195
+ autocomplete: 'off',
196
+ })
197
+
198
+ const deepseekInput = input({
199
+ type: 'password',
200
+ placeholder: 'sk-...',
201
+ value: currentSettings.deepseekKey,
202
+ autocomplete: 'off',
203
+ })
204
+
205
+ const customUrlInput = input({
206
+ type: 'url',
207
+ placeholder: 'http://localhost:1234/v1',
208
+ value: currentSettings.customLlmUrl,
209
+ })
210
+
211
+ const close = () => {
212
+ overlay.remove()
213
+ }
214
+
215
+ const save = () => {
216
+ onSave({
217
+ preferredProvider: (providerSelect as HTMLSelectElement)
218
+ .value as LLMProvider,
219
+ openaiKey: (openaiInput as HTMLInputElement).value,
220
+ anthropicKey: (anthropicInput as HTMLInputElement).value,
221
+ deepseekKey: (deepseekInput as HTMLInputElement).value,
222
+ customLlmUrl: (customUrlInput as HTMLInputElement).value,
223
+ })
224
+ close()
225
+ }
226
+
227
+ overlay.append(
228
+ div(
229
+ { class: 'settings-dialog' },
230
+ // Header
231
+ div(
232
+ { class: 'settings-header' },
233
+ h2('Settings'),
234
+ button(
235
+ {
236
+ class: 'iconic',
237
+ onClick: close,
238
+ },
239
+ icons.x()
240
+ )
241
+ ),
242
+
243
+ // LLM API Keys section
244
+ div(
245
+ { class: 'settings-section' },
246
+ div(
247
+ { style: { fontWeight: 600, marginBottom: '10px' } },
248
+ 'LLM Settings'
249
+ ),
250
+ p(
251
+ { style: { fontSize: '0.85em', opacity: 0.8, marginBottom: '15px' } },
252
+ 'API keys are stored locally in your browser and never sent to any server except the respective API provider.'
253
+ ),
254
+
255
+ div(
256
+ { class: 'settings-field' },
257
+ label('Preferred Provider'),
258
+ providerSelect,
259
+ div(
260
+ { class: 'hint' },
261
+ '"First Available" uses the first configured provider in order below'
262
+ )
263
+ ),
264
+
265
+ div(
266
+ { class: 'settings-field' },
267
+ label('Custom LLM Endpoint'),
268
+ customUrlInput,
269
+ div(
270
+ { class: 'hint' },
271
+ 'OpenAI-compatible endpoint (e.g., LM Studio, Ollama). ',
272
+ window.location.protocol === 'https:'
273
+ ? span(
274
+ { style: { color: '#dc2626' } },
275
+ 'Note: Local endpoints require HTTP.'
276
+ )
277
+ : ''
278
+ ),
279
+ div(
280
+ {
281
+ style: {
282
+ marginTop: '8px',
283
+ display: 'flex',
284
+ alignItems: 'center',
285
+ gap: '10px',
286
+ flexWrap: 'wrap',
287
+ },
288
+ },
289
+ button(
290
+ {
291
+ class: 'settings-btn secondary',
292
+ style: { padding: '4px 10px', fontSize: '0.85em' },
293
+ onClick: async (e: Event) => {
294
+ const btn = e.target as HTMLButtonElement
295
+ const container = btn.parentElement as HTMLElement
296
+ const statusEl = container.querySelector(
297
+ '.status-text'
298
+ ) as HTMLElement
299
+ btn.disabled = true
300
+ btn.textContent = 'Scanning...'
301
+ statusEl.textContent = ''
302
+
303
+ const url = (customUrlInput as HTMLInputElement).value
304
+ const models = await rescanLocalModels(url)
305
+
306
+ btn.disabled = false
307
+ btn.textContent = 'Rescan Models'
308
+
309
+ if (models.length > 0) {
310
+ const visionModels = models.filter(
311
+ (m) =>
312
+ m.includes('-vl') ||
313
+ m.includes('vl-') ||
314
+ m.includes('vision') ||
315
+ m.includes('llava') ||
316
+ m.includes('gemma-3') ||
317
+ m.includes('gemma3')
318
+ )
319
+ statusEl.textContent =
320
+ `Found ${models.length} model(s)` +
321
+ (visionModels.length > 0
322
+ ? ` (${visionModels.length} vision)`
323
+ : '')
324
+ statusEl.style.color = '#16a34a'
325
+ } else {
326
+ statusEl.textContent = 'No models found'
327
+ statusEl.style.color = '#dc2626'
328
+ }
329
+ },
330
+ },
331
+ 'Rescan Models'
332
+ ),
333
+ button(
334
+ {
335
+ class: 'settings-btn secondary',
336
+ style: { padding: '4px 10px', fontSize: '0.85em' },
337
+ onClick: async (e: Event) => {
338
+ const btn = e.target as HTMLButtonElement
339
+ const container = btn.parentElement as HTMLElement
340
+ const statusEl = container.querySelector(
341
+ '.status-text'
342
+ ) as HTMLElement
343
+ btn.disabled = true
344
+ btn.textContent = 'Checking...'
345
+
346
+ const url = (customUrlInput as HTMLInputElement).value
347
+ if (!url) {
348
+ statusEl.textContent = 'No URL configured'
349
+ statusEl.style.color = '#dc2626'
350
+ btn.disabled = false
351
+ btn.textContent = 'Check Status'
352
+ return
353
+ }
354
+
355
+ const isResponsive = await checkServerLoad(url)
356
+ const pending = getPendingRequests(url)
357
+
358
+ btn.disabled = false
359
+ btn.textContent = 'Check Status'
360
+
361
+ if (isResponsive) {
362
+ statusEl.textContent =
363
+ pending > 0 ? `Ready (${pending} pending)` : 'Ready'
364
+ statusEl.style.color = '#16a34a'
365
+ } else {
366
+ statusEl.textContent =
367
+ pending > 0
368
+ ? `Under load (${pending} pending)`
369
+ : 'Under load or unreachable'
370
+ statusEl.style.color = '#f59e0b'
371
+ }
372
+ },
373
+ },
374
+ 'Check Status'
375
+ ),
376
+ span({ class: 'status-text', style: { fontSize: '0.85em' } }, '')
377
+ )
378
+ ),
379
+
380
+ div(
381
+ { class: 'settings-field' },
382
+ label('OpenAI API Key'),
383
+ openaiInput,
384
+ div({ class: 'hint' }, 'For GPT-4, GPT-3.5, etc.')
385
+ ),
386
+
387
+ div(
388
+ { class: 'settings-field' },
389
+ label('Anthropic API Key'),
390
+ anthropicInput,
391
+ div({ class: 'hint' }, 'For Claude models')
392
+ ),
393
+
394
+ div(
395
+ { class: 'settings-field' },
396
+ label('Deepseek API Key'),
397
+ deepseekInput,
398
+ div({ class: 'hint' }, 'For Deepseek models (cheap & capable)')
399
+ )
400
+ ),
401
+
402
+ // Actions
403
+ div(
404
+ { class: 'settings-actions' },
405
+ button(
406
+ {
407
+ class: 'settings-btn secondary',
408
+ onClick: close,
409
+ },
410
+ 'Cancel'
411
+ ),
412
+ button(
413
+ {
414
+ class: 'settings-btn primary',
415
+ onClick: save,
416
+ },
417
+ 'Save'
418
+ )
419
+ )
420
+ )
421
+ )
422
+
423
+ // Close on overlay click
424
+ overlay.addEventListener('click', (e) => {
425
+ if (e.target === overlay) {
426
+ close()
427
+ }
428
+ })
429
+
430
+ // Close on Escape
431
+ const handleEscape = (e: KeyboardEvent) => {
432
+ if (e.key === 'Escape') {
433
+ close()
434
+ document.removeEventListener('keydown', handleEscape)
435
+ }
436
+ }
437
+ document.addEventListener('keydown', handleEscape)
438
+
439
+ document.body.append(overlay)
440
+ }
@@ -0,0 +1,280 @@
1
+ /*
2
+ * style.ts - CSS styles for agent-99 demo site
3
+ *
4
+ * Uses tosijs StyleSheet with CSS variables for theming.
5
+ */
6
+
7
+ import { vars } from 'tosijs'
8
+
9
+ // Brand colors
10
+ const brandColor = '#3d4a6b' // Dark muted blue
11
+ const brandTextColor = '#ffffff'
12
+
13
+ // Extend CSS variables
14
+ export const styleSpec = {
15
+ ':root': {
16
+ _brandColor: brandColor,
17
+ _brandTextColor: brandTextColor,
18
+ _headerHeight: '50px',
19
+ _sidebarWidth: '220px',
20
+ _spacing: '10px',
21
+ _spacing50: '5px',
22
+ _spacing200: '20px',
23
+ _borderRadius: '6px',
24
+ _fontMono: "'SF Mono', Monaco, 'Cascadia Code', Consolas, monospace",
25
+ _fontSize: '15px',
26
+ _lineHeight: '1.6',
27
+ _xinTabsSelectedColor: vars.brandColor,
28
+ // Light mode colors
29
+ _background: '#ffffff',
30
+ _textColor: '#1f2937',
31
+ _codeBackground: '#f3f4f6',
32
+ _codeBorder: '#e5e7eb',
33
+ _linkColor: brandColor,
34
+ },
35
+
36
+ // Dark mode - explicit colors for better control
37
+ '.darkmode': {
38
+ _background: '#111827',
39
+ _textColor: '#f3f4f6',
40
+ _codeBackground: '#1f2937',
41
+ _codeBorder: '#374151',
42
+ _linkColor: '#818cf8',
43
+ // xin-tabs uses these
44
+ _xinTabsBarColor: '#374151',
45
+ },
46
+
47
+ // High contrast mode - uses filter for comprehensive contrast boost
48
+ '.high-contrast': {
49
+ filter: 'contrast(1.4)',
50
+ },
51
+
52
+ // Base styles
53
+ 'html, body': {
54
+ margin: 0,
55
+ padding: 0,
56
+ height: '100%',
57
+ fontFamily:
58
+ "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
59
+ fontSize: vars.fontSize,
60
+ lineHeight: vars.lineHeight,
61
+ background: vars.background,
62
+ color: vars.textColor,
63
+ },
64
+
65
+ body: {
66
+ display: 'flex',
67
+ flexDirection: 'column',
68
+ },
69
+
70
+ main: {
71
+ flex: '1',
72
+ display: 'flex',
73
+ flexDirection: 'column',
74
+ overflow: 'hidden',
75
+ },
76
+
77
+ // Header
78
+ header: {
79
+ display: 'flex',
80
+ alignItems: 'center',
81
+ height: vars.headerHeight,
82
+ padding: `0 ${vars.spacing}`,
83
+ background: vars.brandColor,
84
+ color: vars.brandTextColor,
85
+ gap: vars.spacing,
86
+ flexShrink: 0,
87
+ },
88
+
89
+ 'header h1': {
90
+ margin: 0,
91
+ fontSize: '1.25em',
92
+ fontWeight: 600,
93
+ },
94
+
95
+ 'header a': {
96
+ color: vars.brandTextColor,
97
+ textDecoration: 'none',
98
+ display: 'flex',
99
+ alignItems: 'center',
100
+ gap: vars.spacing50,
101
+ },
102
+
103
+ 'header .elastic': {
104
+ flex: '1 1 auto',
105
+ },
106
+
107
+ // Icon buttons
108
+ '.iconic': {
109
+ background: 'none',
110
+ border: 'none',
111
+ cursor: 'pointer',
112
+ padding: vars.spacing50,
113
+ borderRadius: vars.borderRadius,
114
+ color: 'inherit',
115
+ display: 'flex',
116
+ alignItems: 'center',
117
+ justifyContent: 'center',
118
+ transition: 'background 0.15s',
119
+ },
120
+
121
+ '.iconic:hover': {
122
+ background: 'rgba(255, 255, 255, 0.15)',
123
+ },
124
+
125
+ // Links
126
+ a: {
127
+ color: vars.linkColor,
128
+ textDecoration: 'none',
129
+ },
130
+
131
+ 'a:hover': {
132
+ textDecoration: 'underline',
133
+ },
134
+
135
+ // Doc links in sidebar
136
+ '.doc-link': {
137
+ display: 'block',
138
+ padding: `${vars.spacing50} ${vars.spacing}`,
139
+ color: vars.textColor,
140
+ borderRadius: vars.borderRadius,
141
+ textDecoration: 'none',
142
+ transition: 'background 0.15s',
143
+ },
144
+
145
+ '.doc-link:hover': {
146
+ background: 'rgba(99, 102, 241, 0.1)',
147
+ textDecoration: 'none',
148
+ },
149
+
150
+ '.doc-link.current': {
151
+ background: vars.brandColor,
152
+ color: vars.brandTextColor,
153
+ },
154
+
155
+ // Code blocks (exclude CodeMirror)
156
+ 'pre:not(.cm-content), code:not(.cm-content code)': {
157
+ fontFamily: vars.fontMono,
158
+ fontSize: '0.9em',
159
+ },
160
+
161
+ 'pre:not(.cm-content)': {
162
+ background: vars.codeBackground,
163
+ border: `1px solid ${vars.codeBorder}`,
164
+ borderRadius: vars.borderRadius,
165
+ padding: vars.spacing,
166
+ overflow: 'auto',
167
+ },
168
+
169
+ // CodeMirror fixes
170
+ '.cm-editor': {
171
+ height: '100%',
172
+ },
173
+
174
+ 'code:not(pre code)': {
175
+ background: vars.codeBackground,
176
+ padding: '2px 6px',
177
+ borderRadius: '4px',
178
+ },
179
+
180
+ // Badges
181
+ '.badge': {
182
+ display: 'inline-flex',
183
+ alignItems: 'center',
184
+ gap: '4px',
185
+ },
186
+
187
+ '.badge img': {
188
+ height: '20px',
189
+ },
190
+
191
+ // Search input
192
+ 'input[type="search"]': {
193
+ width: '100%',
194
+ padding: vars.spacing50,
195
+ border: `1px solid ${vars.codeBorder}`,
196
+ borderRadius: vars.borderRadius,
197
+ background: vars.background,
198
+ color: vars.textColor,
199
+ fontSize: 'inherit',
200
+ },
201
+
202
+ 'input[type="search"]:focus': {
203
+ outline: 'none',
204
+ borderColor: vars.brandColor,
205
+ boxShadow: `0 0 0 2px rgba(99, 102, 241, 0.2)`,
206
+ },
207
+
208
+ // Markdown content styling
209
+ '.markdown-content': {
210
+ maxWidth: '48em',
211
+ margin: '0 auto',
212
+ padding: vars.spacing200,
213
+ },
214
+
215
+ '.markdown-content h1': {
216
+ fontSize: '2em',
217
+ marginTop: 0,
218
+ },
219
+
220
+ '.markdown-content h2': {
221
+ fontSize: '1.5em',
222
+ marginTop: '1.5em',
223
+ paddingBottom: '0.3em',
224
+ borderBottom: `1px solid ${vars.codeBorder}`,
225
+ },
226
+
227
+ '.markdown-content h3': {
228
+ fontSize: '1.25em',
229
+ marginTop: '1.25em',
230
+ },
231
+
232
+ // Table styles (global)
233
+ table: {
234
+ width: '100%',
235
+ borderCollapse: 'collapse',
236
+ marginBottom: '1em',
237
+ fontSize: '0.9em',
238
+ },
239
+
240
+ 'th, td': {
241
+ padding: '6px 10px',
242
+ borderBottom: `1px solid ${vars.codeBorder}`,
243
+ textAlign: 'left',
244
+ },
245
+
246
+ th: {
247
+ fontWeight: 600,
248
+ background: 'rgba(0, 0, 0, 0.03)',
249
+ },
250
+
251
+ '.darkmode th': {
252
+ background: 'rgba(255, 255, 255, 0.03)',
253
+ },
254
+
255
+ 'tr:nth-child(even)': {
256
+ background: 'rgba(0, 0, 0, 0.03)',
257
+ },
258
+
259
+ '.darkmode tr:nth-child(even)': {
260
+ background: 'rgba(255, 255, 255, 0.03)',
261
+ },
262
+
263
+ 'tr:hover': {
264
+ background: 'rgba(0, 0, 0, 0.06)',
265
+ },
266
+
267
+ '.darkmode tr:hover': {
268
+ background: 'rgba(255, 255, 255, 0.06)',
269
+ },
270
+
271
+ // Loading state
272
+ '.loading': {
273
+ display: 'flex',
274
+ alignItems: 'center',
275
+ justifyContent: 'center',
276
+ height: '100%',
277
+ fontSize: '1.2em',
278
+ opacity: 0.6,
279
+ },
280
+ }