| """ |
| ui/modern_components.py |
| π Modern UI Components for ARF v3.3.9 |
| Enhanced with design system, accessibility, and performance features |
| |
| Features: |
| 1. Design token system with CSS variables |
| 2. Responsive, mobile-first components |
| 3. Accessibility (ARIA labels, keyboard nav) |
| 4. Performance optimizations |
| 5. Dark mode support |
| 6. Progressive enhancement |
| """ |
|
|
| import json |
| from datetime import datetime |
| from typing import Dict, List, Any, Optional, Tuple |
| import plotly.graph_objects as go |
| import plotly.express as px |
| import numpy as np |
| import pandas as pd |
|
|
| |
| |
| |
| DESIGN_TOKENS = { |
| "colors": { |
| "primary": { |
| "50": "#eff6ff", |
| "100": "#dbeafe", |
| "200": "#bfdbfe", |
| "300": "#93c5fd", |
| "400": "#60a5fa", |
| "500": "#3b82f6", |
| "600": "#2563eb", |
| "700": "#1d4ed8", |
| "800": "#1e40af", |
| "900": "#1e3a8a" |
| }, |
| "success": { |
| "50": "#f0fdf4", |
| "100": "#dcfce7", |
| "200": "#bbf7d0", |
| "300": "#86efac", |
| "400": "#4ade80", |
| "500": "#22c55e", |
| "600": "#16a34a", |
| "700": "#15803d", |
| "800": "#166534", |
| "900": "#14532d" |
| }, |
| "warning": { |
| "50": "#fffbeb", |
| "100": "#fef3c7", |
| "200": "#fde68a", |
| "300": "#fcd34d", |
| "400": "#fbbf24", |
| "500": "#f59e0b", |
| "600": "#d97706", |
| "700": "#b45309", |
| "800": "#92400e", |
| "900": "#78350f" |
| }, |
| "danger": { |
| "50": "#fef2f2", |
| "100": "#fee2e2", |
| "200": "#fecaca", |
| "300": "#fca5a5", |
| "400": "#f87171", |
| "500": "#ef4444", |
| "600": "#dc2626", |
| "700": "#b91c1c", |
| "800": "#991b1b", |
| "900": "#7f1d1d" |
| }, |
| "neutral": { |
| "50": "#f8fafc", |
| "100": "#f1f5f9", |
| "200": "#e2e8f0", |
| "300": "#cbd5e1", |
| "400": "#94a3b8", |
| "500": "#64748b", |
| "600": "#475569", |
| "700": "#334155", |
| "800": "#1e293b", |
| "900": "#0f172a" |
| } |
| }, |
| "spacing": { |
| "1": "0.25rem", |
| "2": "0.5rem", |
| "3": "0.75rem", |
| "4": "1rem", |
| "5": "1.25rem", |
| "6": "1.5rem", |
| "8": "2rem", |
| "10": "2.5rem", |
| "12": "3rem", |
| "16": "4rem", |
| "20": "5rem" |
| }, |
| "typography": { |
| "fontSizes": { |
| "xs": "0.75rem", |
| "sm": "0.875rem", |
| "base": "1rem", |
| "lg": "1.125rem", |
| "xl": "1.25rem", |
| "2xl": "1.5rem", |
| "3xl": "1.875rem", |
| "4xl": "2.25rem" |
| }, |
| "fontWeights": { |
| "light": "300", |
| "normal": "400", |
| "medium": "500", |
| "semibold": "600", |
| "bold": "700", |
| "extrabold": "800" |
| } |
| }, |
| "breakpoints": { |
| "sm": "640px", |
| "md": "768px", |
| "lg": "1024px", |
| "xl": "1280px", |
| "2xl": "1536px" |
| }, |
| "shadows": { |
| "sm": "0 1px 2px 0 rgb(0 0 0 / 0.05)", |
| "md": "0 4px 6px -1px rgb(0 0 0 / 0.1)", |
| "lg": "0 10px 15px -3px rgb(0 0 0 / 0.1)", |
| "xl": "0 20px 25px -5px rgb(0 0 0 / 0.1)" |
| }, |
| "borderRadius": { |
| "sm": "0.25rem", |
| "md": "0.375rem", |
| "lg": "0.5rem", |
| "xl": "0.75rem", |
| "2xl": "1rem", |
| "full": "9999px" |
| } |
| } |
|
|
| |
| |
| |
| def inject_design_tokens() -> str: |
| """Inject CSS variables for design tokens into the page""" |
| css_variables = [] |
| |
| |
| for category, values in DESIGN_TOKENS.items(): |
| if isinstance(values, dict): |
| for key, value in values.items(): |
| if isinstance(value, dict): |
| for subkey, subvalue in value.items(): |
| css_variables.append(f"--{category}-{key}-{subkey}: {subvalue};") |
| else: |
| css_variables.append(f"--{category}-{key}: {value};") |
| |
| return f""" |
| <style id="design-tokens"> |
| :root {{ |
| {chr(10).join(css_variables)} |
| |
| /* Semantic aliases */ |
| --color-primary: var(--colors-primary-500); |
| --color-success: var(--colors-success-500); |
| --color-warning: var(--colors-warning-500); |
| --color-danger: var(--colors-danger-500); |
| |
| /* Spacing aliases */ |
| --spacing-xs: var(--spacing-2); |
| --spacing-sm: var(--spacing-3); |
| --spacing-md: var(--spacing-4); |
| --spacing-lg: var(--spacing-6); |
| --spacing-xl: var(--spacing-8); |
| |
| /* Font families */ |
| --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; |
| --font-mono: 'JetBrains Mono', 'SF Mono', Monaco, 'Cascadia Code', monospace; |
| |
| /* Transitions */ |
| --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1); |
| --transition-normal: 250ms cubic-bezier(0.4, 0, 0.2, 1); |
| --transition-slow: 350ms cubic-bezier(0.4, 0, 0.2, 1); |
| }} |
| |
| /* Dark mode overrides */ |
| [data-theme="dark"] {{ |
| --colors-neutral-50: #0f172a; |
| --colors-neutral-100: #1e293b; |
| --colors-neutral-200: #334155; |
| --colors-neutral-300: #475569; |
| --colors-neutral-400: #64748b; |
| --colors-neutral-500: #94a3b8; |
| --colors-neutral-600: #cbd5e1; |
| --colors-neutral-700: #e2e8f0; |
| --colors-neutral-800: #f1f5f9; |
| --colors-neutral-900: #f8fafc; |
| }} |
| |
| /* Utility classes */ |
| .sr-only {{ |
| position: absolute; |
| width: 1px; |
| height: 1px; |
| padding: 0; |
| margin: -1px; |
| overflow: hidden; |
| clip: rect(0, 0, 0, 0); |
| white-space: nowrap; |
| border: 0; |
| }} |
| |
| .focus-ring {{ |
| outline: 2px solid var(--color-primary); |
| outline-offset: 2px; |
| }} |
| |
| .focus-ring:focus:not(:focus-visible) {{ |
| outline: none; |
| }} |
| |
| @media (prefers-reduced-motion: reduce) {{ |
| *, |
| *::before, |
| *::after {{ |
| animation-duration: 0.01ms !important; |
| animation-iteration-count: 1 !important; |
| transition-duration: 0.01ms !important; |
| }} |
| }} |
| </style> |
| """ |
|
|
| |
| |
| |
| class ModernComponent: |
| """Base class for all modern components with consistent styling""" |
| |
| def __init__(self): |
| self.component_id = f"component_{datetime.now().timestamp()}" |
| |
| @staticmethod |
| def create_container(children: str, **kwargs) -> str: |
| """Create a responsive container""" |
| classes = kwargs.get('classes', '') |
| style = kwargs.get('style', '') |
| |
| return f""" |
| <div class="container {classes}" style="{style}"> |
| {children} |
| </div> |
| """ |
| |
| @staticmethod |
| def create_section(title: str = "", children: str = "", **kwargs) -> str: |
| """Create a semantic section with optional title""" |
| section_id = kwargs.get('id', '') |
| aria_label = kwargs.get('aria_label', title) |
| |
| title_html = f'<h2 class="section-title">{title}</h2>' if title else '' |
| |
| return f""" |
| <section id="{section_id}" class="section" aria-label="{aria_label}"> |
| {title_html} |
| <div class="section-content"> |
| {children} |
| </div> |
| </section> |
| """ |
|
|
| |
| |
| |
| class Card(ModernComponent): |
| """Card component with multiple variants""" |
| |
| @staticmethod |
| def create(content: str, **kwargs) -> str: |
| """Create a card with optional header and footer""" |
| title = kwargs.get('title', '') |
| footer = kwargs.get('footer', '') |
| variant = kwargs.get('variant', 'default') |
| border_color = kwargs.get('border_color', 'var(--colors-neutral-200)') |
| |
| variant_classes = { |
| 'default': 'card-default', |
| 'elevated': 'card-elevated', |
| 'outlined': 'card-outlined', |
| 'filled': 'card-filled' |
| } |
| |
| header_html = f""" |
| <div class="card-header"> |
| <h3 class="card-title">{title}</h3> |
| </div> |
| """ if title else '' |
| |
| footer_html = f""" |
| <div class="card-footer"> |
| {footer} |
| </div> |
| """ if footer else '' |
| |
| return f""" |
| <div class="card {variant_classes.get(variant, 'card-default')}" |
| style="--border-color: {border_color};"> |
| {header_html} |
| <div class="card-body"> |
| {content} |
| </div> |
| {footer_html} |
| </div> |
| """ |
| |
| @staticmethod |
| def create_metric(value: str, label: str, **kwargs) -> str: |
| """Create a metric card for KPIs""" |
| trend = kwargs.get('trend', None) |
| change = kwargs.get('change', '') |
| |
| trend_icon = { |
| 'up': 'β', |
| 'down': 'β', |
| 'neutral': 'β' |
| }.get(trend, '') |
| |
| trend_color = { |
| 'up': 'var(--color-success)', |
| 'down': 'var(--color-danger)', |
| 'neutral': 'var(--colors-neutral-500)' |
| }.get(trend, 'var(--colors-neutral-500)') |
| |
| return f""" |
| <div class="card card-metric" role="status" aria-label="{label}: {value}"> |
| <div class="metric-value" aria-live="polite">{value}</div> |
| <div class="metric-label">{label}</div> |
| {f'<div class="metric-trend" style="color: {trend_color};">{trend_icon} {change}</div>' if trend else ''} |
| </div> |
| """ |
|
|
| class Button(ModernComponent): |
| """Accessible button component with variants""" |
| |
| @staticmethod |
| def create(text: str, **kwargs) -> str: |
| """Create a styled button""" |
| variant = kwargs.get('variant', 'primary') |
| size = kwargs.get('size', 'md') |
| disabled = kwargs.get('disabled', False) |
| full_width = kwargs.get('full_width', False) |
| |
| variant_classes = { |
| 'primary': 'btn-primary', |
| 'secondary': 'btn-secondary', |
| 'danger': 'btn-danger', |
| 'ghost': 'btn-ghost' |
| } |
| |
| size_classes = { |
| 'sm': 'btn-sm', |
| 'md': 'btn-md', |
| 'lg': 'btn-lg' |
| } |
| |
| classes = [ |
| 'btn', |
| variant_classes.get(variant, 'btn-primary'), |
| size_classes.get(size, 'btn-md'), |
| 'full-width' if full_width else '' |
| ] |
| |
| disabled_attr = 'disabled aria-disabled="true"' if disabled else '' |
| |
| return f""" |
| <button class="{' '.join(classes)}" {disabled_attr}> |
| {text} |
| </button> |
| """ |
| |
| @staticmethod |
| def create_icon_button(icon: str, label: str, **kwargs) -> str: |
| """Create an icon-only button with proper accessibility""" |
| return f""" |
| <button class="btn-icon" aria-label="{label}"> |
| <span class="btn-icon-inner">{icon}</span> |
| </button> |
| """ |
|
|
| class Badge(ModernComponent): |
| """Badge component for status, tags, etc.""" |
| |
| @staticmethod |
| def create(text: str, **kwargs) -> str: |
| """Create a badge""" |
| variant = kwargs.get('variant', 'default') |
| size = kwargs.get('size', 'md') |
| |
| variant_classes = { |
| 'default': 'badge-default', |
| 'success': 'badge-success', |
| 'warning': 'badge-warning', |
| 'danger': 'badge-danger' |
| } |
| |
| size_classes = { |
| 'sm': 'badge-sm', |
| 'md': 'badge-md', |
| 'lg': 'badge-lg' |
| } |
| |
| return f""" |
| <span class="badge {variant_classes.get(variant, 'badge-default')} {size_classes.get(size, 'badge-md')}"> |
| {text} |
| </span> |
| """ |
|
|
| |
| |
| |
| class Grid(ModernComponent): |
| """Responsive grid system""" |
| |
| @staticmethod |
| def create(children: List[str], **kwargs) -> str: |
| """Create a responsive grid""" |
| columns = kwargs.get('columns', { |
| 'default': 1, |
| 'sm': 2, |
| 'lg': 3 |
| }) |
| |
| gap = kwargs.get('gap', 'var(--spacing-4)') |
| |
| |
| grid_template = [] |
| for breakpoint, col_count in columns.items(): |
| if breakpoint == 'default': |
| grid_template.append(f"grid-template-columns: repeat({col_count}, 1fr);") |
| else: |
| grid_template.append(f"@media (min-width: {DESIGN_TOKENS['breakpoints'][breakpoint]}) {{") |
| grid_template.append(f" grid-template-columns: repeat({col_count}, 1fr);") |
| grid_template.append("}") |
| |
| return f""" |
| <div class="grid" style="gap: {gap};"> |
| {''.join(children)} |
| </div> |
| |
| <style> |
| .grid {{ |
| display: grid; |
| {grid_template[0] if grid_template else ''} |
| }} |
| |
| {''.join(grid_template[1:]) if len(grid_template) > 1 else ''} |
| </style> |
| """ |
| |
| @staticmethod |
| def create_metric_grid(metrics: List[Dict[str, Any]]) -> str: |
| """Create a grid of metric cards""" |
| metric_cards = [] |
| for metric in metrics: |
| metric_cards.append( |
| Card.create_metric( |
| value=metric.get('value', ''), |
| label=metric.get('label', ''), |
| trend=metric.get('trend'), |
| change=metric.get('change', '') |
| ) |
| ) |
| |
| return Grid.create( |
| children=metric_cards, |
| columns={'default': 2, 'sm': 2, 'lg': 4} |
| ) |
|
|
| class Stack(ModernComponent): |
| """Vertical stack layout""" |
| |
| @staticmethod |
| def create(children: List[str], **kwargs) -> str: |
| """Create a vertical stack""" |
| spacing = kwargs.get('spacing', 'var(--spacing-4)') |
| align = kwargs.get('align', 'stretch') |
| |
| return f""" |
| <div class="stack" style="--spacing: {spacing}; --align: {align};"> |
| {''.join([f'<div class="stack-item">{child}</div>' for child in children])} |
| </div> |
| |
| <style> |
| .stack {{ |
| display: flex; |
| flex-direction: column; |
| align-items: var(--align); |
| gap: var(--spacing); |
| }} |
| |
| .stack-item {{ |
| width: 100%; |
| }} |
| </style> |
| """ |
|
|
| |
| |
| |
| class ObservationGate(ModernComponent): |
| """Observation gate component showing system restraint""" |
| |
| @staticmethod |
| def create(confidence: float = 65.0, **kwargs) -> str: |
| """Create observation gate display""" |
| reason = kwargs.get('reason', 'uncertainty_too_high_for_action') |
| frozen_until = kwargs.get('frozen_until', '') |
| |
| threshold = 70.0 |
| is_blocked = confidence < threshold |
| |
| |
| countdown_html = "" |
| if frozen_until: |
| countdown_html = f""" |
| <div class="countdown"> |
| <span class="countdown-label">Next evaluation:</span> |
| <span class="countdown-timer" data-until="{frozen_until}">5:00</span> |
| </div> |
| """ |
| |
| status_text = "Observation Gate: Awaiting confirmation" if is_blocked else "Observation Gate Cleared" |
| status_color = "var(--color-warning)" if is_blocked else "var(--color-success)" |
| |
| return f""" |
| <div class="observation-gate" style="--status-color: {status_color};"> |
| <div class="observation-gate-header"> |
| <div class="observation-icon">β³</div> |
| <div class="observation-title"> |
| <h3>{status_text}</h3> |
| <p>System restraint engaged</p> |
| </div> |
| <div class="observation-badge"> |
| ACTIVE RESTRAINT |
| </div> |
| </div> |
| |
| <div class="observation-content"> |
| <div class="observation-message"> |
| <h4>Decision Intentionally Deferred</h4> |
| <p>The system has detected uncertainty (<strong>{confidence:.1f}% confidence</strong>) |
| and has chosen to observe rather than act. Historical evidence indicates |
| premature action increases risk by <strong>47%</strong>.</p> |
| </div> |
| |
| <div class="confidence-comparison"> |
| <div class="confidence-item"> |
| <div class="confidence-label">Confidence Threshold</div> |
| <div class="confidence-value">{threshold}%</div> |
| <div class="confidence-note">Required for action</div> |
| </div> |
| |
| <div class="confidence-item"> |
| <div class="confidence-label">Current Confidence</div> |
| <div class="confidence-value" style="color: {status_color};">{confidence:.1f}%</div> |
| <div class="confidence-note">Below threshold β Observe</div> |
| </div> |
| </div> |
| |
| <div class="confidence-visualization"> |
| <div class="confidence-scale"> |
| <div class="scale-marker" style="left: {confidence}%;"></div> |
| <div class="scale-bar" style="width: {confidence}%;"></div> |
| </div> |
| <div class="scale-labels"> |
| <span>Observe ({confidence:.1f}%)</span> |
| <span>Threshold ({threshold}%)</span> |
| <span>Act (75%+)</span> |
| </div> |
| </div> |
| |
| {countdown_html} |
| |
| <div class="prevented-actions"> |
| <h5>Prevented Actions (Contraindicated)</h5> |
| <div class="action-tags"> |
| <span class="action-tag">scale_during_retry_storm</span> |
| <span class="action-tag">add_capacity_during_amplification</span> |
| <span class="action-tag">any_action_during_high_uncertainty</span> |
| </div> |
| </div> |
| </div> |
| </div> |
| """ |
|
|
| class SequencingFlow(ModernComponent): |
| """Visualization of policy-enforced sequencing""" |
| |
| @staticmethod |
| def create(steps: List[Dict[str, Any]], **kwargs) -> str: |
| """Create sequencing flow visualization""" |
| current_step = kwargs.get('current_step', 0) |
| |
| steps_html = [] |
| for i, step in enumerate(steps): |
| is_current = i == current_step |
| is_completed = i < current_step |
| is_future = i > current_step |
| |
| status_class = 'completed' if is_completed else 'current' if is_current else 'future' |
| |
| steps_html.append(f""" |
| <div class="sequencing-step {status_class}" data-step="{i}"> |
| <div class="step-number">{i + 1}</div> |
| <div class="step-content"> |
| <div class="step-title">{step.get('title', 'Step')}</div> |
| <div class="step-description">{step.get('description', '')}</div> |
| <div class="step-badge">{step.get('badge', 'REQUIRED')}</div> |
| </div> |
| </div> |
| """) |
| |
| |
| connectors_html = '<div class="step-connectors">' |
| for i in range(len(steps) - 1): |
| is_completed = i < current_step |
| connector_class = 'completed' if is_completed else '' |
| connectors_html += f'<div class="step-connector {connector_class}"></div>' |
| connectors_html += '</div>' |
| |
| return f""" |
| <div class="sequencing-flow"> |
| <div class="sequencing-header"> |
| <h3>π Doctrinal Sequencing: Policy Over Reaction</h3> |
| <p>System enforces sequencing regardless of prediction confidence</p> |
| <div class="policy-badge">POLICY ENFORCED</div> |
| </div> |
| |
| <div class="sequencing-steps"> |
| {connectors_html} |
| {''.join(steps_html)} |
| </div> |
| |
| <div class="sequencing-constraint"> |
| <div class="constraint-icon">π―</div> |
| <div class="constraint-content"> |
| <h4>Doctrinal Constraint: Scaling Cannot Appear First</h4> |
| <p>If retry amplification is detected, scaling is <strong>contraindicated entirely</strong>. |
| The system must observe stabilization before considering capacity increases. |
| Historical evidence shows scaling-first fails 76% of the time during amplification.</p> |
| </div> |
| </div> |
| </div> |
| """ |
|
|
| class ProcessDisplay(ModernComponent): |
| """Display for ARF processes (Detection, Recall, Decision)""" |
| |
| @staticmethod |
| def create(process_type: str, data: Dict[str, Any]) -> str: |
| """Create process display card""" |
| icons = { |
| 'detection': 'π΅οΈββοΈ', |
| 'recall': 'π§ ', |
| 'decision': 'π―', |
| 'safety': 'π‘οΈ', |
| 'execution': 'β‘', |
| 'learning': 'π' |
| } |
| |
| status_colors = { |
| 'active': 'var(--color-success)', |
| 'inactive': 'var(--colors-neutral-400)', |
| 'error': 'var(--color-danger)' |
| } |
| |
| icon = icons.get(process_type, 'π') |
| status = data.get('status', 'inactive') |
| title = data.get('title', process_type.title()) |
| description = data.get('description', '') |
| |
| |
| metrics_html = "" |
| if 'metrics' in data: |
| metrics = data['metrics'] |
| metrics_html = '<div class="process-metrics">' |
| for key, value in metrics.items(): |
| metrics_html += f""" |
| <div class="process-metric"> |
| <div class="metric-key">{key}</div> |
| <div class="metric-value">{value}</div> |
| </div> |
| """ |
| metrics_html += '</div>' |
| |
| |
| next_step_html = "" |
| if 'next_step' in data: |
| next_step_html = f""" |
| <div class="process-next-step"> |
| <div class="next-step-label">Next Step:</div> |
| <div class="next-step-value">{data['next_step']}</div> |
| </div> |
| """ |
| |
| return f""" |
| <div class="process-card" data-process="{process_type}" data-status="{status}"> |
| <div class="process-header"> |
| <div class="process-icon">{icon}</div> |
| <div class="process-title"> |
| <h4>{title}</h4> |
| <p>{description}</p> |
| </div> |
| <div class="process-status" style="--status-color: {status_colors.get(status, 'var(--colors-neutral-400)')};"> |
| STATUS: {status.upper()} |
| </div> |
| </div> |
| |
| <div class="process-body"> |
| {metrics_html} |
| |
| {data.get('content', '')} |
| |
| {next_step_html} |
| </div> |
| </div> |
| """ |
|
|
| |
| |
| |
| class Chart(ModernComponent): |
| """Wrapper for Plotly charts with consistent styling""" |
| |
| @staticmethod |
| def create_line(data: Dict[str, Any], **kwargs) -> go.Figure: |
| """Create a line chart with ARF styling""" |
| title = kwargs.get('title', '') |
| height = kwargs.get('height', 300) |
| |
| fig = go.Figure() |
| |
| |
| for trace in data.get('traces', []): |
| fig.add_trace(go.Scatter( |
| x=trace.get('x', []), |
| y=trace.get('y', []), |
| mode=trace.get('mode', 'lines'), |
| name=trace.get('name', ''), |
| line=dict( |
| color=trace.get('color', 'var(--color-primary)'), |
| width=trace.get('width', 3) |
| ), |
| fill=trace.get('fill', None) |
| )) |
| |
| |
| fig.update_layout( |
| title={ |
| 'text': title, |
| 'font': { |
| 'size': 18, |
| 'color': 'var(--colors-neutral-900)', |
| 'family': 'var(--font-sans)' |
| } |
| }, |
| height=height, |
| plot_bgcolor='white', |
| paper_bgcolor='white', |
| font={ |
| 'family': 'var(--font-sans)', |
| 'color': 'var(--colors-neutral-700)' |
| }, |
| margin=dict(l=40, r=20, t=60, b=40), |
| hovermode='x unified', |
| showlegend=kwargs.get('show_legend', True), |
| legend=dict( |
| orientation="h", |
| yanchor="bottom", |
| y=1.02, |
| xanchor="center", |
| x=0.5 |
| ) |
| ) |
| |
| |
| fig.update_xaxes( |
| gridcolor='var(--colors-neutral-200)', |
| linecolor='var(--colors-neutral-300)', |
| title_font=dict(size=12) |
| ) |
| |
| fig.update_yaxes( |
| gridcolor='var(--colors-neutral-200)', |
| linecolor='var(--colors-neutral-300)', |
| title_font=dict(size=12) |
| ) |
| |
| return fig |
| |
| @staticmethod |
| def create_gauge(value: float, **kwargs) -> go.Figure: |
| """Create a gauge chart for metrics""" |
| title = kwargs.get('title', '') |
| min_val = kwargs.get('min', 0) |
| max_val = kwargs.get('max', 100) |
| |
| fig = go.Figure(go.Indicator( |
| mode="gauge+number", |
| value=value, |
| domain={'x': [0, 1], 'y': [0, 1]}, |
| title={ |
| 'text': title, |
| 'font': { |
| 'size': 16, |
| 'family': 'var(--font-sans)' |
| } |
| }, |
| number={ |
| 'font': { |
| 'size': 28, |
| 'family': 'var(--font-sans)' |
| } |
| }, |
| gauge={ |
| 'axis': { |
| 'range': [min_val, max_val], |
| 'tickwidth': 1, |
| 'tickcolor': "var(--colors-neutral-700)" |
| }, |
| 'bar': {'color': "var(--color-primary)"}, |
| 'bgcolor': "white", |
| 'borderwidth': 2, |
| 'bordercolor': "var(--colors-neutral-300)", |
| 'steps': [ |
| { |
| 'range': [min_val, max_val * 0.3], |
| 'color': 'var(--color-success)' |
| }, |
| { |
| 'range': [max_val * 0.3, max_val * 0.7], |
| 'color': 'var(--color-warning)' |
| }, |
| { |
| 'range': [max_val * 0.7, max_val], |
| 'color': 'var(--color-danger)' |
| } |
| ], |
| 'threshold': { |
| 'line': {'color': "var(--colors-neutral-900)", 'width': 4}, |
| 'thickness': 0.75, |
| 'value': value |
| } |
| } |
| )) |
| |
| fig.update_layout( |
| height=300, |
| margin=dict(l=30, r=30, t=70, b=30), |
| paper_bgcolor='white', |
| font={ |
| 'family': 'var(--font-sans)' |
| } |
| ) |
| |
| return fig |
|
|
| |
| |
| |
| class ResponsiveUtils: |
| """Responsive design utilities""" |
| |
| @staticmethod |
| def create_responsive_styles() -> str: |
| """Generate responsive CSS utilities""" |
| return """ |
| <style> |
| /* Container utilities */ |
| .container { |
| width: 100%; |
| margin-left: auto; |
| margin-right: auto; |
| padding-left: var(--spacing-4); |
| padding-right: var(--spacing-4); |
| } |
| |
| @media (min-width: 640px) { |
| .container { |
| max-width: 640px; |
| } |
| } |
| |
| @media (min-width: 768px) { |
| .container { |
| max-width: 768px; |
| } |
| } |
| |
| @media (min-width: 1024px) { |
| .container { |
| max-width: 1024px; |
| } |
| } |
| |
| @media (min-width: 1280px) { |
| .container { |
| max-width: 1280px; |
| } |
| } |
| |
| /* Responsive hide/show */ |
| .hide-on-mobile { |
| display: none; |
| } |
| |
| .show-on-mobile { |
| display: block; |
| } |
| |
| @media (min-width: 768px) { |
| .hide-on-mobile { |
| display: block; |
| } |
| |
| .show-on-mobile { |
| display: none; |
| } |
| } |
| |
| /* Responsive text sizes */ |
| .text-responsive { |
| font-size: var(--typography-fontSizes-sm); |
| } |
| |
| @media (min-width: 768px) { |
| .text-responsive { |
| font-size: var(--typography-fontSizes-base); |
| } |
| } |
| |
| @media (min-width: 1024px) { |
| .text-responsive { |
| font-size: var(--typography-fontSizes-lg); |
| } |
| } |
| |
| /* Responsive spacing */ |
| .responsive-padding { |
| padding: var(--spacing-2); |
| } |
| |
| @media (min-width: 768px) { |
| .responsive-padding { |
| padding: var(--spacing-4); |
| } |
| } |
| |
| @media (min-width: 1024px) { |
| .responsive-padding { |
| padding: var(--spacing-6); |
| } |
| } |
| </style> |
| """ |
| |
| @staticmethod |
| def create_mobile_navigation() -> str: |
| """Create mobile-friendly navigation toggle""" |
| return """ |
| <style> |
| /* Mobile navigation */ |
| .mobile-nav-toggle { |
| display: none; |
| position: fixed; |
| top: 20px; |
| right: 20px; |
| z-index: 1000; |
| width: 50px; |
| height: 50px; |
| border-radius: 50%; |
| background: var(--color-primary); |
| color: white; |
| border: none; |
| font-size: 24px; |
| cursor: pointer; |
| align-items: center; |
| justify-content: center; |
| box-shadow: var(--shadows-md); |
| } |
| |
| @media (max-width: 768px) { |
| .mobile-nav-toggle { |
| display: flex; |
| } |
| |
| .main-nav { |
| position: fixed; |
| top: 0; |
| left: 0; |
| right: 0; |
| bottom: 0; |
| background: var(--colors-neutral-50); |
| padding: 80px 20px 20px; |
| transform: translateX(-100%); |
| transition: transform var(--transition-normal); |
| z-index: 999; |
| overflow-y: auto; |
| } |
| |
| .main-nav.open { |
| transform: translateX(0); |
| } |
| } |
| |
| /* Touch-friendly buttons */ |
| @media (hover: none) and (pointer: coarse) { |
| .btn, button, [role="button"] { |
| min-height: 44px; |
| min-width: 44px; |
| } |
| |
| input, select, textarea { |
| font-size: 16px; /* Prevents iOS zoom */ |
| } |
| } |
| </style> |
| |
| <button class="mobile-nav-toggle" id="mobileNavToggle" aria-label="Toggle navigation"> |
| β° |
| </button> |
| |
| <script> |
| document.getElementById('mobileNavToggle').addEventListener('click', function() { |
| const nav = document.querySelector('.main-nav'); |
| nav.classList.toggle('open'); |
| this.setAttribute('aria-expanded', nav.classList.contains('open')); |
| }); |
| |
| // Close mobile nav when clicking outside |
| document.addEventListener('click', function(event) { |
| const nav = document.querySelector('.main-nav'); |
| const toggle = document.getElementById('mobileNavToggle'); |
| |
| if (nav && nav.classList.contains('open') && |
| !nav.contains(event.target) && |
| !toggle.contains(event.target)) { |
| nav.classList.remove('open'); |
| toggle.setAttribute('aria-expanded', 'false'); |
| } |
| }); |
| </script> |
| """ |
|
|
| |
| |
| |
| class Accessibility: |
| """Accessibility enhancements""" |
| |
| @staticmethod |
| def create_skip_link() -> str: |
| """Create skip to content link for keyboard users""" |
| return """ |
| <a href="#main-content" class="skip-link"> |
| Skip to main content |
| </a> |
| |
| <style> |
| .skip-link { |
| position: absolute; |
| top: -40px; |
| left: 0; |
| background: var(--color-primary); |
| color: white; |
| padding: 8px 16px; |
| text-decoration: none; |
| z-index: 9999; |
| border-radius: 0 0 var(--borderRadius-md) 0; |
| } |
| |
| .skip-link:focus { |
| top: 0; |
| } |
| </style> |
| """ |
| |
| @staticmethod |
| def create_focus_management() -> str: |
| """Focus management for modals and dialogs""" |
| return """ |
| <script> |
| // Focus trap for modals |
| class FocusTrap { |
| constructor(container) { |
| this.container = container; |
| this.firstFocusable = null; |
| this.lastFocusable = null; |
| this.previousFocus = null; |
| |
| this.init(); |
| } |
| |
| init() { |
| const focusableElements = this.container.querySelectorAll( |
| 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' |
| ); |
| |
| if (focusableElements.length > 0) { |
| this.firstFocusable = focusableElements[0]; |
| this.lastFocusable = focusableElements[focusableElements.length - 1]; |
| this.previousFocus = document.activeElement; |
| |
| this.firstFocusable.focus(); |
| |
| // Add keydown listener for trap |
| this.container.addEventListener('keydown', (e) => this.handleKeyDown(e)); |
| } |
| } |
| |
| handleKeyDown(e) { |
| if (e.key === 'Tab') { |
| if (e.shiftKey) { |
| // Shift + Tab |
| if (document.activeElement === this.firstFocusable) { |
| e.preventDefault(); |
| this.lastFocusable.focus(); |
| } |
| } else { |
| // Tab |
| if (document.activeElement === this.lastFocusable) { |
| e.preventDefault(); |
| this.firstFocusable.focus(); |
| } |
| } |
| } else if (e.key === 'Escape') { |
| // Close modal and return focus |
| this.close(); |
| } |
| } |
| |
| close() { |
| if (this.previousFocus) { |
| this.previousFocus.focus(); |
| } |
| this.container.removeEventListener('keydown', this.handleKeyDown); |
| } |
| } |
| |
| // Initialize focus traps for all modals |
| document.addEventListener('DOMContentLoaded', () => { |
| document.querySelectorAll('[role="dialog"]').forEach(modal => { |
| new FocusTrap(modal); |
| }); |
| }); |
| </script> |
| """ |
| |
| @staticmethod |
| def create_aria_live_regions() -> str: |
| """Create ARIA live regions for dynamic content""" |
| return """ |
| <div aria-live="polite" aria-atomic="true" class="sr-only" id="status-announcements"> |
| <!-- Status messages will be injected here --> |
| </div> |
| |
| <div aria-live="assertive" aria-atomic="true" class="sr-only" id="alert-announcements"> |
| <!-- Alert messages will be injected here --> |
| </div> |
| |
| <script> |
| // Utility functions for announcing messages |
| const AriaAnnouncer = { |
| announceStatus(message) { |
| const region = document.getElementById('status-announcements'); |
| region.textContent = message; |
| |
| // Clear after announcement |
| setTimeout(() => { |
| region.textContent = ''; |
| }, 1000); |
| }, |
| |
| announceAlert(message) { |
| const region = document.getElementById('alert-announcements'); |
| region.textContent = message; |
| |
| // Clear after announcement |
| setTimeout(() => { |
| region.textContent = ''; |
| }, 1000); |
| } |
| }; |
| |
| // Example usage: |
| // AriaAnnouncer.announceStatus('Analysis complete'); |
| // AriaAnnouncer.announceAlert('Error detected'); |
| </script> |
| """ |
|
|
| |
| |
| |
| class DarkMode: |
| """Dark mode toggle and management""" |
| |
| @staticmethod |
| def create_toggle() -> str: |
| """Create dark mode toggle with system preference detection""" |
| return """ |
| <button class="dark-mode-toggle" id="darkModeToggle" aria-label="Toggle dark mode"> |
| <span class="light-icon">βοΈ</span> |
| <span class="dark-icon">π</span> |
| </button> |
| |
| <script> |
| class DarkModeManager { |
| constructor() { |
| this.toggle = document.getElementById('darkModeToggle'); |
| this.prefersDark = window.matchMedia('(prefers-color-scheme: dark)'); |
| this.savedTheme = localStorage.getItem('theme'); |
| |
| this.init(); |
| } |
| |
| init() { |
| // Set initial theme |
| const initialTheme = this.savedTheme || |
| (this.prefersDark.matches ? 'dark' : 'light'); |
| this.setTheme(initialTheme); |
| |
| // Add click listener |
| this.toggle.addEventListener('click', () => this.toggleTheme()); |
| |
| // Listen for system preference changes |
| this.prefersDark.addEventListener('change', (e) => { |
| if (!this.savedTheme) { |
| this.setTheme(e.matches ? 'dark' : 'light'); |
| } |
| }); |
| } |
| |
| setTheme(theme) { |
| document.documentElement.setAttribute('data-theme', theme); |
| localStorage.setItem('theme', theme); |
| |
| // Update toggle state |
| if (theme === 'dark') { |
| this.toggle.classList.add('dark'); |
| this.toggle.setAttribute('aria-label', 'Switch to light mode'); |
| } else { |
| this.toggle.classList.remove('dark'); |
| this.toggle.setAttribute('aria-label', 'Switch to dark mode'); |
| } |
| } |
| |
| toggleTheme() { |
| const currentTheme = document.documentElement.getAttribute('data-theme'); |
| const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; |
| |
| this.setTheme(newTheme); |
| this.savedTheme = newTheme; |
| } |
| } |
| |
| // Initialize when DOM is ready |
| if (document.readyState === 'loading') { |
| document.addEventListener('DOMContentLoaded', () => new DarkModeManager()); |
| } else { |
| new DarkModeManager(); |
| } |
| </script> |
| |
| <style> |
| .dark-mode-toggle { |
| position: fixed; |
| bottom: 20px; |
| right: 20px; |
| width: 50px; |
| height: 50px; |
| border-radius: 50%; |
| background: var(--colors-neutral-100); |
| border: 2px solid var(--colors-neutral-300); |
| color: var(--colors-neutral-700); |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 20px; |
| transition: all var(--transition-normal); |
| z-index: 1000; |
| } |
| |
| .dark-mode-toggle:hover { |
| transform: scale(1.1); |
| box-shadow: var(--shadows-md); |
| } |
| |
| .dark-mode-toggle.dark { |
| background: var(--colors-neutral-800); |
| border-color: var(--colors-neutral-600); |
| color: var(--colors-neutral-300); |
| } |
| |
| .dark-icon { |
| display: none; |
| } |
| |
| .dark-mode-toggle.dark .light-icon { |
| display: none; |
| } |
| |
| .dark-mode-toggle.dark .dark-icon { |
| display: inline; |
| } |
| |
| /* Smooth transitions for theme changes */ |
| * { |
| transition: background-color var(--transition-normal), |
| border-color var(--transition-normal), |
| color var(--transition-normal); |
| } |
| </style> |
| """ |
|
|
| |
| |
| |
| def create_component_styles() -> str: |
| """Create all component styles in one place""" |
| return """ |
| <style> |
| /* Card styles */ |
| .card { |
| background: var(--colors-neutral-50); |
| border-radius: var(--borderRadius-lg); |
| padding: var(--spacing-6); |
| border: 1px solid var(--border-color, var(--colors-neutral-200)); |
| transition: box-shadow var(--transition-normal); |
| } |
| |
| .card:hover { |
| box-shadow: var(--shadows-md); |
| } |
| |
| .card-elevated { |
| box-shadow: var(--shadows-lg); |
| border: none; |
| } |
| |
| .card-outlined { |
| border: 2px solid var(--border-color, var(--color-primary)); |
| background: transparent; |
| } |
| |
| .card-filled { |
| background: var(--color-primary); |
| color: white; |
| border: none; |
| } |
| |
| .card-header { |
| margin-bottom: var(--spacing-4); |
| border-bottom: 1px solid var(--colors-neutral-200); |
| padding-bottom: var(--spacing-3); |
| } |
| |
| .card-title { |
| margin: 0; |
| font-size: var(--typography-fontSizes-xl); |
| font-weight: var(--typography-fontWeights-semibold); |
| color: var(--colors-neutral-900); |
| } |
| |
| .card-body { |
| color: var(--colors-neutral-700); |
| } |
| |
| .card-footer { |
| margin-top: var(--spacing-4); |
| padding-top: var(--spacing-3); |
| border-top: 1px solid var(--colors-neutral-200); |
| color: var(--colors-neutral-600); |
| font-size: var(--typography-fontSizes-sm); |
| } |
| |
| /* Metric card */ |
| .card-metric { |
| text-align: center; |
| padding: var(--spacing-4); |
| } |
| |
| .metric-value { |
| font-size: var(--typography-fontSizes-3xl); |
| font-weight: var(--typography-fontWeights-bold); |
| color: var(--colors-neutral-900); |
| margin-bottom: var(--spacing-2); |
| font-family: var(--font-mono); |
| } |
| |
| .metric-label { |
| font-size: var(--typography-fontSizes-sm); |
| color: var(--colors-neutral-600); |
| text-transform: uppercase; |
| letter-spacing: 0.05em; |
| margin-bottom: var(--spacing-2); |
| } |
| |
| .metric-trend { |
| font-size: var(--typography-fontSizes-sm); |
| font-weight: var(--typography-fontWeights-semibold); |
| } |
| |
| /* Button styles */ |
| .btn { |
| display: inline-flex; |
| align-items: center; |
| justify-content: center; |
| padding: var(--spacing-2) var(--spacing-4); |
| border-radius: var(--borderRadius-md); |
| font-weight: var(--typography-fontWeights-medium); |
| font-size: var(--typography-fontSizes-sm); |
| border: none; |
| cursor: pointer; |
| transition: all var(--transition-fast); |
| text-decoration: none; |
| gap: var(--spacing-2); |
| } |
| |
| .btn:disabled { |
| opacity: 0.5; |
| cursor: not-allowed; |
| } |
| |
| .btn-primary { |
| background: var(--color-primary); |
| color: white; |
| } |
| |
| .btn-primary:hover:not(:disabled) { |
| background: var(--colors-primary-600); |
| } |
| |
| .btn-secondary { |
| background: var(--colors-neutral-100); |
| color: var(--colors-neutral-700); |
| border: 1px solid var(--colors-neutral-300); |
| } |
| |
| .btn-secondary:hover:not(:disabled) { |
| background: var(--colors-neutral-200); |
| } |
| |
| .btn-danger { |
| background: var(--color-danger); |
| color: white; |
| } |
| |
| .btn-danger:hover:not(:disabled) { |
| background: var(--colors-danger-600); |
| } |
| |
| .btn-ghost { |
| background: transparent; |
| color: var(--colors-neutral-700); |
| } |
| |
| .btn-ghost:hover:not(:disabled) { |
| background: var(--colors-neutral-100); |
| } |
| |
| .btn-sm { |
| padding: var(--spacing-1) var(--spacing-3); |
| font-size: var(--typography-fontSizes-xs); |
| } |
| |
| .btn-lg { |
| padding: var(--spacing-3) var(--spacing-6); |
| font-size: var(--typography-fontSizes-base); |
| } |
| |
| .btn.full-width { |
| width: 100%; |
| } |
| |
| .btn-icon { |
| width: 40px; |
| height: 40px; |
| border-radius: var(--borderRadius-md); |
| background: transparent; |
| border: none; |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| transition: background var(--transition-fast); |
| } |
| |
| .btn-icon:hover { |
| background: var(--colors-neutral-100); |
| } |
| |
| /* Badge styles */ |
| .badge { |
| display: inline-block; |
| padding: var(--spacing-1) var(--spacing-2); |
| border-radius: var(--borderRadius-full); |
| font-size: var(--typography-fontSizes-xs); |
| font-weight: var(--typography-fontWeights-medium); |
| text-transform: uppercase; |
| letter-spacing: 0.05em; |
| } |
| |
| .badge-default { |
| background: var(--colors-neutral-100); |
| color: var(--colors-neutral-700); |
| } |
| |
| .badge-success { |
| background: var(--colors-success-100); |
| color: var(--colors-success-700); |
| } |
| |
| .badge-warning { |
| background: var(--colors-warning-100); |
| color: var(--colors-warning-700); |
| } |
| |
| .badge-danger { |
| background: var(--colors-danger-100); |
| color: var(--colors-danger-700); |
| } |
| |
| .badge-sm { |
| padding: 2px 6px; |
| font-size: 10px; |
| } |
| |
| .badge-lg { |
| padding: var(--spacing-1) var(--spacing-3); |
| font-size: var(--typography-fontSizes-sm); |
| } |
| |
| /* Observation gate styles */ |
| .observation-gate { |
| border: 3px solid var(--status-color, var(--color-warning)); |
| border-radius: var(--borderRadius-xl); |
| padding: var(--spacing-6); |
| background: linear-gradient(135deg, |
| color-mix(in srgb, var(--status-color, var(--color-warning)) 5%, transparent), |
| color-mix(in srgb, var(--status-color, var(--color-warning)) 10%, transparent)); |
| margin: var(--spacing-4) 0; |
| } |
| |
| .observation-gate-header { |
| display: flex; |
| align-items: center; |
| gap: var(--spacing-4); |
| margin-bottom: var(--spacing-4); |
| } |
| |
| .observation-icon { |
| font-size: 48px; |
| color: var(--status-color, var(--color-warning)); |
| } |
| |
| .observation-title h3 { |
| margin: 0; |
| color: color-mix(in srgb, var(--status-color, var(--color-warning)) 90%, black); |
| font-size: var(--typography-fontSizes-2xl); |
| } |
| |
| .observation-title p { |
| margin: var(--spacing-1) 0 0 0; |
| color: color-mix(in srgb, var(--status-color, var(--color-warning)) 70%, black); |
| } |
| |
| .observation-badge { |
| margin-left: auto; |
| padding: var(--spacing-2) var(--spacing-4); |
| background: var(--status-color, var(--color-warning)); |
| color: white; |
| border-radius: var(--borderRadius-full); |
| font-size: var(--typography-fontSizes-xs); |
| font-weight: var(--typography-fontWeights-bold); |
| text-transform: uppercase; |
| letter-spacing: 1px; |
| } |
| |
| .confidence-comparison { |
| display: grid; |
| grid-template-columns: 1fr 1fr; |
| gap: var(--spacing-4); |
| margin: var(--spacing-4) 0; |
| } |
| |
| .confidence-item { |
| background: white; |
| border-radius: var(--borderRadius-lg); |
| padding: var(--spacing-4); |
| text-align: center; |
| border: 1px solid var(--colors-neutral-200); |
| } |
| |
| .confidence-label { |
| font-size: var(--typography-fontSizes-sm); |
| color: var(--colors-neutral-600); |
| text-transform: uppercase; |
| letter-spacing: 0.05em; |
| margin-bottom: var(--spacing-2); |
| } |
| |
| .confidence-value { |
| font-size: var(--typography-fontSizes-3xl); |
| font-weight: var(--typography-fontWeights-bold); |
| margin-bottom: var(--spacing-1); |
| } |
| |
| .confidence-note { |
| font-size: var(--typography-fontSizes-xs); |
| color: var(--colors-neutral-500); |
| } |
| |
| /* Process display styles */ |
| .process-card { |
| border: 2px solid var(--colors-neutral-300); |
| border-radius: var(--borderRadius-lg); |
| padding: var(--spacing-4); |
| margin: var(--spacing-3) 0; |
| background: var(--colors-neutral-50); |
| } |
| |
| .process-card[data-status="active"] { |
| border-color: var(--color-success); |
| background: color-mix(in srgb, var(--color-success) 5%, transparent); |
| } |
| |
| .process-card[data-status="error"] { |
| border-color: var(--color-danger); |
| background: color-mix(in srgb, var(--color-danger) 5%, transparent); |
| } |
| |
| .process-header { |
| display: flex; |
| align-items: center; |
| gap: var(--spacing-3); |
| margin-bottom: var(--spacing-3); |
| } |
| |
| .process-icon { |
| font-size: 32px; |
| } |
| |
| .process-title h4 { |
| margin: 0; |
| font-size: var(--typography-fontSizes-lg); |
| color: var(--colors-neutral-900); |
| } |
| |
| .process-title p { |
| margin: var(--spacing-1) 0 0 0; |
| font-size: var(--typography-fontSizes-sm); |
| color: var(--colors-neutral-600); |
| } |
| |
| .process-status { |
| margin-left: auto; |
| padding: var(--spacing-1) var(--spacing-3); |
| background: var(--status-color, var(--colors-neutral-300)); |
| color: white; |
| border-radius: var(--borderRadius-full); |
| font-size: var(--typography-fontSizes-xs); |
| font-weight: var(--typography-fontWeights-bold); |
| text-transform: uppercase; |
| letter-spacing: 0.05em; |
| } |
| |
| .process-metrics { |
| display: grid; |
| grid-template-columns: repeat(2, 1fr); |
| gap: var(--spacing-3); |
| margin-bottom: var(--spacing-3); |
| } |
| |
| .process-metric { |
| background: white; |
| border-radius: var(--borderRadius-md); |
| padding: var(--spacing-2); |
| border: 1px solid var(--colors-neutral-200); |
| } |
| |
| .metric-key { |
| font-size: var(--typography-fontSizes-xs); |
| color: var(--colors-neutral-600); |
| text-transform: uppercase; |
| letter-spacing: 0.05em; |
| } |
| |
| .metric-value { |
| font-size: var(--typography-fontSizes-lg); |
| font-weight: var(--typography-fontWeights-semibold); |
| color: var(--colors-neutral-900); |
| font-family: var(--font-mono); |
| } |
| |
| /* Responsive adjustments */ |
| @media (max-width: 768px) { |
| .confidence-comparison { |
| grid-template-columns: 1fr; |
| } |
| |
| .process-metrics { |
| grid-template-columns: 1fr; |
| } |
| |
| .observation-gate-header { |
| flex-direction: column; |
| text-align: center; |
| gap: var(--spacing-2); |
| } |
| |
| .observation-badge { |
| margin-left: 0; |
| margin-top: var(--spacing-2); |
| } |
| } |
| |
| @media (max-width: 480px) { |
| .card { |
| padding: var(--spacing-3); |
| } |
| |
| .observation-gate { |
| padding: var(--spacing-3); |
| } |
| |
| .metric-value { |
| font-size: var(--typography-fontSizes-2xl); |
| } |
| } |
| </style> |
| """ |
|
|
| |
| |
| |
| def initialize_modern_ui() -> str: |
| """Initialize all modern UI components and styles""" |
| components = [ |
| inject_design_tokens(), |
| create_component_styles(), |
| ResponsiveUtils.create_responsive_styles(), |
| Accessibility.create_skip_link(), |
| Accessibility.create_aria_live_regions(), |
| DarkMode.create_toggle(), |
| ResponsiveUtils.create_mobile_navigation(), |
| Accessibility.create_focus_management() |
| ] |
| |
| return "\n".join(components) |
|
|
| |
| |
| |
| def create_example_dashboard() -> str: |
| """Create an example dashboard using modern components""" |
| |
| |
| metrics = [ |
| {"value": "42s", "label": "Detection Time", "trend": "down", "change": "β 90%"}, |
| {"value": "94%", "label": "Recall Quality", "trend": "up", "change": "β 8%"}, |
| {"value": "87%", "label": "Confidence Score", "trend": "neutral", "change": "β"}, |
| {"value": "Dampening", "label": "Sequencing Stage", "trend": None} |
| ] |
| |
| |
| detection_process = { |
| "status": "active", |
| "title": "Detection Process", |
| "description": "Telemetry analysis & pattern recognition", |
| "metrics": { |
| "Pattern Match": "Retry Amplification", |
| "Confidence": "92.7%", |
| "Detection Time": "0.8 seconds", |
| "Severity": "HIGH_VARIANCE" |
| }, |
| "content": """ |
| <div class="success-message"> |
| β
Detected: Retry amplification pattern with exponential growth (r=1.8) |
| </div> |
| """, |
| "next_step": "Activate recall process" |
| } |
| |
| |
| dashboard = f""" |
| {initialize_modern_ui()} |
| |
| <div class="container" style="max-width: 1200px; margin: 0 auto;"> |
| <h1>Modern ARF Dashboard</h1> |
| |
| <!-- Metric Grid --> |
| {Grid.create_metric_grid(metrics)} |
| |
| <!-- Observation Gate --> |
| {ObservationGate.create(confidence=65.0)} |
| |
| <!-- Process Display --> |
| {ProcessDisplay.create('detection', detection_process)} |
| |
| <!-- Sequencing Flow --> |
| {SequencingFlow.create([ |
| {"title": "Dampening", "description": "Prevent amplification first", "badge": "REQUIRED"}, |
| {"title": "Concurrency", "description": "Manage load, then observe", "badge": "REQUIRED"}, |
| {"title": "Observe", "description": "Validate trends for 5+ minutes", "badge": "REQUIRED"}, |
| {"title": "Scale", "description": "Only if all previous succeed", "badge": "OPTIONAL"} |
| ])} |
| |
| <!-- Example Cards --> |
| <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; margin-top: 30px;"> |
| {Card.create( |
| title="OSS Analysis", |
| content="Open source advisory intelligence", |
| footer="Apache 2.0 License", |
| variant="outlined", |
| border_color="var(--color-success)" |
| )} |
| |
| {Card.create( |
| title="Enterprise Execution", |
| content="Commercial autonomous execution", |
| footer="Requires License", |
| variant="elevated" |
| )} |
| </div> |
| </div> |
| """ |
| |
| return dashboard |
|
|
| |
| |
| |
| __all__ = [ |
| |
| 'ModernComponent', 'Card', 'Button', 'Badge', |
| 'Grid', 'Stack', |
| |
| |
| 'ObservationGate', 'SequencingFlow', 'ProcessDisplay', |
| |
| |
| 'Chart', |
| |
| |
| 'ResponsiveUtils', 'Accessibility', 'DarkMode', |
| |
| |
| 'initialize_modern_ui', 'create_example_dashboard', |
| |
| |
| 'DESIGN_TOKENS' |
| ] |
|
|
| |
| |
| |
| if __name__ == "__main__": |
| |
| print("Testing modern components...") |
| |
| |
| example = create_example_dashboard() |
| |
| |
| with open("modern_dashboard_example.html", "w") as f: |
| f.write(example) |
| |
| print("β
Modern components created successfully") |
| print("π Example saved to: modern_dashboard_example.html") |