Agentic-Reliability-Framework-API / ui /modern_components.py
petter2025's picture
Create modern_components.py
ea68e5a verified
raw
history blame
59.6 kB
"""
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 TOKEN SYSTEM (CSS Variables in JavaScript)
# ===========================================
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"
}
}
# ===========================================
# CSS VARIABLES INJECTION
# ===========================================
def inject_design_tokens() -> str:
"""Inject CSS variables for design tokens into the page"""
css_variables = []
# Convert design tokens to 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>
"""
# ===========================================
# BASE COMPONENT CLASS
# ===========================================
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>
"""
# ===========================================
# ATOMIC COMPONENTS
# ===========================================
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') # default, elevated, outlined, filled
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) # 'up', 'down', 'neutral'
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') # primary, secondary, danger, ghost
size = kwargs.get('size', 'md') # sm, md, lg
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') # default, success, warning, danger
size = kwargs.get('size', 'md') # sm, md, lg
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>
"""
# ===========================================
# LAYOUT COMPONENTS
# ===========================================
class Grid(ModernComponent):
"""Responsive grid system"""
@staticmethod
def create(children: List[str], **kwargs) -> str:
"""Create a responsive grid"""
columns = kwargs.get('columns', { # responsive column definitions
'default': 1,
'sm': 2,
'lg': 3
})
gap = kwargs.get('gap', 'var(--spacing-4)')
# Convert columns dict to CSS
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') # start, center, end, 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>
"""
# ===========================================
# SPECIALIZED ARF COMPONENTS
# ===========================================
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
# Format countdown if available
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>
""")
# Add connecting lines
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 display
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
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>
"""
# ===========================================
# DATA VISUALIZATION COMPONENTS
# ===========================================
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()
# Add traces
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)
))
# Update layout
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
)
)
# Update axes
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
# ===========================================
# RESPONSIVE UTILITIES
# ===========================================
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>
"""
# ===========================================
# ACCESSIBILITY ENHANCEMENTS
# ===========================================
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>
"""
# ===========================================
# DARK MODE SUPPORT
# ===========================================
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>
"""
# ===========================================
# COMPONENT STYLES (CSS)
# ===========================================
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>
"""
# ===========================================
# INITIALIZATION FUNCTION
# ===========================================
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)
# ===========================================
# EXAMPLE USAGE (for documentation)
# ===========================================
def create_example_dashboard() -> str:
"""Create an example dashboard using modern components"""
# Example metrics
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}
]
# Example process data
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"
}
# Build dashboard
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
# ===========================================
# EXPORT LIST
# ===========================================
__all__ = [
# Core components
'ModernComponent', 'Card', 'Button', 'Badge',
'Grid', 'Stack',
# ARF-specific components
'ObservationGate', 'SequencingFlow', 'ProcessDisplay',
# Visualization
'Chart',
# Utilities
'ResponsiveUtils', 'Accessibility', 'DarkMode',
# Initialization
'initialize_modern_ui', 'create_example_dashboard',
# Constants
'DESIGN_TOKENS'
]
# ===========================================
# TESTING CODE (Remove in production)
# ===========================================
if __name__ == "__main__":
# Test that components can be created
print("Testing modern components...")
# Create example dashboard
example = create_example_dashboard()
# Save to file for inspection
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")