petter2025 commited on
Commit
ea68e5a
·
verified ·
1 Parent(s): 1f3a817

Create modern_components.py

Browse files
Files changed (1) hide show
  1. ui/modern_components.py +1884 -0
ui/modern_components.py ADDED
@@ -0,0 +1,1884 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ui/modern_components.py
3
+ 🚀 Modern UI Components for ARF v3.3.9
4
+ Enhanced with design system, accessibility, and performance features
5
+
6
+ Features:
7
+ 1. Design token system with CSS variables
8
+ 2. Responsive, mobile-first components
9
+ 3. Accessibility (ARIA labels, keyboard nav)
10
+ 4. Performance optimizations
11
+ 5. Dark mode support
12
+ 6. Progressive enhancement
13
+ """
14
+
15
+ import json
16
+ from datetime import datetime
17
+ from typing import Dict, List, Any, Optional, Tuple
18
+ import plotly.graph_objects as go
19
+ import plotly.express as px
20
+ import numpy as np
21
+ import pandas as pd
22
+
23
+ # ===========================================
24
+ # DESIGN TOKEN SYSTEM (CSS Variables in JavaScript)
25
+ # ===========================================
26
+ DESIGN_TOKENS = {
27
+ "colors": {
28
+ "primary": {
29
+ "50": "#eff6ff",
30
+ "100": "#dbeafe",
31
+ "200": "#bfdbfe",
32
+ "300": "#93c5fd",
33
+ "400": "#60a5fa",
34
+ "500": "#3b82f6",
35
+ "600": "#2563eb",
36
+ "700": "#1d4ed8",
37
+ "800": "#1e40af",
38
+ "900": "#1e3a8a"
39
+ },
40
+ "success": {
41
+ "50": "#f0fdf4",
42
+ "100": "#dcfce7",
43
+ "200": "#bbf7d0",
44
+ "300": "#86efac",
45
+ "400": "#4ade80",
46
+ "500": "#22c55e",
47
+ "600": "#16a34a",
48
+ "700": "#15803d",
49
+ "800": "#166534",
50
+ "900": "#14532d"
51
+ },
52
+ "warning": {
53
+ "50": "#fffbeb",
54
+ "100": "#fef3c7",
55
+ "200": "#fde68a",
56
+ "300": "#fcd34d",
57
+ "400": "#fbbf24",
58
+ "500": "#f59e0b",
59
+ "600": "#d97706",
60
+ "700": "#b45309",
61
+ "800": "#92400e",
62
+ "900": "#78350f"
63
+ },
64
+ "danger": {
65
+ "50": "#fef2f2",
66
+ "100": "#fee2e2",
67
+ "200": "#fecaca",
68
+ "300": "#fca5a5",
69
+ "400": "#f87171",
70
+ "500": "#ef4444",
71
+ "600": "#dc2626",
72
+ "700": "#b91c1c",
73
+ "800": "#991b1b",
74
+ "900": "#7f1d1d"
75
+ },
76
+ "neutral": {
77
+ "50": "#f8fafc",
78
+ "100": "#f1f5f9",
79
+ "200": "#e2e8f0",
80
+ "300": "#cbd5e1",
81
+ "400": "#94a3b8",
82
+ "500": "#64748b",
83
+ "600": "#475569",
84
+ "700": "#334155",
85
+ "800": "#1e293b",
86
+ "900": "#0f172a"
87
+ }
88
+ },
89
+ "spacing": {
90
+ "1": "0.25rem",
91
+ "2": "0.5rem",
92
+ "3": "0.75rem",
93
+ "4": "1rem",
94
+ "5": "1.25rem",
95
+ "6": "1.5rem",
96
+ "8": "2rem",
97
+ "10": "2.5rem",
98
+ "12": "3rem",
99
+ "16": "4rem",
100
+ "20": "5rem"
101
+ },
102
+ "typography": {
103
+ "fontSizes": {
104
+ "xs": "0.75rem",
105
+ "sm": "0.875rem",
106
+ "base": "1rem",
107
+ "lg": "1.125rem",
108
+ "xl": "1.25rem",
109
+ "2xl": "1.5rem",
110
+ "3xl": "1.875rem",
111
+ "4xl": "2.25rem"
112
+ },
113
+ "fontWeights": {
114
+ "light": "300",
115
+ "normal": "400",
116
+ "medium": "500",
117
+ "semibold": "600",
118
+ "bold": "700",
119
+ "extrabold": "800"
120
+ }
121
+ },
122
+ "breakpoints": {
123
+ "sm": "640px",
124
+ "md": "768px",
125
+ "lg": "1024px",
126
+ "xl": "1280px",
127
+ "2xl": "1536px"
128
+ },
129
+ "shadows": {
130
+ "sm": "0 1px 2px 0 rgb(0 0 0 / 0.05)",
131
+ "md": "0 4px 6px -1px rgb(0 0 0 / 0.1)",
132
+ "lg": "0 10px 15px -3px rgb(0 0 0 / 0.1)",
133
+ "xl": "0 20px 25px -5px rgb(0 0 0 / 0.1)"
134
+ },
135
+ "borderRadius": {
136
+ "sm": "0.25rem",
137
+ "md": "0.375rem",
138
+ "lg": "0.5rem",
139
+ "xl": "0.75rem",
140
+ "2xl": "1rem",
141
+ "full": "9999px"
142
+ }
143
+ }
144
+
145
+ # ===========================================
146
+ # CSS VARIABLES INJECTION
147
+ # ===========================================
148
+ def inject_design_tokens() -> str:
149
+ """Inject CSS variables for design tokens into the page"""
150
+ css_variables = []
151
+
152
+ # Convert design tokens to CSS variables
153
+ for category, values in DESIGN_TOKENS.items():
154
+ if isinstance(values, dict):
155
+ for key, value in values.items():
156
+ if isinstance(value, dict):
157
+ for subkey, subvalue in value.items():
158
+ css_variables.append(f"--{category}-{key}-{subkey}: {subvalue};")
159
+ else:
160
+ css_variables.append(f"--{category}-{key}: {value};")
161
+
162
+ return f"""
163
+ <style id="design-tokens">
164
+ :root {{
165
+ {chr(10).join(css_variables)}
166
+
167
+ /* Semantic aliases */
168
+ --color-primary: var(--colors-primary-500);
169
+ --color-success: var(--colors-success-500);
170
+ --color-warning: var(--colors-warning-500);
171
+ --color-danger: var(--colors-danger-500);
172
+
173
+ /* Spacing aliases */
174
+ --spacing-xs: var(--spacing-2);
175
+ --spacing-sm: var(--spacing-3);
176
+ --spacing-md: var(--spacing-4);
177
+ --spacing-lg: var(--spacing-6);
178
+ --spacing-xl: var(--spacing-8);
179
+
180
+ /* Font families */
181
+ --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
182
+ --font-mono: 'JetBrains Mono', 'SF Mono', Monaco, 'Cascadia Code', monospace;
183
+
184
+ /* Transitions */
185
+ --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
186
+ --transition-normal: 250ms cubic-bezier(0.4, 0, 0.2, 1);
187
+ --transition-slow: 350ms cubic-bezier(0.4, 0, 0.2, 1);
188
+ }}
189
+
190
+ /* Dark mode overrides */
191
+ [data-theme="dark"] {{
192
+ --colors-neutral-50: #0f172a;
193
+ --colors-neutral-100: #1e293b;
194
+ --colors-neutral-200: #334155;
195
+ --colors-neutral-300: #475569;
196
+ --colors-neutral-400: #64748b;
197
+ --colors-neutral-500: #94a3b8;
198
+ --colors-neutral-600: #cbd5e1;
199
+ --colors-neutral-700: #e2e8f0;
200
+ --colors-neutral-800: #f1f5f9;
201
+ --colors-neutral-900: #f8fafc;
202
+ }}
203
+
204
+ /* Utility classes */
205
+ .sr-only {{
206
+ position: absolute;
207
+ width: 1px;
208
+ height: 1px;
209
+ padding: 0;
210
+ margin: -1px;
211
+ overflow: hidden;
212
+ clip: rect(0, 0, 0, 0);
213
+ white-space: nowrap;
214
+ border: 0;
215
+ }}
216
+
217
+ .focus-ring {{
218
+ outline: 2px solid var(--color-primary);
219
+ outline-offset: 2px;
220
+ }}
221
+
222
+ .focus-ring:focus:not(:focus-visible) {{
223
+ outline: none;
224
+ }}
225
+
226
+ @media (prefers-reduced-motion: reduce) {{
227
+ *,
228
+ *::before,
229
+ *::after {{
230
+ animation-duration: 0.01ms !important;
231
+ animation-iteration-count: 1 !important;
232
+ transition-duration: 0.01ms !important;
233
+ }}
234
+ }}
235
+ </style>
236
+ """
237
+
238
+ # ===========================================
239
+ # BASE COMPONENT CLASS
240
+ # ===========================================
241
+ class ModernComponent:
242
+ """Base class for all modern components with consistent styling"""
243
+
244
+ def __init__(self):
245
+ self.component_id = f"component_{datetime.now().timestamp()}"
246
+
247
+ @staticmethod
248
+ def create_container(children: str, **kwargs) -> str:
249
+ """Create a responsive container"""
250
+ classes = kwargs.get('classes', '')
251
+ style = kwargs.get('style', '')
252
+
253
+ return f"""
254
+ <div class="container {classes}" style="{style}">
255
+ {children}
256
+ </div>
257
+ """
258
+
259
+ @staticmethod
260
+ def create_section(title: str = "", children: str = "", **kwargs) -> str:
261
+ """Create a semantic section with optional title"""
262
+ section_id = kwargs.get('id', '')
263
+ aria_label = kwargs.get('aria_label', title)
264
+
265
+ title_html = f'<h2 class="section-title">{title}</h2>' if title else ''
266
+
267
+ return f"""
268
+ <section id="{section_id}" class="section" aria-label="{aria_label}">
269
+ {title_html}
270
+ <div class="section-content">
271
+ {children}
272
+ </div>
273
+ </section>
274
+ """
275
+
276
+ # ===========================================
277
+ # ATOMIC COMPONENTS
278
+ # ===========================================
279
+ class Card(ModernComponent):
280
+ """Card component with multiple variants"""
281
+
282
+ @staticmethod
283
+ def create(content: str, **kwargs) -> str:
284
+ """Create a card with optional header and footer"""
285
+ title = kwargs.get('title', '')
286
+ footer = kwargs.get('footer', '')
287
+ variant = kwargs.get('variant', 'default') # default, elevated, outlined, filled
288
+ border_color = kwargs.get('border_color', 'var(--colors-neutral-200)')
289
+
290
+ variant_classes = {
291
+ 'default': 'card-default',
292
+ 'elevated': 'card-elevated',
293
+ 'outlined': 'card-outlined',
294
+ 'filled': 'card-filled'
295
+ }
296
+
297
+ header_html = f"""
298
+ <div class="card-header">
299
+ <h3 class="card-title">{title}</h3>
300
+ </div>
301
+ """ if title else ''
302
+
303
+ footer_html = f"""
304
+ <div class="card-footer">
305
+ {footer}
306
+ </div>
307
+ """ if footer else ''
308
+
309
+ return f"""
310
+ <div class="card {variant_classes.get(variant, 'card-default')}"
311
+ style="--border-color: {border_color};">
312
+ {header_html}
313
+ <div class="card-body">
314
+ {content}
315
+ </div>
316
+ {footer_html}
317
+ </div>
318
+ """
319
+
320
+ @staticmethod
321
+ def create_metric(value: str, label: str, **kwargs) -> str:
322
+ """Create a metric card for KPIs"""
323
+ trend = kwargs.get('trend', None) # 'up', 'down', 'neutral'
324
+ change = kwargs.get('change', '')
325
+
326
+ trend_icon = {
327
+ 'up': '↗',
328
+ 'down': '↘',
329
+ 'neutral': '→'
330
+ }.get(trend, '')
331
+
332
+ trend_color = {
333
+ 'up': 'var(--color-success)',
334
+ 'down': 'var(--color-danger)',
335
+ 'neutral': 'var(--colors-neutral-500)'
336
+ }.get(trend, 'var(--colors-neutral-500)')
337
+
338
+ return f"""
339
+ <div class="card card-metric" role="status" aria-label="{label}: {value}">
340
+ <div class="metric-value" aria-live="polite">{value}</div>
341
+ <div class="metric-label">{label}</div>
342
+ {f'<div class="metric-trend" style="color: {trend_color};">{trend_icon} {change}</div>' if trend else ''}
343
+ </div>
344
+ """
345
+
346
+ class Button(ModernComponent):
347
+ """Accessible button component with variants"""
348
+
349
+ @staticmethod
350
+ def create(text: str, **kwargs) -> str:
351
+ """Create a styled button"""
352
+ variant = kwargs.get('variant', 'primary') # primary, secondary, danger, ghost
353
+ size = kwargs.get('size', 'md') # sm, md, lg
354
+ disabled = kwargs.get('disabled', False)
355
+ full_width = kwargs.get('full_width', False)
356
+
357
+ variant_classes = {
358
+ 'primary': 'btn-primary',
359
+ 'secondary': 'btn-secondary',
360
+ 'danger': 'btn-danger',
361
+ 'ghost': 'btn-ghost'
362
+ }
363
+
364
+ size_classes = {
365
+ 'sm': 'btn-sm',
366
+ 'md': 'btn-md',
367
+ 'lg': 'btn-lg'
368
+ }
369
+
370
+ classes = [
371
+ 'btn',
372
+ variant_classes.get(variant, 'btn-primary'),
373
+ size_classes.get(size, 'btn-md'),
374
+ 'full-width' if full_width else ''
375
+ ]
376
+
377
+ disabled_attr = 'disabled aria-disabled="true"' if disabled else ''
378
+
379
+ return f"""
380
+ <button class="{' '.join(classes)}" {disabled_attr}>
381
+ {text}
382
+ </button>
383
+ """
384
+
385
+ @staticmethod
386
+ def create_icon_button(icon: str, label: str, **kwargs) -> str:
387
+ """Create an icon-only button with proper accessibility"""
388
+ return f"""
389
+ <button class="btn-icon" aria-label="{label}">
390
+ <span class="btn-icon-inner">{icon}</span>
391
+ </button>
392
+ """
393
+
394
+ class Badge(ModernComponent):
395
+ """Badge component for status, tags, etc."""
396
+
397
+ @staticmethod
398
+ def create(text: str, **kwargs) -> str:
399
+ """Create a badge"""
400
+ variant = kwargs.get('variant', 'default') # default, success, warning, danger
401
+ size = kwargs.get('size', 'md') # sm, md, lg
402
+
403
+ variant_classes = {
404
+ 'default': 'badge-default',
405
+ 'success': 'badge-success',
406
+ 'warning': 'badge-warning',
407
+ 'danger': 'badge-danger'
408
+ }
409
+
410
+ size_classes = {
411
+ 'sm': 'badge-sm',
412
+ 'md': 'badge-md',
413
+ 'lg': 'badge-lg'
414
+ }
415
+
416
+ return f"""
417
+ <span class="badge {variant_classes.get(variant, 'badge-default')} {size_classes.get(size, 'badge-md')}">
418
+ {text}
419
+ </span>
420
+ """
421
+
422
+ # ===========================================
423
+ # LAYOUT COMPONENTS
424
+ # ===========================================
425
+ class Grid(ModernComponent):
426
+ """Responsive grid system"""
427
+
428
+ @staticmethod
429
+ def create(children: List[str], **kwargs) -> str:
430
+ """Create a responsive grid"""
431
+ columns = kwargs.get('columns', { # responsive column definitions
432
+ 'default': 1,
433
+ 'sm': 2,
434
+ 'lg': 3
435
+ })
436
+
437
+ gap = kwargs.get('gap', 'var(--spacing-4)')
438
+
439
+ # Convert columns dict to CSS
440
+ grid_template = []
441
+ for breakpoint, col_count in columns.items():
442
+ if breakpoint == 'default':
443
+ grid_template.append(f"grid-template-columns: repeat({col_count}, 1fr);")
444
+ else:
445
+ grid_template.append(f"@media (min-width: {DESIGN_TOKENS['breakpoints'][breakpoint]}) {{")
446
+ grid_template.append(f" grid-template-columns: repeat({col_count}, 1fr);")
447
+ grid_template.append("}")
448
+
449
+ return f"""
450
+ <div class="grid" style="gap: {gap};">
451
+ {''.join(children)}
452
+ </div>
453
+
454
+ <style>
455
+ .grid {{
456
+ display: grid;
457
+ {grid_template[0] if grid_template else ''}
458
+ }}
459
+
460
+ {''.join(grid_template[1:]) if len(grid_template) > 1 else ''}
461
+ </style>
462
+ """
463
+
464
+ @staticmethod
465
+ def create_metric_grid(metrics: List[Dict[str, Any]]) -> str:
466
+ """Create a grid of metric cards"""
467
+ metric_cards = []
468
+ for metric in metrics:
469
+ metric_cards.append(
470
+ Card.create_metric(
471
+ value=metric.get('value', ''),
472
+ label=metric.get('label', ''),
473
+ trend=metric.get('trend'),
474
+ change=metric.get('change', '')
475
+ )
476
+ )
477
+
478
+ return Grid.create(
479
+ children=metric_cards,
480
+ columns={'default': 2, 'sm': 2, 'lg': 4}
481
+ )
482
+
483
+ class Stack(ModernComponent):
484
+ """Vertical stack layout"""
485
+
486
+ @staticmethod
487
+ def create(children: List[str], **kwargs) -> str:
488
+ """Create a vertical stack"""
489
+ spacing = kwargs.get('spacing', 'var(--spacing-4)')
490
+ align = kwargs.get('align', 'stretch') # start, center, end, stretch
491
+
492
+ return f"""
493
+ <div class="stack" style="--spacing: {spacing}; --align: {align};">
494
+ {''.join([f'<div class="stack-item">{child}</div>' for child in children])}
495
+ </div>
496
+
497
+ <style>
498
+ .stack {{
499
+ display: flex;
500
+ flex-direction: column;
501
+ align-items: var(--align);
502
+ gap: var(--spacing);
503
+ }}
504
+
505
+ .stack-item {{
506
+ width: 100%;
507
+ }}
508
+ </style>
509
+ """
510
+
511
+ # ===========================================
512
+ # SPECIALIZED ARF COMPONENTS
513
+ # ===========================================
514
+ class ObservationGate(ModernComponent):
515
+ """Observation gate component showing system restraint"""
516
+
517
+ @staticmethod
518
+ def create(confidence: float = 65.0, **kwargs) -> str:
519
+ """Create observation gate display"""
520
+ reason = kwargs.get('reason', 'uncertainty_too_high_for_action')
521
+ frozen_until = kwargs.get('frozen_until', '')
522
+
523
+ threshold = 70.0
524
+ is_blocked = confidence < threshold
525
+
526
+ # Format countdown if available
527
+ countdown_html = ""
528
+ if frozen_until:
529
+ countdown_html = f"""
530
+ <div class="countdown">
531
+ <span class="countdown-label">Next evaluation:</span>
532
+ <span class="countdown-timer" data-until="{frozen_until}">5:00</span>
533
+ </div>
534
+ """
535
+
536
+ status_text = "Observation Gate: Awaiting confirmation" if is_blocked else "Observation Gate Cleared"
537
+ status_color = "var(--color-warning)" if is_blocked else "var(--color-success)"
538
+
539
+ return f"""
540
+ <div class="observation-gate" style="--status-color: {status_color};">
541
+ <div class="observation-gate-header">
542
+ <div class="observation-icon">⏳</div>
543
+ <div class="observation-title">
544
+ <h3>{status_text}</h3>
545
+ <p>System restraint engaged</p>
546
+ </div>
547
+ <div class="observation-badge">
548
+ ACTIVE RESTRAINT
549
+ </div>
550
+ </div>
551
+
552
+ <div class="observation-content">
553
+ <div class="observation-message">
554
+ <h4>Decision Intentionally Deferred</h4>
555
+ <p>The system has detected uncertainty (<strong>{confidence:.1f}% confidence</strong>)
556
+ and has chosen to observe rather than act. Historical evidence indicates
557
+ premature action increases risk by <strong>47%</strong>.</p>
558
+ </div>
559
+
560
+ <div class="confidence-comparison">
561
+ <div class="confidence-item">
562
+ <div class="confidence-label">Confidence Threshold</div>
563
+ <div class="confidence-value">{threshold}%</div>
564
+ <div class="confidence-note">Required for action</div>
565
+ </div>
566
+
567
+ <div class="confidence-item">
568
+ <div class="confidence-label">Current Confidence</div>
569
+ <div class="confidence-value" style="color: {status_color};">{confidence:.1f}%</div>
570
+ <div class="confidence-note">Below threshold → Observe</div>
571
+ </div>
572
+ </div>
573
+
574
+ <div class="confidence-visualization">
575
+ <div class="confidence-scale">
576
+ <div class="scale-marker" style="left: {confidence}%;"></div>
577
+ <div class="scale-bar" style="width: {confidence}%;"></div>
578
+ </div>
579
+ <div class="scale-labels">
580
+ <span>Observe ({confidence:.1f}%)</span>
581
+ <span>Threshold ({threshold}%)</span>
582
+ <span>Act (75%+)</span>
583
+ </div>
584
+ </div>
585
+
586
+ {countdown_html}
587
+
588
+ <div class="prevented-actions">
589
+ <h5>Prevented Actions (Contraindicated)</h5>
590
+ <div class="action-tags">
591
+ <span class="action-tag">scale_during_retry_storm</span>
592
+ <span class="action-tag">add_capacity_during_amplification</span>
593
+ <span class="action-tag">any_action_during_high_uncertainty</span>
594
+ </div>
595
+ </div>
596
+ </div>
597
+ </div>
598
+ """
599
+
600
+ class SequencingFlow(ModernComponent):
601
+ """Visualization of policy-enforced sequencing"""
602
+
603
+ @staticmethod
604
+ def create(steps: List[Dict[str, Any]], **kwargs) -> str:
605
+ """Create sequencing flow visualization"""
606
+ current_step = kwargs.get('current_step', 0)
607
+
608
+ steps_html = []
609
+ for i, step in enumerate(steps):
610
+ is_current = i == current_step
611
+ is_completed = i < current_step
612
+ is_future = i > current_step
613
+
614
+ status_class = 'completed' if is_completed else 'current' if is_current else 'future'
615
+
616
+ steps_html.append(f"""
617
+ <div class="sequencing-step {status_class}" data-step="{i}">
618
+ <div class="step-number">{i + 1}</div>
619
+ <div class="step-content">
620
+ <div class="step-title">{step.get('title', 'Step')}</div>
621
+ <div class="step-description">{step.get('description', '')}</div>
622
+ <div class="step-badge">{step.get('badge', 'REQUIRED')}</div>
623
+ </div>
624
+ </div>
625
+ """)
626
+
627
+ # Add connecting lines
628
+ connectors_html = '<div class="step-connectors">'
629
+ for i in range(len(steps) - 1):
630
+ is_completed = i < current_step
631
+ connector_class = 'completed' if is_completed else ''
632
+ connectors_html += f'<div class="step-connector {connector_class}"></div>'
633
+ connectors_html += '</div>'
634
+
635
+ return f"""
636
+ <div class="sequencing-flow">
637
+ <div class="sequencing-header">
638
+ <h3>🔄 Doctrinal Sequencing: Policy Over Reaction</h3>
639
+ <p>System enforces sequencing regardless of prediction confidence</p>
640
+ <div class="policy-badge">POLICY ENFORCED</div>
641
+ </div>
642
+
643
+ <div class="sequencing-steps">
644
+ {connectors_html}
645
+ {''.join(steps_html)}
646
+ </div>
647
+
648
+ <div class="sequencing-constraint">
649
+ <div class="constraint-icon">🎯</div>
650
+ <div class="constraint-content">
651
+ <h4>Doctrinal Constraint: Scaling Cannot Appear First</h4>
652
+ <p>If retry amplification is detected, scaling is <strong>contraindicated entirely</strong>.
653
+ The system must observe stabilization before considering capacity increases.
654
+ Historical evidence shows scaling-first fails 76% of the time during amplification.</p>
655
+ </div>
656
+ </div>
657
+ </div>
658
+ """
659
+
660
+ class ProcessDisplay(ModernComponent):
661
+ """Display for ARF processes (Detection, Recall, Decision)"""
662
+
663
+ @staticmethod
664
+ def create(process_type: str, data: Dict[str, Any]) -> str:
665
+ """Create process display card"""
666
+ icons = {
667
+ 'detection': '🕵️‍♂️',
668
+ 'recall': '🧠',
669
+ 'decision': '🎯',
670
+ 'safety': '🛡️',
671
+ 'execution': '⚡',
672
+ 'learning': '📚'
673
+ }
674
+
675
+ status_colors = {
676
+ 'active': 'var(--color-success)',
677
+ 'inactive': 'var(--colors-neutral-400)',
678
+ 'error': 'var(--color-danger)'
679
+ }
680
+
681
+ icon = icons.get(process_type, '📊')
682
+ status = data.get('status', 'inactive')
683
+ title = data.get('title', process_type.title())
684
+ description = data.get('description', '')
685
+
686
+ # Metrics display
687
+ metrics_html = ""
688
+ if 'metrics' in data:
689
+ metrics = data['metrics']
690
+ metrics_html = '<div class="process-metrics">'
691
+ for key, value in metrics.items():
692
+ metrics_html += f"""
693
+ <div class="process-metric">
694
+ <div class="metric-key">{key}</div>
695
+ <div class="metric-value">{value}</div>
696
+ </div>
697
+ """
698
+ metrics_html += '</div>'
699
+
700
+ # Next step
701
+ next_step_html = ""
702
+ if 'next_step' in data:
703
+ next_step_html = f"""
704
+ <div class="process-next-step">
705
+ <div class="next-step-label">Next Step:</div>
706
+ <div class="next-step-value">{data['next_step']}</div>
707
+ </div>
708
+ """
709
+
710
+ return f"""
711
+ <div class="process-card" data-process="{process_type}" data-status="{status}">
712
+ <div class="process-header">
713
+ <div class="process-icon">{icon}</div>
714
+ <div class="process-title">
715
+ <h4>{title}</h4>
716
+ <p>{description}</p>
717
+ </div>
718
+ <div class="process-status" style="--status-color: {status_colors.get(status, 'var(--colors-neutral-400)')};">
719
+ STATUS: {status.upper()}
720
+ </div>
721
+ </div>
722
+
723
+ <div class="process-body">
724
+ {metrics_html}
725
+
726
+ {data.get('content', '')}
727
+
728
+ {next_step_html}
729
+ </div>
730
+ </div>
731
+ """
732
+
733
+ # ===========================================
734
+ # DATA VISUALIZATION COMPONENTS
735
+ # ===========================================
736
+ class Chart(ModernComponent):
737
+ """Wrapper for Plotly charts with consistent styling"""
738
+
739
+ @staticmethod
740
+ def create_line(data: Dict[str, Any], **kwargs) -> go.Figure:
741
+ """Create a line chart with ARF styling"""
742
+ title = kwargs.get('title', '')
743
+ height = kwargs.get('height', 300)
744
+
745
+ fig = go.Figure()
746
+
747
+ # Add traces
748
+ for trace in data.get('traces', []):
749
+ fig.add_trace(go.Scatter(
750
+ x=trace.get('x', []),
751
+ y=trace.get('y', []),
752
+ mode=trace.get('mode', 'lines'),
753
+ name=trace.get('name', ''),
754
+ line=dict(
755
+ color=trace.get('color', 'var(--color-primary)'),
756
+ width=trace.get('width', 3)
757
+ ),
758
+ fill=trace.get('fill', None)
759
+ ))
760
+
761
+ # Update layout
762
+ fig.update_layout(
763
+ title={
764
+ 'text': title,
765
+ 'font': {
766
+ 'size': 18,
767
+ 'color': 'var(--colors-neutral-900)',
768
+ 'family': 'var(--font-sans)'
769
+ }
770
+ },
771
+ height=height,
772
+ plot_bgcolor='white',
773
+ paper_bgcolor='white',
774
+ font={
775
+ 'family': 'var(--font-sans)',
776
+ 'color': 'var(--colors-neutral-700)'
777
+ },
778
+ margin=dict(l=40, r=20, t=60, b=40),
779
+ hovermode='x unified',
780
+ showlegend=kwargs.get('show_legend', True),
781
+ legend=dict(
782
+ orientation="h",
783
+ yanchor="bottom",
784
+ y=1.02,
785
+ xanchor="center",
786
+ x=0.5
787
+ )
788
+ )
789
+
790
+ # Update axes
791
+ fig.update_xaxes(
792
+ gridcolor='var(--colors-neutral-200)',
793
+ linecolor='var(--colors-neutral-300)',
794
+ title_font=dict(size=12)
795
+ )
796
+
797
+ fig.update_yaxes(
798
+ gridcolor='var(--colors-neutral-200)',
799
+ linecolor='var(--colors-neutral-300)',
800
+ title_font=dict(size=12)
801
+ )
802
+
803
+ return fig
804
+
805
+ @staticmethod
806
+ def create_gauge(value: float, **kwargs) -> go.Figure:
807
+ """Create a gauge chart for metrics"""
808
+ title = kwargs.get('title', '')
809
+ min_val = kwargs.get('min', 0)
810
+ max_val = kwargs.get('max', 100)
811
+
812
+ fig = go.Figure(go.Indicator(
813
+ mode="gauge+number",
814
+ value=value,
815
+ domain={'x': [0, 1], 'y': [0, 1]},
816
+ title={
817
+ 'text': title,
818
+ 'font': {
819
+ 'size': 16,
820
+ 'family': 'var(--font-sans)'
821
+ }
822
+ },
823
+ number={
824
+ 'font': {
825
+ 'size': 28,
826
+ 'family': 'var(--font-sans)'
827
+ }
828
+ },
829
+ gauge={
830
+ 'axis': {
831
+ 'range': [min_val, max_val],
832
+ 'tickwidth': 1,
833
+ 'tickcolor': "var(--colors-neutral-700)"
834
+ },
835
+ 'bar': {'color': "var(--color-primary)"},
836
+ 'bgcolor': "white",
837
+ 'borderwidth': 2,
838
+ 'bordercolor': "var(--colors-neutral-300)",
839
+ 'steps': [
840
+ {
841
+ 'range': [min_val, max_val * 0.3],
842
+ 'color': 'var(--color-success)'
843
+ },
844
+ {
845
+ 'range': [max_val * 0.3, max_val * 0.7],
846
+ 'color': 'var(--color-warning)'
847
+ },
848
+ {
849
+ 'range': [max_val * 0.7, max_val],
850
+ 'color': 'var(--color-danger)'
851
+ }
852
+ ],
853
+ 'threshold': {
854
+ 'line': {'color': "var(--colors-neutral-900)", 'width': 4},
855
+ 'thickness': 0.75,
856
+ 'value': value
857
+ }
858
+ }
859
+ ))
860
+
861
+ fig.update_layout(
862
+ height=300,
863
+ margin=dict(l=30, r=30, t=70, b=30),
864
+ paper_bgcolor='white',
865
+ font={
866
+ 'family': 'var(--font-sans)'
867
+ }
868
+ )
869
+
870
+ return fig
871
+
872
+ # ===========================================
873
+ # RESPONSIVE UTILITIES
874
+ # ===========================================
875
+ class ResponsiveUtils:
876
+ """Responsive design utilities"""
877
+
878
+ @staticmethod
879
+ def create_responsive_styles() -> str:
880
+ """Generate responsive CSS utilities"""
881
+ return """
882
+ <style>
883
+ /* Container utilities */
884
+ .container {
885
+ width: 100%;
886
+ margin-left: auto;
887
+ margin-right: auto;
888
+ padding-left: var(--spacing-4);
889
+ padding-right: var(--spacing-4);
890
+ }
891
+
892
+ @media (min-width: 640px) {
893
+ .container {
894
+ max-width: 640px;
895
+ }
896
+ }
897
+
898
+ @media (min-width: 768px) {
899
+ .container {
900
+ max-width: 768px;
901
+ }
902
+ }
903
+
904
+ @media (min-width: 1024px) {
905
+ .container {
906
+ max-width: 1024px;
907
+ }
908
+ }
909
+
910
+ @media (min-width: 1280px) {
911
+ .container {
912
+ max-width: 1280px;
913
+ }
914
+ }
915
+
916
+ /* Responsive hide/show */
917
+ .hide-on-mobile {
918
+ display: none;
919
+ }
920
+
921
+ .show-on-mobile {
922
+ display: block;
923
+ }
924
+
925
+ @media (min-width: 768px) {
926
+ .hide-on-mobile {
927
+ display: block;
928
+ }
929
+
930
+ .show-on-mobile {
931
+ display: none;
932
+ }
933
+ }
934
+
935
+ /* Responsive text sizes */
936
+ .text-responsive {
937
+ font-size: var(--typography-fontSizes-sm);
938
+ }
939
+
940
+ @media (min-width: 768px) {
941
+ .text-responsive {
942
+ font-size: var(--typography-fontSizes-base);
943
+ }
944
+ }
945
+
946
+ @media (min-width: 1024px) {
947
+ .text-responsive {
948
+ font-size: var(--typography-fontSizes-lg);
949
+ }
950
+ }
951
+
952
+ /* Responsive spacing */
953
+ .responsive-padding {
954
+ padding: var(--spacing-2);
955
+ }
956
+
957
+ @media (min-width: 768px) {
958
+ .responsive-padding {
959
+ padding: var(--spacing-4);
960
+ }
961
+ }
962
+
963
+ @media (min-width: 1024px) {
964
+ .responsive-padding {
965
+ padding: var(--spacing-6);
966
+ }
967
+ }
968
+ </style>
969
+ """
970
+
971
+ @staticmethod
972
+ def create_mobile_navigation() -> str:
973
+ """Create mobile-friendly navigation toggle"""
974
+ return """
975
+ <style>
976
+ /* Mobile navigation */
977
+ .mobile-nav-toggle {
978
+ display: none;
979
+ position: fixed;
980
+ top: 20px;
981
+ right: 20px;
982
+ z-index: 1000;
983
+ width: 50px;
984
+ height: 50px;
985
+ border-radius: 50%;
986
+ background: var(--color-primary);
987
+ color: white;
988
+ border: none;
989
+ font-size: 24px;
990
+ cursor: pointer;
991
+ align-items: center;
992
+ justify-content: center;
993
+ box-shadow: var(--shadows-md);
994
+ }
995
+
996
+ @media (max-width: 768px) {
997
+ .mobile-nav-toggle {
998
+ display: flex;
999
+ }
1000
+
1001
+ .main-nav {
1002
+ position: fixed;
1003
+ top: 0;
1004
+ left: 0;
1005
+ right: 0;
1006
+ bottom: 0;
1007
+ background: var(--colors-neutral-50);
1008
+ padding: 80px 20px 20px;
1009
+ transform: translateX(-100%);
1010
+ transition: transform var(--transition-normal);
1011
+ z-index: 999;
1012
+ overflow-y: auto;
1013
+ }
1014
+
1015
+ .main-nav.open {
1016
+ transform: translateX(0);
1017
+ }
1018
+ }
1019
+
1020
+ /* Touch-friendly buttons */
1021
+ @media (hover: none) and (pointer: coarse) {
1022
+ .btn, button, [role="button"] {
1023
+ min-height: 44px;
1024
+ min-width: 44px;
1025
+ }
1026
+
1027
+ input, select, textarea {
1028
+ font-size: 16px; /* Prevents iOS zoom */
1029
+ }
1030
+ }
1031
+ </style>
1032
+
1033
+ <button class="mobile-nav-toggle" id="mobileNavToggle" aria-label="Toggle navigation">
1034
+
1035
+ </button>
1036
+
1037
+ <script>
1038
+ document.getElementById('mobileNavToggle').addEventListener('click', function() {
1039
+ const nav = document.querySelector('.main-nav');
1040
+ nav.classList.toggle('open');
1041
+ this.setAttribute('aria-expanded', nav.classList.contains('open'));
1042
+ });
1043
+
1044
+ // Close mobile nav when clicking outside
1045
+ document.addEventListener('click', function(event) {
1046
+ const nav = document.querySelector('.main-nav');
1047
+ const toggle = document.getElementById('mobileNavToggle');
1048
+
1049
+ if (nav && nav.classList.contains('open') &&
1050
+ !nav.contains(event.target) &&
1051
+ !toggle.contains(event.target)) {
1052
+ nav.classList.remove('open');
1053
+ toggle.setAttribute('aria-expanded', 'false');
1054
+ }
1055
+ });
1056
+ </script>
1057
+ """
1058
+
1059
+ # ===========================================
1060
+ # ACCESSIBILITY ENHANCEMENTS
1061
+ # ===========================================
1062
+ class Accessibility:
1063
+ """Accessibility enhancements"""
1064
+
1065
+ @staticmethod
1066
+ def create_skip_link() -> str:
1067
+ """Create skip to content link for keyboard users"""
1068
+ return """
1069
+ <a href="#main-content" class="skip-link">
1070
+ Skip to main content
1071
+ </a>
1072
+
1073
+ <style>
1074
+ .skip-link {
1075
+ position: absolute;
1076
+ top: -40px;
1077
+ left: 0;
1078
+ background: var(--color-primary);
1079
+ color: white;
1080
+ padding: 8px 16px;
1081
+ text-decoration: none;
1082
+ z-index: 9999;
1083
+ border-radius: 0 0 var(--borderRadius-md) 0;
1084
+ }
1085
+
1086
+ .skip-link:focus {
1087
+ top: 0;
1088
+ }
1089
+ </style>
1090
+ """
1091
+
1092
+ @staticmethod
1093
+ def create_focus_management() -> str:
1094
+ """Focus management for modals and dialogs"""
1095
+ return """
1096
+ <script>
1097
+ // Focus trap for modals
1098
+ class FocusTrap {
1099
+ constructor(container) {
1100
+ this.container = container;
1101
+ this.firstFocusable = null;
1102
+ this.lastFocusable = null;
1103
+ this.previousFocus = null;
1104
+
1105
+ this.init();
1106
+ }
1107
+
1108
+ init() {
1109
+ const focusableElements = this.container.querySelectorAll(
1110
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
1111
+ );
1112
+
1113
+ if (focusableElements.length > 0) {
1114
+ this.firstFocusable = focusableElements[0];
1115
+ this.lastFocusable = focusableElements[focusableElements.length - 1];
1116
+ this.previousFocus = document.activeElement;
1117
+
1118
+ this.firstFocusable.focus();
1119
+
1120
+ // Add keydown listener for trap
1121
+ this.container.addEventListener('keydown', (e) => this.handleKeyDown(e));
1122
+ }
1123
+ }
1124
+
1125
+ handleKeyDown(e) {
1126
+ if (e.key === 'Tab') {
1127
+ if (e.shiftKey) {
1128
+ // Shift + Tab
1129
+ if (document.activeElement === this.firstFocusable) {
1130
+ e.preventDefault();
1131
+ this.lastFocusable.focus();
1132
+ }
1133
+ } else {
1134
+ // Tab
1135
+ if (document.activeElement === this.lastFocusable) {
1136
+ e.preventDefault();
1137
+ this.firstFocusable.focus();
1138
+ }
1139
+ }
1140
+ } else if (e.key === 'Escape') {
1141
+ // Close modal and return focus
1142
+ this.close();
1143
+ }
1144
+ }
1145
+
1146
+ close() {
1147
+ if (this.previousFocus) {
1148
+ this.previousFocus.focus();
1149
+ }
1150
+ this.container.removeEventListener('keydown', this.handleKeyDown);
1151
+ }
1152
+ }
1153
+
1154
+ // Initialize focus traps for all modals
1155
+ document.addEventListener('DOMContentLoaded', () => {
1156
+ document.querySelectorAll('[role="dialog"]').forEach(modal => {
1157
+ new FocusTrap(modal);
1158
+ });
1159
+ });
1160
+ </script>
1161
+ """
1162
+
1163
+ @staticmethod
1164
+ def create_aria_live_regions() -> str:
1165
+ """Create ARIA live regions for dynamic content"""
1166
+ return """
1167
+ <div aria-live="polite" aria-atomic="true" class="sr-only" id="status-announcements">
1168
+ <!-- Status messages will be injected here -->
1169
+ </div>
1170
+
1171
+ <div aria-live="assertive" aria-atomic="true" class="sr-only" id="alert-announcements">
1172
+ <!-- Alert messages will be injected here -->
1173
+ </div>
1174
+
1175
+ <script>
1176
+ // Utility functions for announcing messages
1177
+ const AriaAnnouncer = {
1178
+ announceStatus(message) {
1179
+ const region = document.getElementById('status-announcements');
1180
+ region.textContent = message;
1181
+
1182
+ // Clear after announcement
1183
+ setTimeout(() => {
1184
+ region.textContent = '';
1185
+ }, 1000);
1186
+ },
1187
+
1188
+ announceAlert(message) {
1189
+ const region = document.getElementById('alert-announcements');
1190
+ region.textContent = message;
1191
+
1192
+ // Clear after announcement
1193
+ setTimeout(() => {
1194
+ region.textContent = '';
1195
+ }, 1000);
1196
+ }
1197
+ };
1198
+
1199
+ // Example usage:
1200
+ // AriaAnnouncer.announceStatus('Analysis complete');
1201
+ // AriaAnnouncer.announceAlert('Error detected');
1202
+ </script>
1203
+ """
1204
+
1205
+ # ===========================================
1206
+ # DARK MODE SUPPORT
1207
+ # ===========================================
1208
+ class DarkMode:
1209
+ """Dark mode toggle and management"""
1210
+
1211
+ @staticmethod
1212
+ def create_toggle() -> str:
1213
+ """Create dark mode toggle with system preference detection"""
1214
+ return """
1215
+ <button class="dark-mode-toggle" id="darkModeToggle" aria-label="Toggle dark mode">
1216
+ <span class="light-icon">☀️</span>
1217
+ <span class="dark-icon">🌙</span>
1218
+ </button>
1219
+
1220
+ <script>
1221
+ class DarkModeManager {
1222
+ constructor() {
1223
+ this.toggle = document.getElementById('darkModeToggle');
1224
+ this.prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
1225
+ this.savedTheme = localStorage.getItem('theme');
1226
+
1227
+ this.init();
1228
+ }
1229
+
1230
+ init() {
1231
+ // Set initial theme
1232
+ const initialTheme = this.savedTheme ||
1233
+ (this.prefersDark.matches ? 'dark' : 'light');
1234
+ this.setTheme(initialTheme);
1235
+
1236
+ // Add click listener
1237
+ this.toggle.addEventListener('click', () => this.toggleTheme());
1238
+
1239
+ // Listen for system preference changes
1240
+ this.prefersDark.addEventListener('change', (e) => {
1241
+ if (!this.savedTheme) {
1242
+ this.setTheme(e.matches ? 'dark' : 'light');
1243
+ }
1244
+ });
1245
+ }
1246
+
1247
+ setTheme(theme) {
1248
+ document.documentElement.setAttribute('data-theme', theme);
1249
+ localStorage.setItem('theme', theme);
1250
+
1251
+ // Update toggle state
1252
+ if (theme === 'dark') {
1253
+ this.toggle.classList.add('dark');
1254
+ this.toggle.setAttribute('aria-label', 'Switch to light mode');
1255
+ } else {
1256
+ this.toggle.classList.remove('dark');
1257
+ this.toggle.setAttribute('aria-label', 'Switch to dark mode');
1258
+ }
1259
+ }
1260
+
1261
+ toggleTheme() {
1262
+ const currentTheme = document.documentElement.getAttribute('data-theme');
1263
+ const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
1264
+
1265
+ this.setTheme(newTheme);
1266
+ this.savedTheme = newTheme;
1267
+ }
1268
+ }
1269
+
1270
+ // Initialize when DOM is ready
1271
+ if (document.readyState === 'loading') {
1272
+ document.addEventListener('DOMContentLoaded', () => new DarkModeManager());
1273
+ } else {
1274
+ new DarkModeManager();
1275
+ }
1276
+ </script>
1277
+
1278
+ <style>
1279
+ .dark-mode-toggle {
1280
+ position: fixed;
1281
+ bottom: 20px;
1282
+ right: 20px;
1283
+ width: 50px;
1284
+ height: 50px;
1285
+ border-radius: 50%;
1286
+ background: var(--colors-neutral-100);
1287
+ border: 2px solid var(--colors-neutral-300);
1288
+ color: var(--colors-neutral-700);
1289
+ cursor: pointer;
1290
+ display: flex;
1291
+ align-items: center;
1292
+ justify-content: center;
1293
+ font-size: 20px;
1294
+ transition: all var(--transition-normal);
1295
+ z-index: 1000;
1296
+ }
1297
+
1298
+ .dark-mode-toggle:hover {
1299
+ transform: scale(1.1);
1300
+ box-shadow: var(--shadows-md);
1301
+ }
1302
+
1303
+ .dark-mode-toggle.dark {
1304
+ background: var(--colors-neutral-800);
1305
+ border-color: var(--colors-neutral-600);
1306
+ color: var(--colors-neutral-300);
1307
+ }
1308
+
1309
+ .dark-icon {
1310
+ display: none;
1311
+ }
1312
+
1313
+ .dark-mode-toggle.dark .light-icon {
1314
+ display: none;
1315
+ }
1316
+
1317
+ .dark-mode-toggle.dark .dark-icon {
1318
+ display: inline;
1319
+ }
1320
+
1321
+ /* Smooth transitions for theme changes */
1322
+ * {
1323
+ transition: background-color var(--transition-normal),
1324
+ border-color var(--transition-normal),
1325
+ color var(--transition-normal);
1326
+ }
1327
+ </style>
1328
+ """
1329
+
1330
+ # ===========================================
1331
+ # COMPONENT STYLES (CSS)
1332
+ # ===========================================
1333
+ def create_component_styles() -> str:
1334
+ """Create all component styles in one place"""
1335
+ return """
1336
+ <style>
1337
+ /* Card styles */
1338
+ .card {
1339
+ background: var(--colors-neutral-50);
1340
+ border-radius: var(--borderRadius-lg);
1341
+ padding: var(--spacing-6);
1342
+ border: 1px solid var(--border-color, var(--colors-neutral-200));
1343
+ transition: box-shadow var(--transition-normal);
1344
+ }
1345
+
1346
+ .card:hover {
1347
+ box-shadow: var(--shadows-md);
1348
+ }
1349
+
1350
+ .card-elevated {
1351
+ box-shadow: var(--shadows-lg);
1352
+ border: none;
1353
+ }
1354
+
1355
+ .card-outlined {
1356
+ border: 2px solid var(--border-color, var(--color-primary));
1357
+ background: transparent;
1358
+ }
1359
+
1360
+ .card-filled {
1361
+ background: var(--color-primary);
1362
+ color: white;
1363
+ border: none;
1364
+ }
1365
+
1366
+ .card-header {
1367
+ margin-bottom: var(--spacing-4);
1368
+ border-bottom: 1px solid var(--colors-neutral-200);
1369
+ padding-bottom: var(--spacing-3);
1370
+ }
1371
+
1372
+ .card-title {
1373
+ margin: 0;
1374
+ font-size: var(--typography-fontSizes-xl);
1375
+ font-weight: var(--typography-fontWeights-semibold);
1376
+ color: var(--colors-neutral-900);
1377
+ }
1378
+
1379
+ .card-body {
1380
+ color: var(--colors-neutral-700);
1381
+ }
1382
+
1383
+ .card-footer {
1384
+ margin-top: var(--spacing-4);
1385
+ padding-top: var(--spacing-3);
1386
+ border-top: 1px solid var(--colors-neutral-200);
1387
+ color: var(--colors-neutral-600);
1388
+ font-size: var(--typography-fontSizes-sm);
1389
+ }
1390
+
1391
+ /* Metric card */
1392
+ .card-metric {
1393
+ text-align: center;
1394
+ padding: var(--spacing-4);
1395
+ }
1396
+
1397
+ .metric-value {
1398
+ font-size: var(--typography-fontSizes-3xl);
1399
+ font-weight: var(--typography-fontWeights-bold);
1400
+ color: var(--colors-neutral-900);
1401
+ margin-bottom: var(--spacing-2);
1402
+ font-family: var(--font-mono);
1403
+ }
1404
+
1405
+ .metric-label {
1406
+ font-size: var(--typography-fontSizes-sm);
1407
+ color: var(--colors-neutral-600);
1408
+ text-transform: uppercase;
1409
+ letter-spacing: 0.05em;
1410
+ margin-bottom: var(--spacing-2);
1411
+ }
1412
+
1413
+ .metric-trend {
1414
+ font-size: var(--typography-fontSizes-sm);
1415
+ font-weight: var(--typography-fontWeights-semibold);
1416
+ }
1417
+
1418
+ /* Button styles */
1419
+ .btn {
1420
+ display: inline-flex;
1421
+ align-items: center;
1422
+ justify-content: center;
1423
+ padding: var(--spacing-2) var(--spacing-4);
1424
+ border-radius: var(--borderRadius-md);
1425
+ font-weight: var(--typography-fontWeights-medium);
1426
+ font-size: var(--typography-fontSizes-sm);
1427
+ border: none;
1428
+ cursor: pointer;
1429
+ transition: all var(--transition-fast);
1430
+ text-decoration: none;
1431
+ gap: var(--spacing-2);
1432
+ }
1433
+
1434
+ .btn:disabled {
1435
+ opacity: 0.5;
1436
+ cursor: not-allowed;
1437
+ }
1438
+
1439
+ .btn-primary {
1440
+ background: var(--color-primary);
1441
+ color: white;
1442
+ }
1443
+
1444
+ .btn-primary:hover:not(:disabled) {
1445
+ background: var(--colors-primary-600);
1446
+ }
1447
+
1448
+ .btn-secondary {
1449
+ background: var(--colors-neutral-100);
1450
+ color: var(--colors-neutral-700);
1451
+ border: 1px solid var(--colors-neutral-300);
1452
+ }
1453
+
1454
+ .btn-secondary:hover:not(:disabled) {
1455
+ background: var(--colors-neutral-200);
1456
+ }
1457
+
1458
+ .btn-danger {
1459
+ background: var(--color-danger);
1460
+ color: white;
1461
+ }
1462
+
1463
+ .btn-danger:hover:not(:disabled) {
1464
+ background: var(--colors-danger-600);
1465
+ }
1466
+
1467
+ .btn-ghost {
1468
+ background: transparent;
1469
+ color: var(--colors-neutral-700);
1470
+ }
1471
+
1472
+ .btn-ghost:hover:not(:disabled) {
1473
+ background: var(--colors-neutral-100);
1474
+ }
1475
+
1476
+ .btn-sm {
1477
+ padding: var(--spacing-1) var(--spacing-3);
1478
+ font-size: var(--typography-fontSizes-xs);
1479
+ }
1480
+
1481
+ .btn-lg {
1482
+ padding: var(--spacing-3) var(--spacing-6);
1483
+ font-size: var(--typography-fontSizes-base);
1484
+ }
1485
+
1486
+ .btn.full-width {
1487
+ width: 100%;
1488
+ }
1489
+
1490
+ .btn-icon {
1491
+ width: 40px;
1492
+ height: 40px;
1493
+ border-radius: var(--borderRadius-md);
1494
+ background: transparent;
1495
+ border: none;
1496
+ cursor: pointer;
1497
+ display: flex;
1498
+ align-items: center;
1499
+ justify-content: center;
1500
+ transition: background var(--transition-fast);
1501
+ }
1502
+
1503
+ .btn-icon:hover {
1504
+ background: var(--colors-neutral-100);
1505
+ }
1506
+
1507
+ /* Badge styles */
1508
+ .badge {
1509
+ display: inline-block;
1510
+ padding: var(--spacing-1) var(--spacing-2);
1511
+ border-radius: var(--borderRadius-full);
1512
+ font-size: var(--typography-fontSizes-xs);
1513
+ font-weight: var(--typography-fontWeights-medium);
1514
+ text-transform: uppercase;
1515
+ letter-spacing: 0.05em;
1516
+ }
1517
+
1518
+ .badge-default {
1519
+ background: var(--colors-neutral-100);
1520
+ color: var(--colors-neutral-700);
1521
+ }
1522
+
1523
+ .badge-success {
1524
+ background: var(--colors-success-100);
1525
+ color: var(--colors-success-700);
1526
+ }
1527
+
1528
+ .badge-warning {
1529
+ background: var(--colors-warning-100);
1530
+ color: var(--colors-warning-700);
1531
+ }
1532
+
1533
+ .badge-danger {
1534
+ background: var(--colors-danger-100);
1535
+ color: var(--colors-danger-700);
1536
+ }
1537
+
1538
+ .badge-sm {
1539
+ padding: 2px 6px;
1540
+ font-size: 10px;
1541
+ }
1542
+
1543
+ .badge-lg {
1544
+ padding: var(--spacing-1) var(--spacing-3);
1545
+ font-size: var(--typography-fontSizes-sm);
1546
+ }
1547
+
1548
+ /* Observation gate styles */
1549
+ .observation-gate {
1550
+ border: 3px solid var(--status-color, var(--color-warning));
1551
+ border-radius: var(--borderRadius-xl);
1552
+ padding: var(--spacing-6);
1553
+ background: linear-gradient(135deg,
1554
+ color-mix(in srgb, var(--status-color, var(--color-warning)) 5%, transparent),
1555
+ color-mix(in srgb, var(--status-color, var(--color-warning)) 10%, transparent));
1556
+ margin: var(--spacing-4) 0;
1557
+ }
1558
+
1559
+ .observation-gate-header {
1560
+ display: flex;
1561
+ align-items: center;
1562
+ gap: var(--spacing-4);
1563
+ margin-bottom: var(--spacing-4);
1564
+ }
1565
+
1566
+ .observation-icon {
1567
+ font-size: 48px;
1568
+ color: var(--status-color, var(--color-warning));
1569
+ }
1570
+
1571
+ .observation-title h3 {
1572
+ margin: 0;
1573
+ color: color-mix(in srgb, var(--status-color, var(--color-warning)) 90%, black);
1574
+ font-size: var(--typography-fontSizes-2xl);
1575
+ }
1576
+
1577
+ .observation-title p {
1578
+ margin: var(--spacing-1) 0 0 0;
1579
+ color: color-mix(in srgb, var(--status-color, var(--color-warning)) 70%, black);
1580
+ }
1581
+
1582
+ .observation-badge {
1583
+ margin-left: auto;
1584
+ padding: var(--spacing-2) var(--spacing-4);
1585
+ background: var(--status-color, var(--color-warning));
1586
+ color: white;
1587
+ border-radius: var(--borderRadius-full);
1588
+ font-size: var(--typography-fontSizes-xs);
1589
+ font-weight: var(--typography-fontWeights-bold);
1590
+ text-transform: uppercase;
1591
+ letter-spacing: 1px;
1592
+ }
1593
+
1594
+ .confidence-comparison {
1595
+ display: grid;
1596
+ grid-template-columns: 1fr 1fr;
1597
+ gap: var(--spacing-4);
1598
+ margin: var(--spacing-4) 0;
1599
+ }
1600
+
1601
+ .confidence-item {
1602
+ background: white;
1603
+ border-radius: var(--borderRadius-lg);
1604
+ padding: var(--spacing-4);
1605
+ text-align: center;
1606
+ border: 1px solid var(--colors-neutral-200);
1607
+ }
1608
+
1609
+ .confidence-label {
1610
+ font-size: var(--typography-fontSizes-sm);
1611
+ color: var(--colors-neutral-600);
1612
+ text-transform: uppercase;
1613
+ letter-spacing: 0.05em;
1614
+ margin-bottom: var(--spacing-2);
1615
+ }
1616
+
1617
+ .confidence-value {
1618
+ font-size: var(--typography-fontSizes-3xl);
1619
+ font-weight: var(--typography-fontWeights-bold);
1620
+ margin-bottom: var(--spacing-1);
1621
+ }
1622
+
1623
+ .confidence-note {
1624
+ font-size: var(--typography-fontSizes-xs);
1625
+ color: var(--colors-neutral-500);
1626
+ }
1627
+
1628
+ /* Process display styles */
1629
+ .process-card {
1630
+ border: 2px solid var(--colors-neutral-300);
1631
+ border-radius: var(--borderRadius-lg);
1632
+ padding: var(--spacing-4);
1633
+ margin: var(--spacing-3) 0;
1634
+ background: var(--colors-neutral-50);
1635
+ }
1636
+
1637
+ .process-card[data-status="active"] {
1638
+ border-color: var(--color-success);
1639
+ background: color-mix(in srgb, var(--color-success) 5%, transparent);
1640
+ }
1641
+
1642
+ .process-card[data-status="error"] {
1643
+ border-color: var(--color-danger);
1644
+ background: color-mix(in srgb, var(--color-danger) 5%, transparent);
1645
+ }
1646
+
1647
+ .process-header {
1648
+ display: flex;
1649
+ align-items: center;
1650
+ gap: var(--spacing-3);
1651
+ margin-bottom: var(--spacing-3);
1652
+ }
1653
+
1654
+ .process-icon {
1655
+ font-size: 32px;
1656
+ }
1657
+
1658
+ .process-title h4 {
1659
+ margin: 0;
1660
+ font-size: var(--typography-fontSizes-lg);
1661
+ color: var(--colors-neutral-900);
1662
+ }
1663
+
1664
+ .process-title p {
1665
+ margin: var(--spacing-1) 0 0 0;
1666
+ font-size: var(--typography-fontSizes-sm);
1667
+ color: var(--colors-neutral-600);
1668
+ }
1669
+
1670
+ .process-status {
1671
+ margin-left: auto;
1672
+ padding: var(--spacing-1) var(--spacing-3);
1673
+ background: var(--status-color, var(--colors-neutral-300));
1674
+ color: white;
1675
+ border-radius: var(--borderRadius-full);
1676
+ font-size: var(--typography-fontSizes-xs);
1677
+ font-weight: var(--typography-fontWeights-bold);
1678
+ text-transform: uppercase;
1679
+ letter-spacing: 0.05em;
1680
+ }
1681
+
1682
+ .process-metrics {
1683
+ display: grid;
1684
+ grid-template-columns: repeat(2, 1fr);
1685
+ gap: var(--spacing-3);
1686
+ margin-bottom: var(--spacing-3);
1687
+ }
1688
+
1689
+ .process-metric {
1690
+ background: white;
1691
+ border-radius: var(--borderRadius-md);
1692
+ padding: var(--spacing-2);
1693
+ border: 1px solid var(--colors-neutral-200);
1694
+ }
1695
+
1696
+ .metric-key {
1697
+ font-size: var(--typography-fontSizes-xs);
1698
+ color: var(--colors-neutral-600);
1699
+ text-transform: uppercase;
1700
+ letter-spacing: 0.05em;
1701
+ }
1702
+
1703
+ .metric-value {
1704
+ font-size: var(--typography-fontSizes-lg);
1705
+ font-weight: var(--typography-fontWeights-semibold);
1706
+ color: var(--colors-neutral-900);
1707
+ font-family: var(--font-mono);
1708
+ }
1709
+
1710
+ /* Responsive adjustments */
1711
+ @media (max-width: 768px) {
1712
+ .confidence-comparison {
1713
+ grid-template-columns: 1fr;
1714
+ }
1715
+
1716
+ .process-metrics {
1717
+ grid-template-columns: 1fr;
1718
+ }
1719
+
1720
+ .observation-gate-header {
1721
+ flex-direction: column;
1722
+ text-align: center;
1723
+ gap: var(--spacing-2);
1724
+ }
1725
+
1726
+ .observation-badge {
1727
+ margin-left: 0;
1728
+ margin-top: var(--spacing-2);
1729
+ }
1730
+ }
1731
+
1732
+ @media (max-width: 480px) {
1733
+ .card {
1734
+ padding: var(--spacing-3);
1735
+ }
1736
+
1737
+ .observation-gate {
1738
+ padding: var(--spacing-3);
1739
+ }
1740
+
1741
+ .metric-value {
1742
+ font-size: var(--typography-fontSizes-2xl);
1743
+ }
1744
+ }
1745
+ </style>
1746
+ """
1747
+
1748
+ # ===========================================
1749
+ # INITIALIZATION FUNCTION
1750
+ # ===========================================
1751
+ def initialize_modern_ui() -> str:
1752
+ """Initialize all modern UI components and styles"""
1753
+ components = [
1754
+ inject_design_tokens(),
1755
+ create_component_styles(),
1756
+ ResponsiveUtils.create_responsive_styles(),
1757
+ Accessibility.create_skip_link(),
1758
+ Accessibility.create_aria_live_regions(),
1759
+ DarkMode.create_toggle(),
1760
+ ResponsiveUtils.create_mobile_navigation(),
1761
+ Accessibility.create_focus_management()
1762
+ ]
1763
+
1764
+ return "\n".join(components)
1765
+
1766
+ # ===========================================
1767
+ # EXAMPLE USAGE (for documentation)
1768
+ # ===========================================
1769
+ def create_example_dashboard() -> str:
1770
+ """Create an example dashboard using modern components"""
1771
+
1772
+ # Example metrics
1773
+ metrics = [
1774
+ {"value": "42s", "label": "Detection Time", "trend": "down", "change": "↓ 90%"},
1775
+ {"value": "94%", "label": "Recall Quality", "trend": "up", "change": "↑ 8%"},
1776
+ {"value": "87%", "label": "Confidence Score", "trend": "neutral", "change": "→"},
1777
+ {"value": "Dampening", "label": "Sequencing Stage", "trend": None}
1778
+ ]
1779
+
1780
+ # Example process data
1781
+ detection_process = {
1782
+ "status": "active",
1783
+ "title": "Detection Process",
1784
+ "description": "Telemetry analysis & pattern recognition",
1785
+ "metrics": {
1786
+ "Pattern Match": "Retry Amplification",
1787
+ "Confidence": "92.7%",
1788
+ "Detection Time": "0.8 seconds",
1789
+ "Severity": "HIGH_VARIANCE"
1790
+ },
1791
+ "content": """
1792
+ <div class="success-message">
1793
+ ✅ Detected: Retry amplification pattern with exponential growth (r=1.8)
1794
+ </div>
1795
+ """,
1796
+ "next_step": "Activate recall process"
1797
+ }
1798
+
1799
+ # Build dashboard
1800
+ dashboard = f"""
1801
+ {initialize_modern_ui()}
1802
+
1803
+ <div class="container" style="max-width: 1200px; margin: 0 auto;">
1804
+ <h1>Modern ARF Dashboard</h1>
1805
+
1806
+ <!-- Metric Grid -->
1807
+ {Grid.create_metric_grid(metrics)}
1808
+
1809
+ <!-- Observation Gate -->
1810
+ {ObservationGate.create(confidence=65.0)}
1811
+
1812
+ <!-- Process Display -->
1813
+ {ProcessDisplay.create('detection', detection_process)}
1814
+
1815
+ <!-- Sequencing Flow -->
1816
+ {SequencingFlow.create([
1817
+ {"title": "Dampening", "description": "Prevent amplification first", "badge": "REQUIRED"},
1818
+ {"title": "Concurrency", "description": "Manage load, then observe", "badge": "REQUIRED"},
1819
+ {"title": "Observe", "description": "Validate trends for 5+ minutes", "badge": "REQUIRED"},
1820
+ {"title": "Scale", "description": "Only if all previous succeed", "badge": "OPTIONAL"}
1821
+ ])}
1822
+
1823
+ <!-- Example Cards -->
1824
+ <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; margin-top: 30px;">
1825
+ {Card.create(
1826
+ title="OSS Analysis",
1827
+ content="Open source advisory intelligence",
1828
+ footer="Apache 2.0 License",
1829
+ variant="outlined",
1830
+ border_color="var(--color-success)"
1831
+ )}
1832
+
1833
+ {Card.create(
1834
+ title="Enterprise Execution",
1835
+ content="Commercial autonomous execution",
1836
+ footer="Requires License",
1837
+ variant="elevated"
1838
+ )}
1839
+ </div>
1840
+ </div>
1841
+ """
1842
+
1843
+ return dashboard
1844
+
1845
+ # ===========================================
1846
+ # EXPORT LIST
1847
+ # ===========================================
1848
+ __all__ = [
1849
+ # Core components
1850
+ 'ModernComponent', 'Card', 'Button', 'Badge',
1851
+ 'Grid', 'Stack',
1852
+
1853
+ # ARF-specific components
1854
+ 'ObservationGate', 'SequencingFlow', 'ProcessDisplay',
1855
+
1856
+ # Visualization
1857
+ 'Chart',
1858
+
1859
+ # Utilities
1860
+ 'ResponsiveUtils', 'Accessibility', 'DarkMode',
1861
+
1862
+ # Initialization
1863
+ 'initialize_modern_ui', 'create_example_dashboard',
1864
+
1865
+ # Constants
1866
+ 'DESIGN_TOKENS'
1867
+ ]
1868
+
1869
+ # ===========================================
1870
+ # TESTING CODE (Remove in production)
1871
+ # ===========================================
1872
+ if __name__ == "__main__":
1873
+ # Test that components can be created
1874
+ print("Testing modern components...")
1875
+
1876
+ # Create example dashboard
1877
+ example = create_example_dashboard()
1878
+
1879
+ # Save to file for inspection
1880
+ with open("modern_dashboard_example.html", "w") as f:
1881
+ f.write(example)
1882
+
1883
+ print("✅ Modern components created successfully")
1884
+ print("📄 Example saved to: modern_dashboard_example.html")