@fjell/registry 4.4.7 → 4.4.9

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 (54) hide show
  1. package/README.md +81 -4
  2. package/dist/Registry.cjs +20 -1
  3. package/dist/Registry.js +20 -1
  4. package/dist/RegistryHub.cjs +17 -1
  5. package/dist/RegistryHub.js +17 -1
  6. package/dist/index.cjs +35 -0
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/types.d.ts +15 -0
  9. package/docs/README.md +74 -0
  10. package/docs/index.html +17 -0
  11. package/docs/memory-data/scaling-10-instances.json +206 -206
  12. package/docs/memory-data/scaling-100-instances.json +206 -206
  13. package/docs/memory-data/scaling-1000-instances.json +108 -108
  14. package/docs/memory-data/scaling-10000-instances.json +49 -49
  15. package/docs/memory-data/scaling-20-instances.json +208 -208
  16. package/docs/memory-data/scaling-200-instances.json +201 -201
  17. package/docs/memory-data/scaling-2000-instances.json +107 -107
  18. package/docs/memory-data/scaling-50-instances.json +206 -206
  19. package/docs/memory-data/scaling-500-instances.json +108 -108
  20. package/docs/memory-data/scaling-5000-instances.json +49 -49
  21. package/docs/memory-overhead.svg +17 -17
  22. package/docs/memory.md +111 -111
  23. package/docs/package.json +35 -0
  24. package/docs/public/README.md +623 -0
  25. package/docs/public/TIMING_NODE_OPTIMIZATION.md +207 -0
  26. package/docs/public/examples/coordinates-example.ts +253 -0
  27. package/docs/public/examples/multi-level-keys.ts +374 -0
  28. package/docs/public/examples/registry-hub-coordinates-example.ts +370 -0
  29. package/docs/public/examples/registry-hub-types.ts +437 -0
  30. package/docs/public/examples/simple-example.ts +250 -0
  31. package/docs/public/examples-README.md +222 -0
  32. package/docs/public/fjell-icon.svg +1 -0
  33. package/docs/public/icon.png +0 -0
  34. package/docs/public/icon2.png +0 -0
  35. package/docs/public/memory-overhead.svg +120 -0
  36. package/docs/public/memory.md +430 -0
  37. package/docs/public/pano.png +0 -0
  38. package/docs/public/pano2.png +0 -0
  39. package/docs/public/timing-range.svg +176 -0
  40. package/docs/public/timing.md +483 -0
  41. package/docs/src/App.css +1175 -0
  42. package/docs/src/App.test.tsx +50 -0
  43. package/docs/src/App.tsx +583 -0
  44. package/docs/src/index.css +40 -0
  45. package/docs/src/main.tsx +10 -0
  46. package/docs/src/test/setup.ts +1 -0
  47. package/docs/timing-range.svg +41 -41
  48. package/docs/timing.md +101 -101
  49. package/docs/tsconfig.node.json +13 -0
  50. package/docs/vitest.config.ts +14 -0
  51. package/examples/README.md +35 -0
  52. package/examples/coordinates-example.ts +253 -0
  53. package/examples/registry-hub-coordinates-example.ts +370 -0
  54. package/package.json +1 -1
@@ -0,0 +1,50 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
3
+ import App from './App'
4
+
5
+ // Mock fetch
6
+ const mockFetch = vi.fn()
7
+ global.fetch = mockFetch
8
+
9
+ describe('App', () => {
10
+ beforeEach(() => {
11
+ mockFetch.mockClear()
12
+ })
13
+
14
+ it('renders the main title', () => {
15
+ mockFetch.mockResolvedValueOnce({
16
+ text: () => Promise.resolve('# Test README\n\nThis is a test.')
17
+ })
18
+
19
+ render(<App />)
20
+
21
+ expect(screen.getByText('🏔️ Fjell Registry')).toBeInTheDocument()
22
+ })
23
+
24
+ it('renders the subtitle', () => {
25
+ mockFetch.mockResolvedValueOnce({
26
+ text: () => Promise.resolve('# Test README\n\nThis is a test.')
27
+ })
28
+
29
+ render(<App />)
30
+
31
+ expect(screen.getByText('Common Registry for Fjell - A powerful TypeScript registry system')).toBeInTheDocument()
32
+ })
33
+
34
+ it('shows loading state initially', () => {
35
+ mockFetch.mockImplementationOnce(() => new Promise(() => { })) // Never resolves
36
+
37
+ render(<App />)
38
+
39
+ expect(screen.getByText('Loading documentation...')).toBeInTheDocument()
40
+ })
41
+
42
+ it('shows error message when fetch fails', async () => {
43
+ mockFetch.mockRejectedValueOnce(new Error('Failed to fetch'))
44
+
45
+ render(<App />)
46
+
47
+ // Wait for the error state
48
+ await screen.findByText(/Error loading documentation/)
49
+ })
50
+ })
@@ -0,0 +1,583 @@
1
+ import React, { useEffect, useState } from 'react'
2
+ import ReactMarkdown from 'react-markdown'
3
+ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
4
+ import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
5
+ import remarkGfm from 'remark-gfm'
6
+ import './App.css'
7
+
8
+ interface DocumentSection {
9
+ id: string;
10
+ title: string;
11
+ subtitle: string;
12
+ file: string;
13
+ content?: string;
14
+ }
15
+
16
+ const documentSections: DocumentSection[] = [
17
+ { id: 'overview', title: 'Foundation', subtitle: 'Core concepts & philosophy', file: '/README.md' },
18
+ { id: 'getting-started', title: 'Getting Started', subtitle: 'Your first steps with Fjell', file: '/fjell-registry/examples-README.md' },
19
+ { id: 'examples', title: 'Examples', subtitle: 'Code examples & usage patterns', file: '/fjell-registry/examples-README.md' },
20
+ { id: 'performance', title: 'Performance', subtitle: 'Memory, timing & optimization', file: '/memory.md' }
21
+ ];
22
+
23
+ const App: React.FC = () => {
24
+ const [currentSection, setCurrentSection] = useState('overview')
25
+ const [documents, setDocuments] = useState<{ [key: string]: string }>({})
26
+ const [loading, setLoading] = useState(true)
27
+ const [sidebarOpen, setSidebarOpen] = useState(false)
28
+ const [fullscreenImage, setFullscreenImage] = useState<string | null>(null)
29
+
30
+ useEffect(() => {
31
+ const loadDocuments = async () => {
32
+ const loadedDocs: { [key: string]: string } = {}
33
+
34
+ for (const section of documentSections) {
35
+ try {
36
+ const response = await fetch(section.file)
37
+
38
+ if (response.ok) {
39
+ loadedDocs[section.id] = await response.text()
40
+ } else {
41
+ // Fallback content for missing files
42
+ loadedDocs[section.id] = getFallbackContent(section.id)
43
+ }
44
+ } catch (error) {
45
+ console.error(`Error loading ${section.file}:`, error)
46
+ loadedDocs[section.id] = getFallbackContent(section.id)
47
+ }
48
+ }
49
+
50
+ setDocuments(loadedDocs)
51
+ setLoading(false)
52
+ }
53
+
54
+ loadDocuments()
55
+ }, [])
56
+
57
+ const getFallbackContent = (sectionId: string): string => {
58
+ switch (sectionId) {
59
+ case 'overview':
60
+ return `# Fjell Registry
61
+
62
+ A comprehensive service location and registry system for the Fjell ecosystem.
63
+ The Registry provides a centralized way to register, scope, and retrieve service
64
+ instances based on type hierarchies and contextual scopes.
65
+
66
+ ## Installation
67
+
68
+ \`\`\`bash
69
+ npm install @fjell/registry
70
+ \`\`\`
71
+
72
+ ## Quick Start
73
+
74
+ \`\`\`typescript
75
+ import { createRegistry, createCoordinate } from '@fjell/registry'
76
+
77
+ // Create a registry with mandatory type identifier
78
+ const registry = createRegistry('services')
79
+
80
+ // Create a coordinate with Key Type Array and scopes
81
+ const userCoordinate = createCoordinate(['User'], ['firestore'])
82
+ const userInstance = registry.createInstance(userCoordinate, () => new UserService())
83
+ \`\`\`
84
+
85
+ ## Core Concepts
86
+
87
+ - **Registry**: Central service locator with mandatory type identifier
88
+ - **Coordinate**: Unique service identifier using Key Type Arrays and scopes
89
+ - **Instance**: Registered service with its coordinate and registry reference
90
+ - **Scopes**: Context qualifiers enabling multiple implementations`
91
+
92
+ case 'getting-started':
93
+ return `# Getting Started
94
+
95
+ ## Basic Usage
96
+
97
+ \`\`\`typescript
98
+ import { createRegistry, createCoordinate } from '@fjell/registry'
99
+
100
+ // Create a registry with type identifier
101
+ const registry = createRegistry('services')
102
+
103
+ // Register a service with coordinate
104
+ const coordinate = createCoordinate(['User'], ['firestore'])
105
+ const instance = registry.createInstance(coordinate, () => new UserService())
106
+ \`\`\`
107
+
108
+ ## Key Concepts
109
+
110
+ - **Coordinates**: Use Key Type Arrays like \`['User', 'Profile']\` for hierarchical types
111
+ - **Scopes**: Context qualifiers like \`['firestore']\`, \`['postgresql']\` for multiple implementations
112
+ - **Registries**: Each has a mandatory type identifier for organization
113
+
114
+ ## Examples
115
+
116
+ Check the examples directory for detailed usage patterns with different key structures and scope configurations.`
117
+
118
+ case 'examples':
119
+ return `# Registry Examples
120
+
121
+ This directory contains examples demonstrating how to use the Registry with different key patterns and configurations.
122
+
123
+ *Note: This content should be loaded from examples/README.md. If you're seeing this, there may be an issue with file loading.*
124
+
125
+ ## Examples Available
126
+
127
+ - **simple-example.ts**: Basic usage without complexity
128
+ - **multi-level-keys.ts**: Advanced hierarchical patterns
129
+ - **registry-hub-types.ts**: Enterprise service organization
130
+ - **coordinates-example.ts**: Service introspection patterns
131
+ - **registry-hub-coordinates-example.ts**: Cross-registry discovery`
132
+
133
+ case 'performance':
134
+ return `# Performance Guide
135
+
136
+ Comprehensive performance analysis and optimization strategies for Fjell Registry.
137
+
138
+ ## Memory Efficiency
139
+
140
+ The registry system is designed for memory efficiency with minimal overhead per registered instance and coordinate.
141
+
142
+ ### Key Features
143
+ - Lightweight coordinate system using arrays
144
+ - Efficient instance tree organization
145
+ - Minimal memory footprint per service registration
146
+
147
+ ### Memory Benchmarks
148
+ Performance metrics and memory usage patterns show efficient scaling with large numbers of registered services.
149
+
150
+ ## Timing Performance
151
+
152
+ Performance benchmarks for Fjell Registry service location operations.
153
+
154
+ ### Core Operations
155
+ - **Instance creation**: Fast atomic registration with coordinates
156
+ - **Registry lookup**: Efficient retrieval by Key Type Arrays and scopes
157
+ - **Coordinate resolution**: Quick scope-based service discovery
158
+ - **Tree traversal**: Optimized instance tree navigation
159
+
160
+ ### Timing Benchmarks
161
+ - Instance registration: < 1ms per service
162
+ - Coordinate-based lookup: < 0.1ms average
163
+ - Scope resolution: < 0.5ms for complex hierarchies
164
+ - Memory allocation: Minimal per registered instance
165
+
166
+ ## Production Optimization
167
+
168
+ Best practices for optimizing Fjell Registry performance in production environments.
169
+
170
+ ### Registry Organization
171
+ - Use specific type identifiers for different service categories
172
+ - Group related services in dedicated registries
173
+ - Leverage scopes for environment-specific implementations
174
+
175
+ ### Performance Tips
176
+ - Design efficient Key Type Array hierarchies
177
+ - Minimize scope complexity where possible
178
+ - Use appropriate registry types for service organization
179
+ - Consider coordinate caching for frequently accessed services
180
+
181
+ ### Production Settings
182
+ Recommended Node.js heap size settings and monitoring approaches for large-scale registry usage.`
183
+
184
+ default:
185
+ return `# ${sectionId}\n\nDocumentation section not found.`
186
+ }
187
+ }
188
+
189
+ const currentContent = documents[currentSection] || ''
190
+ const currentSectionData = documentSections.find(s => s.id === currentSection)
191
+
192
+ return (
193
+ <div className="app">
194
+ <header className="header">
195
+ <div className="header-container">
196
+ <div className="brand">
197
+ <h1 className="brand-title">
198
+ <span className="brand-fjell">Fjell</span>
199
+ <span className="brand-registry">Registry</span>
200
+ </h1>
201
+ <p className="brand-tagline">
202
+ Service location that
203
+ <span className="gradient-text"> weaves through the mist</span>
204
+ </p>
205
+ </div>
206
+
207
+ <div className="header-actions">
208
+ <a
209
+ href="https://github.com/getfjell/fjell-registry"
210
+ target="_blank"
211
+ rel="noopener noreferrer"
212
+ className="header-link"
213
+ >
214
+ <span>View Source</span>
215
+ </a>
216
+ <a
217
+ href="https://www.npmjs.com/package/@fjell/registry"
218
+ target="_blank"
219
+ rel="noopener noreferrer"
220
+ className="header-link"
221
+ >
222
+ <span>Install Package</span>
223
+ </a>
224
+ <button
225
+ className="menu-toggle"
226
+ onClick={() => setSidebarOpen(!sidebarOpen)}
227
+ >
228
+ <span className="menu-line"></span>
229
+ <span className="menu-line"></span>
230
+ <span className="menu-line"></span>
231
+ </button>
232
+ </div>
233
+ </div>
234
+ </header>
235
+
236
+ <div className="layout">
237
+ <nav className={`sidebar ${sidebarOpen ? 'sidebar-open' : ''}`}>
238
+ <div className="nav-content">
239
+ <div className="nav-header">
240
+ <h3 className="nav-title">Explore</h3>
241
+ <p className="nav-subtitle">Navigate through concepts</p>
242
+ </div>
243
+
244
+ <div className="nav-sections">
245
+ {documentSections.map((section) => (
246
+ <button
247
+ key={section.id}
248
+ className={`nav-item ${currentSection === section.id ? 'active' : ''}`}
249
+ onClick={() => {
250
+ setCurrentSection(section.id)
251
+ setSidebarOpen(false)
252
+ }}
253
+ >
254
+ <div className="nav-item-content">
255
+ <div className="nav-item-title">{section.title}</div>
256
+ <div className="nav-item-subtitle">{section.subtitle}</div>
257
+ </div>
258
+ </button>
259
+ ))}
260
+ </div>
261
+
262
+ {/* Artistic Logo Placement */}
263
+ <img
264
+ src="/fjell-registry/icon.png"
265
+ alt="Fjell Registry"
266
+ className="fjell-logo"
267
+ title="Fjell Registry - Service location that weaves through the mist"
268
+ onError={(e) => {
269
+ console.log('Icon failed to load, trying alternative path');
270
+ e.currentTarget.src = '/icon.png';
271
+ }}
272
+ onLoad={() => console.log('Fjell logo loaded successfully')}
273
+ />
274
+ </div>
275
+ </nav>
276
+
277
+ <main className="main">
278
+ <div className="content-container">
279
+ {loading ? (
280
+ <div className="loading">
281
+ <div className="loading-animation">
282
+ <div className="loading-dot"></div>
283
+ <div className="loading-dot"></div>
284
+ <div className="loading-dot"></div>
285
+ </div>
286
+ <p className="loading-text">Awakening the Registry</p>
287
+ </div>
288
+ ) : (
289
+ <div className="content-wrapper">
290
+ <div className="content-header">
291
+ <div className="breadcrumb">
292
+ <span className="breadcrumb-home">Fjell Registry</span>
293
+ <span className="breadcrumb-separator">›</span>
294
+ <span className="breadcrumb-current">{currentSectionData?.title}</span>
295
+ </div>
296
+ </div>
297
+
298
+ <div className="section-header">
299
+ <h1 className="content-title">
300
+ {currentSectionData?.title}
301
+ </h1>
302
+ <p className="content-subtitle">
303
+ {currentSectionData?.subtitle}
304
+ </p>
305
+ </div>
306
+
307
+ <div className="content">
308
+ {currentSection === 'examples' ? (
309
+ <div className="examples-content">
310
+ <ReactMarkdown
311
+ remarkPlugins={[remarkGfm]}
312
+ components={{
313
+ code({ className, children, ...props }: any) {
314
+ const match = /language-(\w+)/.exec(className || '')
315
+ return !props.inline && match ? (
316
+ <SyntaxHighlighter
317
+ style={oneDark as { [key: string]: React.CSSProperties }}
318
+ language={match[1]}
319
+ PreTag="div"
320
+ {...props}
321
+ >
322
+ {String(children).replace(/\n$/, '')}
323
+ </SyntaxHighlighter>
324
+ ) : (
325
+ <code className={className} {...props}>
326
+ {children}
327
+ </code>
328
+ )
329
+ },
330
+ h1({ children }) {
331
+ return <h1 className="content-h1">{children}</h1>
332
+ },
333
+ h2({ children }) {
334
+ return <h2 className="content-h2">{children}</h2>
335
+ },
336
+ h3({ children }) {
337
+ return <h3 className="content-h3">{children}</h3>
338
+ }
339
+ }}
340
+ >
341
+ {currentContent}
342
+ </ReactMarkdown>
343
+
344
+ <div className="examples-grid">
345
+ <div className="example-card">
346
+ <h3>🟢 Simple Example</h3>
347
+ <p>Perfect for beginners! Basic dependency injection without complexity.</p>
348
+ <details>
349
+ <summary>View Code</summary>
350
+ <div className="example-code-block">
351
+ <SyntaxHighlighter
352
+ style={oneDark as { [key: string]: React.CSSProperties }}
353
+ language="bash"
354
+ PreTag="div"
355
+ >
356
+ npx tsx examples/simple-example.ts
357
+ </SyntaxHighlighter>
358
+ </div>
359
+ </details>
360
+ </div>
361
+
362
+ <div className="example-card">
363
+ <h3>🔶 Multi-Level Keys</h3>
364
+ <p>Advanced usage with scopes and hierarchical key type arrays.</p>
365
+ <details>
366
+ <summary>View Code</summary>
367
+ <div className="example-code-block">
368
+ <SyntaxHighlighter
369
+ style={oneDark as { [key: string]: React.CSSProperties }}
370
+ language="bash"
371
+ PreTag="div"
372
+ >
373
+ npx tsx examples/multi-level-keys.ts
374
+ </SyntaxHighlighter>
375
+ </div>
376
+ </details>
377
+ </div>
378
+
379
+ <div className="example-card">
380
+ <h3>🏗️ Registry Hub</h3>
381
+ <p>Enterprise architecture with organized service categories.</p>
382
+ <details>
383
+ <summary>View Code</summary>
384
+ <div className="example-code-block">
385
+ <SyntaxHighlighter
386
+ style={oneDark as { [key: string]: React.CSSProperties }}
387
+ language="bash"
388
+ PreTag="div"
389
+ >
390
+ npx tsx examples/registry-hub-types.ts
391
+ </SyntaxHighlighter>
392
+ </div>
393
+ </details>
394
+ </div>
395
+
396
+ <div className="example-card">
397
+ <h3>🔍 Coordinate Discovery</h3>
398
+ <p>Service introspection and discovery patterns.</p>
399
+ <details>
400
+ <summary>View Code</summary>
401
+ <div className="example-code-block">
402
+ <SyntaxHighlighter
403
+ style={oneDark as { [key: string]: React.CSSProperties }}
404
+ language="bash"
405
+ PreTag="div"
406
+ >
407
+ npx tsx examples/coordinates-example.ts
408
+ </SyntaxHighlighter>
409
+ </div>
410
+ </details>
411
+ </div>
412
+
413
+ <div className="example-card">
414
+ <h3>🌐 Hub Coordinates</h3>
415
+ <p>Cross-registry coordinate discovery for enterprise apps.</p>
416
+ <details>
417
+ <summary>View Code</summary>
418
+ <div className="example-code-block">
419
+ <SyntaxHighlighter
420
+ style={oneDark as { [key: string]: React.CSSProperties }}
421
+ language="bash"
422
+ PreTag="div"
423
+ >
424
+ npx tsx examples/registry-hub-coordinates-example.ts
425
+ </SyntaxHighlighter>
426
+ </div>
427
+ </details>
428
+ </div>
429
+ </div>
430
+ </div>
431
+ ) : currentSection === 'performance' ? (
432
+ <div className="performance-content">
433
+ <ReactMarkdown
434
+ remarkPlugins={[remarkGfm]}
435
+ components={{
436
+ code({ className, children, ...props }: any) {
437
+ const match = /language-(\w+)/.exec(className || '')
438
+ return !props.inline && match ? (
439
+ <SyntaxHighlighter
440
+ style={oneDark as { [key: string]: React.CSSProperties }}
441
+ language={match[1]}
442
+ PreTag="div"
443
+ {...props}
444
+ >
445
+ {String(children).replace(/\n$/, '')}
446
+ </SyntaxHighlighter>
447
+ ) : (
448
+ <code className={className} {...props}>
449
+ {children}
450
+ </code>
451
+ )
452
+ },
453
+ h1({ children }) {
454
+ return <h1 className="content-h1">{children}</h1>
455
+ },
456
+ h2({ children }) {
457
+ return <h2 className="content-h2">{children}</h2>
458
+ },
459
+ h3({ children }) {
460
+ return <h3 className="content-h3">{children}</h3>
461
+ }
462
+ }}
463
+ >
464
+ {currentContent}
465
+ </ReactMarkdown>
466
+ <div className="performance-charts">
467
+ <div className="svg-display">
468
+ <h3>Memory Overhead Analysis</h3>
469
+ <img
470
+ src="/fjell-registry/memory-overhead.svg"
471
+ alt="Memory Overhead Chart"
472
+ className="performance-chart clickable-chart"
473
+ onClick={() => setFullscreenImage('/fjell-registry/memory-overhead.svg')}
474
+ title="Click to view full screen"
475
+ />
476
+ </div>
477
+ <div className="svg-display">
478
+ <h3>Timing Performance Analysis</h3>
479
+ <img
480
+ src="/fjell-registry/timing-range.svg"
481
+ alt="Timing Performance Chart"
482
+ className="performance-chart clickable-chart"
483
+ onClick={() => setFullscreenImage('/fjell-registry/timing-range.svg')}
484
+ title="Click to view full screen"
485
+ />
486
+ </div>
487
+ </div>
488
+ </div>
489
+ ) : (
490
+ <ReactMarkdown
491
+ remarkPlugins={[remarkGfm]}
492
+ components={{
493
+ code({ className, children, ...props }: any) {
494
+ const match = /language-(\w+)/.exec(className || '')
495
+ return !props.inline && match ? (
496
+ <SyntaxHighlighter
497
+ style={oneDark as { [key: string]: React.CSSProperties }}
498
+ language={match[1]}
499
+ PreTag="div"
500
+ {...props}
501
+ >
502
+ {String(children).replace(/\n$/, '')}
503
+ </SyntaxHighlighter>
504
+ ) : (
505
+ <code className={className} {...props}>
506
+ {children}
507
+ </code>
508
+ )
509
+ },
510
+ h1({ children }) {
511
+ return <h1 className="content-h1">{children}</h1>
512
+ },
513
+ h2({ children }) {
514
+ return <h2 className="content-h2">{children}</h2>
515
+ },
516
+ h3({ children }) {
517
+ return <h3 className="content-h3">{children}</h3>
518
+ }
519
+ }}
520
+ >
521
+ {currentContent}
522
+ </ReactMarkdown>
523
+ )}
524
+ </div>
525
+
526
+ <div className="content-navigation">
527
+ {documentSections.map((section) => {
528
+ if (section.id === currentSection) return null
529
+ return (
530
+ <button
531
+ key={section.id}
532
+ className="nav-suggestion"
533
+ onClick={() => setCurrentSection(section.id)}
534
+ >
535
+ <span className="nav-suggestion-label">Next</span>
536
+ <span className="nav-suggestion-title">{section.title}</span>
537
+ </button>
538
+ )
539
+ }).filter(Boolean).slice(0, 1)}
540
+ </div>
541
+ </div>
542
+ )}
543
+ </div>
544
+ </main>
545
+ </div>
546
+
547
+ <footer className="footer">
548
+ <div className="footer-container">
549
+ <div className="footer-content">
550
+ <p className="footer-text">
551
+ Crafted with intention for the Fjell ecosystem
552
+ </p>
553
+ <p className="footer-license">
554
+ Licensed under Apache-2.0 &nbsp;•&nbsp; 2024
555
+ </p>
556
+ </div>
557
+ </div>
558
+ </footer>
559
+
560
+ {/* Fullscreen Image Modal */}
561
+ {fullscreenImage && (
562
+ <div className="fullscreen-modal" onClick={() => setFullscreenImage(null)}>
563
+ <div className="fullscreen-content">
564
+ <img
565
+ src={fullscreenImage}
566
+ alt="Performance Chart"
567
+ className="fullscreen-image"
568
+ />
569
+ <button
570
+ className="close-button"
571
+ onClick={() => setFullscreenImage(null)}
572
+ aria-label="Close fullscreen view"
573
+ >
574
+ ×
575
+ </button>
576
+ </div>
577
+ </div>
578
+ )}
579
+ </div>
580
+ )
581
+ }
582
+
583
+ export default App
@@ -0,0 +1,40 @@
1
+ :root {
2
+ --fjell-blue: #2563eb;
3
+ --fjell-blue-light: #3b82f6;
4
+ --fjell-gray: #374151;
5
+ --fjell-gray-light: #6b7280;
6
+ --fjell-background: #f9fafb;
7
+ --fjell-border: #e5e7eb;
8
+ }
9
+
10
+ * {
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ margin: 0;
16
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
17
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
18
+ sans-serif;
19
+ -webkit-font-smoothing: antialiased;
20
+ -moz-osx-font-smoothing: grayscale;
21
+ color: var(--fjell-gray);
22
+ background-color: var(--fjell-background);
23
+ line-height: 1.6;
24
+ }
25
+
26
+ code {
27
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
28
+ background-color: #f1f5f9;
29
+ padding: 0.125rem 0.25rem;
30
+ border-radius: 0.25rem;
31
+ font-size: 0.875em;
32
+ }
33
+
34
+ pre code {
35
+ display: block;
36
+ padding: 1rem;
37
+ overflow-x: auto;
38
+ border-radius: 0.5rem;
39
+ background-color: #1e293b !important;
40
+ }
@@ -0,0 +1,10 @@
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom/client'
3
+ import App from './App.tsx'
4
+ import './index.css'
5
+
6
+ ReactDOM.createRoot(document.getElementById('root')!).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>,
10
+ )
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom'