Add: Synthwave design system showcase reference

Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
2026-11-05 01:45:05 +01:00
co-authored by Junie
parent 6ab189adae
commit f59a230709
@@ -0,0 +1,957 @@
<!DOCTYPE html>
<html lang="en" class="h-full">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Angular Aria Synthwave Design System Showcase</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- FontAwesome Icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;600;800;900&family=Rajdhani:wght@400;500;600;700&family=Fira+Code:wght@400;500;600&display=swap" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: {
orbitron: ['Orbitron', 'sans-serif'],
rajdhani: ['Rajdhani', 'sans-serif'],
mono: ['Fira Code', 'monospace'],
},
colors: {
synth: {
bg: 'var(--bg-color)',
pink: 'var(--primary-color)',
cyan: 'var(--secondary-color)',
purple: 'var(--accent-color)',
yellow: 'var(--highlight-color)',
dark: '#0a0518',
glass: 'rgba(15, 12, 35, 0.65)'
}
}
}
}
}
</script>
<style>
:root {
--primary-color: #ff007f;
--secondary-color: #00f0ff;
--accent-color: #7928ca;
--highlight-color: #ffbe0b;
--bg-color: #0d0221;
--grid-color: rgba(255, 0, 127, 0.4);
--glow-color: rgba(0, 240, 255, 0.6);
--blur-quality: 16px;
}
/* Color Theme Presets */
.theme-cyber-magenta {
--primary-color: #ff007f;
--secondary-color: #00f0ff;
--accent-color: #7928ca;
--highlight-color: #ffbe0b;
--grid-color: rgba(255, 0, 127, 0.4);
--glow-color: rgba(0, 240, 255, 0.6);
}
.theme-neon-cyan {
--primary-color: #00f0ff;
--secondary-color: #00ff88;
--accent-color: #0077ff;
--highlight-color: #e0f7fa;
--grid-color: rgba(0, 240, 255, 0.4);
--glow-color: rgba(0, 255, 136, 0.6);
}
.theme-sunset-orange {
--primary-color: #ff5e00;
--secondary-color: #ffb703;
--accent-color: #d62828;
--highlight-color: #fcbf49;
--grid-color: rgba(255, 94, 0, 0.4);
--glow-color: rgba(255, 183, 3, 0.6);
}
.theme-matrix-green {
--primary-color: #00ff66;
--secondary-color: #39ff14;
--accent-color: #008000;
--highlight-color: #ccff00;
--grid-color: rgba(0, 255, 102, 0.4);
--glow-color: rgba(57, 255, 20, 0.6);
}
/* High Contrast Mode Overrides */
.high-contrast {
--bg-color: #000000;
--primary-color: #ffff00 !important;
--secondary-color: #00ffff !important;
--accent-color: #ffffff !important;
--highlight-color: #ffffff !important;
--grid-color: rgba(0, 255, 255, 0.8) !important;
}
.high-contrast .glass-panel {
background: rgba(0, 0, 0, 0.95) !important;
border: 2px solid var(--secondary-color) !important;
box-shadow: 0 0 15px var(--secondary-color) !important;
}
body {
background-color: var(--bg-color);
color: #f1f5f9;
font-family: 'Rajdhani', sans-serif;
overflow-x: hidden;
transition: background-color 0.4s ease;
}
/* Synthwave Background World */
.synth-stage {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 0;
overflow: hidden;
pointer-events: none;
}
/* Sun Graphic */
.synth-sun {
position: absolute;
top: 15%;
left: 50%;
transform: translateX(-50%);
width: 280px;
height: 280px;
border-radius: 50%;
background: linear-gradient(to bottom, #ffbe0b, var(--primary-color), var(--accent-color));
box-shadow: 0 0 80px var(--primary-color), 0 0 120px var(--accent-color);
z-index: 1;
}
/* Sun Bars Cutout Effect */
.synth-sun::after {
content: '';
position: absolute;
inset: 0;
border-radius: 50%;
background: repeating-linear-gradient(
to bottom,
transparent 0px,
transparent 6px,
var(--bg-color) 6px,
var(--bg-color) 12px
);
mask-image: linear-gradient(to bottom, transparent 40%, black 100%);
-webkit-mask-image: linear-gradient(to bottom, transparent 40%, black 100%);
}
/* Horizon Silhouette */
.synth-mountains {
position: absolute;
bottom: 45%;
width: 100%;
height: 160px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 120' preserveAspectRatio='none'%3E%3Cpath d='M0,120 L0,80 L150,110 L300,50 L450,95 L600,30 L750,85 L900,40 L1050,90 L1200,60 L1200,120 Z' fill='%23080214'/%3E%3C/svg%3E");
background-size: cover;
z-index: 2;
}
/* Perspective Grid Floor */
.grid-container {
position: absolute;
bottom: 0;
left: -50%;
width: 200%;
height: 50%;
perspective: 450px;
z-index: 3;
mask-image: linear-gradient(to top, rgba(0,0,0,1) 60%, rgba(0,0,0,0) 100%);
-webkit-mask-image: linear-gradient(to top, rgba(0,0,0,1) 60%, rgba(0,0,0,0) 100%);
}
.grid-plane {
position: absolute;
width: 100%;
height: 200%;
top: 0;
left: 0;
transform: rotateX(75deg);
background-size: 50px 50px;
background-image:
linear-gradient(to right, var(--grid-color) 1px, transparent 1px),
linear-gradient(to bottom, var(--grid-color) 1px, transparent 1px);
animation: moveGrid 2s linear infinite;
}
@keyframes moveGrid {
0% { transform: rotateX(75deg) translateY(0); }
100% { transform: rotateX(75deg) translateY(50px); }
}
/* Glassmorphism Cards */
.glass-panel {
background: var(--synth-glass, rgba(15, 12, 35, 0.65));
backdrop-filter: blur(var(--blur-quality));
-webkit-backdrop-filter: blur(var(--blur-quality));
border: 1px solid rgba(0, 240, 255, 0.25);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.5), inset 0 0 15px rgba(255, 255, 255, 0.03);
border-radius: 16px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.glass-panel:hover {
border-color: var(--secondary-color);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.6), 0 0 15px var(--glow-color);
}
/* Neon Text & Glow Effects */
.neon-text-primary {
color: #ffffff;
text-shadow: 0 0 5px var(--primary-color), 0 0 15px var(--primary-color);
}
.neon-text-secondary {
color: #ffffff;
text-shadow: 0 0 5px var(--secondary-color), 0 0 15px var(--secondary-color);
}
.neon-border {
box-shadow: 0 0 10px var(--secondary-color);
}
/* Cyber Button Style */
.cyber-btn {
position: relative;
background: linear-gradient(135deg, rgba(255, 0, 127, 0.2), rgba(0, 240, 255, 0.2));
border: 1px solid var(--secondary-color);
color: #ffffff;
font-family: 'Orbitron', sans-serif;
text-transform: uppercase;
letter-spacing: 1px;
transition: all 0.2s ease;
overflow: hidden;
}
.cyber-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
transition: 0.5s;
}
.cyber-btn:hover::before {
left: 100%;
}
.cyber-btn:hover {
background: var(--secondary-color);
color: #0d0221;
box-shadow: 0 0 20px var(--secondary-color);
transform: translateY(-2px);
}
.cyber-btn:focus-visible {
outline: 3px solid var(--highlight-color);
outline-offset: 3px;
}
/* ARIA Inspector Focus Highlight */
.aria-inspect-active {
outline: 2px dashed var(--highlight-color) !important;
outline-offset: 4px;
animation: pulseAria 1.5s infinite;
}
@keyframes pulseAria {
0%, 100% { outline-color: var(--highlight-color); }
50% { outline-color: var(--primary-color); }
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: rgba(13, 2, 33, 0.8);
}
::-webkit-scrollbar-thumb {
background: var(--primary-color);
border-radius: 4px;
box-shadow: 0 0 10px var(--primary-color);
}
::-webkit-scrollbar-thumb:hover {
background: var(--secondary-color);
}
</style>
</head>
<body class="min-h-screen text-slate-100 flex flex-col justify-between relative selection:bg-pink-500 selection:text-white">
<!-- SYNTHWAVE ANIMATED BACKGROUND -->
<div class="synth-stage" aria-hidden="true">
<div class="synth-sun"></div>
<div class="synth-mountains"></div>
<div class="grid-container">
<div class="grid-plane"></div>
</div>
</div>
<!-- ACCESSIBILITY LIVE ANNOUNCER REGION (Screen Reader Only) -->
<div id="aria-live-polite" class="sr-only" aria-live="polite" aria-atomic="true"></div>
<div id="aria-live-assertive" class="sr-only" aria-live="assertive" aria-atomic="true"></div>
<!-- CONTENT WRAPPER -->
<div class="relative z-10 flex flex-col min-h-screen">
<!-- HEADER / NAVIGATION -->
<header class="p-4 lg:p-6 border-b border-cyan-500/20 glass-panel rounded-none border-t-0 border-x-0 sticky top-0 z-40 backdrop-blur-xl">
<div class="max-w-7xl mx-auto flex flex-col md:flex-row items-center justify-between gap-4">
<!-- Logo & Branding -->
<div class="flex items-center space-x-3">
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-pink-500 to-cyan-400 flex items-center justify-center text-slate-950 font-black text-xl shadow-[0_0_15px_rgba(255,0,127,0.8)] font-orbitron">
A
</div>
<div>
<h1 class="text-2xl font-extrabold font-orbitron tracking-wider neon-text-primary">
ANGULAR <span class="text-cyan-400 font-normal">ARIA</span>
</h1>
<p class="text-xs text-cyan-300/70 font-mono tracking-widest uppercase">Synthwave Design System Showcase</p>
</div>
</div>
<!-- Stats & Diagnostics Indicator -->
<div class="flex items-center space-x-4 bg-slate-950/60 px-4 py-2 rounded-full border border-pink-500/30 text-xs font-mono">
<div class="flex items-center space-x-2">
<span class="w-2 h-2 rounded-full bg-emerald-400 animate-ping"></span>
<span class="text-slate-300">FPS: <span id="fps-counter" class="text-emerald-400 font-bold">60</span></span>
</div>
<span class="text-slate-600">|</span>
<div class="text-slate-300">CDK: <span class="text-cyan-400 font-bold">v18.2.0</span></div>
<span class="text-slate-600">|</span>
<div class="text-slate-300">A11Y: <span class="text-pink-400 font-bold">100%</span></div>
</div>
<!-- Controls & Palette Selector -->
<div class="flex items-center space-x-3">
<label for="palette-select" class="sr-only">Select Synthwave Theme Palette</label>
<div class="relative">
<select id="palette-select" onchange="changeTheme(this.value)" class="bg-slate-900/90 text-cyan-300 border border-cyan-500/40 rounded-lg px-3 py-1.5 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-pink-500 cursor-pointer">
<option value="theme-cyber-magenta">💖 Cyber Magenta</option>
<option value="theme-neon-cyan">🩵 Neon Cyan</option>
<option value="theme-sunset-orange">🧡 Sunset Orange</option>
<option value="theme-matrix-green">💚 Matrix Green</option>
</select>
</div>
<!-- High Contrast Toggle -->
<button onclick="toggleHighContrast()" id="hc-btn" aria-pressed="false" title="Toggle High Contrast Mode" class="px-3 py-1.5 rounded-lg border border-cyan-500/40 bg-slate-900/80 text-xs font-mono hover:bg-cyan-500/20 focus:outline-none focus:ring-2 focus:ring-pink-500 transition-all flex items-center gap-1.5">
<i class="fa-solid fa-circle-half-stroke text-yellow-400"></i>
<span>Contrast</span>
</button>
</div>
</div>
</header>
<!-- MAIN SHOWCASE GRID -->
<main class="flex-grow max-w-7xl w-full mx-auto p-4 md:p-6 grid grid-cols-1 lg:grid-cols-12 gap-6">
<!-- LEFT COLUMN: CONTROLS & INSPECTOR (4 Cols) -->
<section class="lg:col-span-4 flex flex-col gap-6">
<!-- System Config & A11y Tools Panel -->
<div class="glass-panel p-5">
<h2 class="text-lg font-orbitron font-bold text-cyan-300 flex items-center gap-2 mb-4 border-b border-cyan-500/20 pb-2">
<i class="fa-solid fa-sliders text-pink-500"></i> System A11y Controls
</h2>
<div class="space-y-4 text-sm font-rajdhani">
<!-- Backdrop Blur Quality Slider -->
<div>
<div class="flex justify-between text-xs font-mono text-slate-300 mb-1">
<span>Backdrop Blur Filter</span>
<span id="blur-val" class="text-pink-400">16px</span>
</div>
<input type="range" min="0" max="30" value="16" oninput="updateBlur(this.value)" class="w-full accent-pink-500 bg-slate-900 rounded-lg h-2 cursor-pointer">
</div>
<!-- ARIA Inspector Toggle -->
<div class="flex items-center justify-between p-3 bg-slate-900/50 rounded-lg border border-pink-500/20">
<div>
<div class="font-bold text-slate-200">Inspect ARIA Roles</div>
<div class="text-xs text-slate-400">Highlight A11y attributes on hover</div>
</div>
<button id="inspect-toggle-btn" onclick="toggleAriaInspector()" aria-pressed="false" class="w-12 h-6 bg-slate-800 rounded-full border border-cyan-500/30 relative transition-colors focus:outline-none focus:ring-2 focus:ring-pink-500">
<span id="inspect-toggle-dot" class="absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-slate-400 transition-transform"></span>
</button>
</div>
<!-- Animation Speed Control -->
<div class="flex items-center justify-between p-3 bg-slate-900/50 rounded-lg border border-pink-500/20">
<div>
<div class="font-bold text-slate-200">Grid Perspective Anim</div>
<div class="text-xs text-slate-400">Toggle retro grid motion</div>
</div>
<button id="anim-toggle-btn" onclick="toggleGridAnimation()" aria-pressed="true" class="px-3 py-1 rounded bg-pink-500/20 text-pink-300 border border-pink-500/40 text-xs font-mono font-bold hover:bg-pink-500/30">
ACTIVE
</button>
</div>
</div>
</div>
<!-- REAL-TIME ANNOUNCER LOG TERMINAL -->
<div class="glass-panel p-5 flex flex-col flex-grow min-h-[300px]">
<div class="flex items-center justify-between border-b border-cyan-500/20 pb-2 mb-3">
<h2 class="text-md font-orbitron font-bold text-pink-400 flex items-center gap-2">
<i class="fa-solid fa-terminal text-cyan-400"></i> LiveAnnouncer Stream
</h2>
<button onclick="clearAnnouncerLogs()" class="text-xs text-slate-400 hover:text-cyan-300 font-mono">Clear</button>
</div>
<!-- Log Stream Container -->
<div id="announcer-log-stream" class="flex-grow bg-slate-950/90 rounded-lg p-3 font-mono text-xs overflow-y-auto max-h-[260px] space-y-2 border border-cyan-500/20" role="log" aria-label="Accessibility Announcement Log">
<div class="text-slate-500 italic">[00:00:00] LiveAnnouncer initialized. Ready for ARIA events.</div>
</div>
</div>
</section>
<!-- RIGHT COLUMN: INTERACTIVE COMPONENTS SHOWCASE (8 Cols) -->
<section class="lg:col-span-8 flex flex-col gap-6">
<!-- 1. LIVE SYNTHWAVE METRIC CHART CARD -->
<div class="glass-panel p-6 relative overflow-hidden">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-2 mb-4">
<div>
<h2 class="text-xl font-orbitron font-bold neon-text-secondary">REAL-TIME DATA STREAM</h2>
<p class="text-xs text-slate-400 font-mono">SVG Reactive Chart with Pulsing Nodes & Screen Reader Readout</p>
</div>
<div class="flex items-center space-x-2">
<button id="chart-pulse-btn" onclick="toggleChartStream()" class="cyber-btn px-4 py-1.5 rounded-lg text-xs font-bold">
<i class="fa-solid fa-play mr-1"></i> LIVE STREAM
</button>
</div>
</div>
<!-- Interactive SVG Chart -->
<div class="relative w-full h-48 bg-slate-950/80 rounded-xl p-2 border border-cyan-500/30">
<svg id="synth-chart" class="w-full h-full overflow-visible" viewBox="0 0 500 150" aria-label="Real-time performance metric line chart" role="img">
<defs>
<linearGradient id="chartGradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="var(--secondary-color)" stop-opacity="0.4"/>
<stop offset="100%" stop-color="var(--primary-color)" stop-opacity="0.0"/>
</linearGradient>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
</defs>
<!-- Grid Lines -->
<line x1="0" y1="30" x2="500" y2="30" stroke="rgba(0, 240, 255, 0.1)" stroke-dasharray="4"/>
<line x1="0" y1="75" x2="500" y2="75" stroke="rgba(0, 240, 255, 0.1)" stroke-dasharray="4"/>
<line x1="0" y1="120" x2="500" y2="120" stroke="rgba(0, 240, 255, 0.1)" stroke-dasharray="4"/>
<!-- Area Gradient -->
<polygon id="chart-area" points="0,150 0,75 100,40 200,90 300,30 400,80 500,45 500,150" fill="url(#chartGradient)" />
<!-- Glowing Path -->
<path id="chart-path" d="M 0 75 L 100 40 L 200 90 L 300 30 L 400 80 L 500 45" fill="none" stroke="var(--secondary-color)" stroke-width="3" filter="url(#glow)"/>
<!-- Active Data Points -->
<g id="chart-points">
<circle cx="0" cy="75" r="5" fill="var(--primary-color)" class="animate-pulse"/>
<circle cx="100" cy="40" r="5" fill="var(--secondary-color)"/>
<circle cx="200" cy="90" r="5" fill="var(--primary-color)"/>
<circle cx="300" cy="30" r="5" fill="var(--secondary-color)"/>
<circle cx="400" cy="80" r="5" fill="var(--primary-color)"/>
<circle cx="500" cy="45" r="6" fill="var(--highlight-color)" stroke="#ffffff" stroke-width="2"/>
</g>
</svg>
</div>
<div class="mt-3 flex justify-between items-center text-xs font-mono text-slate-400">
<span>Current Frequency: <strong id="chart-val" class="text-cyan-300">142.8 MHz</strong></span>
<span class="text-pink-400"><i class="fa-solid fa-wifi animate-pulse mr-1"></i> Synthwave Telemetry Connected</span>
</div>
</div>
<!-- 2. ACCESSIBLE SYNTHWAVE ARIA COMPONENTS GRID -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Custom Accessible Dropdown / Aria Select Component -->
<div class="glass-panel p-5 flex flex-col justify-between">
<div>
<h3 class="text-md font-orbitron font-bold text-cyan-300 mb-1">ACCESSIBLE ARIA SELECT</h3>
<p class="text-xs text-slate-400 mb-4">Full Keyboard Navigation (Up/Down/Esc/Enter)</p>
<!-- Custom Select Widget -->
<div class="relative">
<label id="synth-select-label" class="block text-xs font-mono text-pink-400 mb-1">Select Synth Preset Profile:</label>
<button id="synth-select-btn"
type="button"
aria-haspopup="listbox"
aria-expanded="false"
aria-labelledby="synth-select-label synth-select-btn"
onclick="toggleCustomSelect()"
onkeydown="handleSelectKeydown(event)"
class="w-full bg-slate-950/80 border border-cyan-500/40 rounded-xl px-4 py-2.5 text-left font-rajdhani text-sm flex items-center justify-between text-slate-200 focus:outline-none focus:ring-2 focus:ring-pink-500 hover:border-cyan-400 transition-all">
<span id="select-current-val" class="font-semibold text-cyan-300">Outrun Cyberpunk '84</span>
<i class="fa-solid fa-chevron-down text-pink-500 transition-transform duration-200" id="select-arrow"></i>
</button>
<!-- Dropdown Listbox -->
<ul id="synth-select-listbox"
role="listbox"
aria-labelledby="synth-select-label"
tabindex="-1"
class="hidden absolute z-30 w-full mt-2 bg-slate-950/95 border border-pink-500/40 rounded-xl shadow-2xl py-2 backdrop-blur-2xl focus:outline-none space-y-1">
<li id="opt-1" role="option" aria-selected="true" onclick="selectOption('Outrun Cyberpunk \'84', 'opt-1')" class="px-4 py-2 text-sm font-rajdhani cursor-pointer bg-pink-500/20 text-pink-300 flex items-center justify-between hover:bg-pink-500/30">
<span>Outrun Cyberpunk '84</span>
<i class="fa-solid fa-check text-xs"></i>
</li>
<li id="opt-2" role="option" aria-selected="false" onclick="selectOption('Neon Grid Driver', 'opt-2')" class="px-4 py-2 text-sm font-rajdhani cursor-pointer text-slate-300 flex items-center justify-between hover:bg-cyan-500/20 hover:text-cyan-300">
<span>Neon Grid Driver</span>
</li>
<li id="opt-3" role="option" aria-selected="false" onclick="selectOption('Hyper-Vaporwave Sunset', 'opt-3')" class="px-4 py-2 text-sm font-rajdhani cursor-pointer text-slate-300 flex items-center justify-between hover:bg-cyan-500/20 hover:text-cyan-300">
<span>Hyper-Vaporwave Sunset</span>
</li>
<li id="opt-4" role="option" aria-selected="false" onclick="selectOption('Dark Synth Retrowave', 'opt-4')" class="px-4 py-2 text-sm font-rajdhani cursor-pointer text-slate-300 flex items-center justify-between hover:bg-cyan-500/20 hover:text-cyan-300">
<span>Dark Synth Retrowave</span>
</li>
</ul>
</div>
</div>
<div class="mt-4 pt-3 border-t border-slate-800/80 text-[11px] font-mono text-slate-400">
Keyboard support: <span class="text-pink-400">↑/↓</span> navigate, <span class="text-pink-400">Enter</span> select, <span class="text-pink-400">Esc</span> close
</div>
</div>
<!-- Accessible Glass Modal Component Trigger -->
<div class="glass-panel p-5 flex flex-col justify-between">
<div>
<h3 class="text-md font-orbitron font-bold text-pink-400 mb-1">ACCESSIBLE GLASS DIALOG</h3>
<p class="text-xs text-slate-400 mb-4">CDK FocusTrap & Escape Key Dismiss Simulation</p>
<div class="p-4 bg-slate-950/60 rounded-xl border border-pink-500/20 text-center">
<p class="text-xs text-slate-300 mb-3 font-rajdhani">Opens a modal overlay with isolated focus management and screen-reader accessibility.</p>
<button onclick="openGlassModal()" class="cyber-btn w-full py-2.5 rounded-xl font-bold text-xs shadow-lg">
<i class="fa-solid fa-window-restore mr-2"></i> LAUNCH GLASS DIALOG
</button>
</div>
</div>
<div class="mt-4 pt-3 border-t border-slate-800/80 text-[11px] font-mono text-slate-400 flex items-center justify-between">
<span>FocusTrap: <strong class="text-emerald-400">ACTIVE</strong></span>
<span>Aria-Modal: <strong class="text-cyan-400">TRUE</strong></span>
</div>
</div>
</div>
<!-- 3. INTERACTIVE ACCESSIBLE TABS SHOWCASE -->
<div class="glass-panel p-6">
<h3 class="text-md font-orbitron font-bold text-cyan-300 mb-3">ARIA TABS DESIGN SYSTEM PATTERN</h3>
<!-- Tab List -->
<div role="tablist" aria-label="Synthwave Components Specs" class="flex border-b border-cyan-500/30 space-x-2">
<button id="tab-1" role="tab" aria-selected="true" aria-controls="panel-1" onclick="switchTab(1)" class="px-4 py-2 text-sm font-orbitron font-bold border-b-2 border-pink-500 text-pink-400 focus:outline-none focus:text-pink-300">
DESIGN TOKENS
</button>
<button id="tab-2" role="tab" aria-selected="false" aria-controls="panel-2" tabindex="-1" onclick="switchTab(2)" class="px-4 py-2 text-sm font-orbitron font-bold border-b-2 border-transparent text-slate-400 hover:text-cyan-300 focus:outline-none">
ACCESSIBILITY RULES
</button>
<button id="tab-3" role="tab" aria-selected="false" aria-controls="panel-3" tabindex="-1" onclick="switchTab(3)" class="px-4 py-2 text-sm font-orbitron font-bold border-b-2 border-transparent text-slate-400 hover:text-cyan-300 focus:outline-none">
CDK INTEGRATION
</button>
</div>
<!-- Tab Panels -->
<div class="mt-4">
<!-- Panel 1 -->
<div id="panel-1" role="tabpanel" aria-labelledby="tab-1" class="text-sm font-rajdhani space-y-3">
<p class="text-slate-300">The Angular Aria Synthwave Design System relies on high-contrast neon variables blended with frosted glass aesthetics.</p>
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3 font-mono text-xs">
<div class="p-2.5 bg-slate-950/80 rounded-lg border border-pink-500/40">
<div class="w-full h-3 rounded bg-[#ff007f] mb-1"></div>
<span class="text-slate-400">Primary Pink</span>
<div class="text-pink-400 font-bold">#ff007f</div>
</div>
<div class="p-2.5 bg-slate-950/80 rounded-lg border border-cyan-500/40">
<div class="w-full h-3 rounded bg-[#00f0ff] mb-1"></div>
<span class="text-slate-400">Secondary Cyan</span>
<div class="text-cyan-400 font-bold">#00f0ff</div>
</div>
<div class="p-2.5 bg-slate-950/80 rounded-lg border border-purple-500/40">
<div class="w-full h-3 rounded bg-[#7928ca] mb-1"></div>
<span class="text-slate-400">Accent Purple</span>
<div class="text-purple-400 font-bold">#7928ca</div>
</div>
<div class="p-2.5 bg-slate-950/80 rounded-lg border border-yellow-500/40">
<div class="w-full h-3 rounded bg-[#ffbe0b] mb-1"></div>
<span class="text-slate-400">Glow Yellow</span>
<div class="text-yellow-400 font-bold">#ffbe0b</div>
</div>
</div>
</div>
<!-- Panel 2 -->
<div id="panel-2" role="tabpanel" aria-labelledby="tab-2" class="hidden text-sm font-rajdhani space-y-2 text-slate-300">
<ul class="list-disc list-inside space-y-1.5 font-mono text-xs text-cyan-200">
<li>All color combinations maintain WCAG 2.1 AA 4.5:1 minimum contrast ratio.</li>
<li>Focus indicators feature dynamic high-visibility outlines (3px offset).</li>
<li>Screen readers receive real-time announcements via Angular CDK LiveAnnouncer.</li>
<li>Full keyboard navigation supported on all interactive custom controls.</li>
</ul>
</div>
<!-- Panel 3 -->
<div id="panel-3" role="tabpanel" aria-labelledby="tab-3" class="hidden text-sm font-rajdhani text-slate-300">
<pre class="bg-slate-950 p-3 rounded-lg border border-cyan-500/30 text-xs font-mono text-cyan-300 overflow-x-auto"><code>import { LiveAnnouncer } from '@angular/cdk/a11y';
@Component({
selector: 'synth-aria-button',
template: `&lt;button (click)="announce()"&gt;Cyber Action&lt;/button&gt;`
})
export class SynthButtonComponent {
constructor(private announcer: LiveAnnouncer) {}
announce() {
this.announcer.announce('Synthwave telemetry synchronized!', 'polite');
}
}</code></pre>
</div>
</div>
</div>
</section>
</main>
<!-- FOOTER -->
<footer class="p-4 border-t border-cyan-500/20 glass-panel rounded-none border-b-0 border-x-0 mt-6 text-center text-xs font-mono text-slate-400">
<div class="max-w-7xl mx-auto flex flex-col sm:flex-row items-center justify-between gap-2">
<div>ANGULAR ARIA SYNTHWAVE DESIGN SYSTEM &copy; 2026</div>
<div class="flex items-center space-x-4">
<span class="text-cyan-400"><i class="fa-solid fa-universal-access mr-1"></i> A11Y CONFORMANT</span>
<span class="text-pink-400"><i class="fa-solid fa-bolt mr-1"></i> HIGH PERF CSS3D</span>
</div>
</div>
</footer>
</div>
<!-- ACCESSIBLE GLASS MODAL DIALOG (HIDDEN BY DEFAULT) -->
<div id="glass-modal-overlay" class="hidden fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-950/80 backdrop-blur-md transition-opacity" role="dialog" aria-modal="true" aria-labelledby="modal-title" aria-describedby="modal-desc">
<div class="glass-panel max-w-lg w-full p-6 relative border-2 border-cyan-400 shadow-[0_0_50px_rgba(0,240,255,0.4)] animate-in fade-in zoom-in duration-200">
<button onclick="closeGlassModal()" aria-label="Close dialog" class="absolute top-4 right-4 text-slate-400 hover:text-pink-400 focus:outline-none focus:ring-2 focus:ring-pink-500 rounded-lg p-1">
<i class="fa-solid fa-xmark text-xl"></i>
</button>
<div class="flex items-center space-x-3 mb-4">
<div class="w-10 h-10 rounded-full bg-pink-500/20 border border-pink-500 flex items-center justify-center text-pink-400 text-lg font-bold">
<i class="fa-solid fa-shield-halved"></i>
</div>
<div>
<h3 id="modal-title" class="text-lg font-orbitron font-bold text-slate-100">ACCESSIBLE GLASS DIALOG</h3>
<p id="modal-desc" class="text-xs text-cyan-300 font-mono">Focus trapped within modal overlay window</p>
</div>
</div>
<div class="space-y-3 font-rajdhani text-sm text-slate-300 mb-6">
<p>This dialog modal simulates Angular CDK <code class="text-pink-400 font-mono">FocusTrap</code> functionality. Tabbing will loop strictly within this glass dialog.</p>
<div class="p-3 bg-slate-950/80 rounded-lg border border-cyan-500/20 text-xs font-mono">
<div class="text-slate-400">Current Focus Target:</div>
<div id="modal-focus-indicator" class="text-emerald-400 font-bold">#modal-input-1</div>
</div>
<input id="modal-input-1" type="text" placeholder="Type inside accessible modal..." class="w-full bg-slate-900 border border-cyan-500/40 rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:ring-2 focus:ring-pink-500 font-mono">
</div>
<div class="flex justify-end space-x-3">
<button onclick="closeGlassModal()" class="px-4 py-2 rounded-lg text-xs font-mono text-slate-400 hover:text-slate-200 focus:outline-none focus:ring-2 focus:ring-slate-500">
CANCEL [ESC]
</button>
<button onclick="confirmModalAction()" class="cyber-btn px-5 py-2 rounded-lg text-xs font-bold">
CONFIRM ACTION
</button>
</div>
</div>
</div>
<!-- JAVASCRIPT INTERACTIVE LOGIC -->
<script>
// --- STATE MANAGEMENT ---
let currentTheme = 'theme-cyber-magenta';
let isHighContrast = false;
let isAriaInspectorActive = false;
let isGridAnimating = true;
let isChartStreaming = true;
let chartInterval = null;
let lastFocusedElement = null;
// --- INITIALIZATION ---
window.addEventListener('DOMContentLoaded', () => {
initFpsCounter();
startChartStream();
logAnnouncerEvent('System ready. Angular Aria Synthwave components mounted.', 'polite');
// Keyboard accessibility listener for Modal ESC & Inspector
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
closeGlassModal();
closeCustomSelect();
}
});
});
// --- ANNOUNCER LOG SYSTEM (Simulating @angular/cdk/a11y LiveAnnouncer) ---
function logAnnouncerEvent(message, mode = 'polite') {
const time = new Date().toLocaleTimeString();
const logContainer = document.getElementById('announcer-log-stream');
// Screen reader live region update
const liveRegion = document.getElementById(`aria-live-${mode}`);
if (liveRegion) liveRegion.innerText = message;
// Terminal UI update
const logEntry = document.createElement('div');
logEntry.className = 'text-xs border-l-2 pl-2 py-0.5 ' + (mode === 'assertive' ? 'border-pink-500 text-pink-300' : 'border-cyan-400 text-cyan-200');
logEntry.innerHTML = `<span class="text-slate-500 font-mono">[${time}]</span> <span class="uppercase font-bold text-[10px] ${mode === 'assertive' ? 'text-pink-400' : 'text-cyan-400'}">[${mode}]</span> ${message}`;
logContainer.appendChild(logEntry);
logContainer.scrollTop = logContainer.scrollHeight;
}
function clearAnnouncerLogs() {
const logContainer = document.getElementById('announcer-log-stream');
logContainer.innerHTML = '<div class="text-slate-500 italic">[Logs cleared]</div>';
}
// --- THEME & CONTRAST CONTROLS ---
function changeTheme(themeName) {
document.body.classList.remove('theme-cyber-magenta', 'theme-neon-cyan', 'theme-sunset-orange', 'theme-matrix-green');
document.body.classList.add(themeName);
currentTheme = themeName;
logAnnouncerEvent(`Theme changed to ${themeName.replace('theme-', '').replace('-', ' ')}`, 'polite');
}
function toggleHighContrast() {
isHighContrast = !isHighContrast;
const btn = document.getElementById('hc-btn');
if (isHighContrast) {
document.body.classList.add('high-contrast');
btn.setAttribute('aria-pressed', 'true');
btn.classList.add('bg-yellow-400', 'text-slate-950');
logAnnouncerEvent('High Contrast Mode Enabled', 'assertive');
} else {
document.body.classList.remove('high-contrast');
btn.setAttribute('aria-pressed', 'false');
btn.classList.remove('bg-yellow-400', 'text-slate-950');
logAnnouncerEvent('High Contrast Mode Disabled', 'polite');
}
}
function updateBlur(val) {
document.documentElement.style.setProperty('--blur-quality', `${val}px`);
document.getElementById('blur-val').innerText = `${val}px`;
}
function toggleAriaInspector() {
isAriaInspectorActive = !isAriaInspectorActive;
const btn = document.getElementById('inspect-toggle-btn');
const dot = document.getElementById('inspect-toggle-dot');
btn.setAttribute('aria-pressed', isAriaInspectorActive.toString());
if (isAriaInspectorActive) {
dot.style.transform = 'translateX(24px)';
btn.classList.add('bg-pink-500');
document.querySelectorAll('[role], [aria-label], [aria-expanded]').forEach(el => {
el.classList.add('aria-inspect-active');
});
logAnnouncerEvent('ARIA Inspector highlights enabled', 'polite');
} else {
dot.style.transform = 'translateX(0px)';
btn.classList.remove('bg-pink-500');
document.querySelectorAll('.aria-inspect-active').forEach(el => {
el.classList.remove('aria-inspect-active');
});
logAnnouncerEvent('ARIA Inspector highlights disabled', 'polite');
}
}
function toggleGridAnimation() {
isGridAnimating = !isGridAnimating;
const gridPlane = document.querySelector('.grid-plane');
const btn = document.getElementById('anim-toggle-btn');
if (isGridAnimating) {
gridPlane.style.animationPlayState = 'running';
btn.innerText = 'ACTIVE';
btn.className = 'px-3 py-1 rounded bg-pink-500/20 text-pink-300 border border-pink-500/40 text-xs font-mono font-bold hover:bg-pink-500/30';
} else {
gridPlane.style.animationPlayState = 'paused';
btn.innerText = 'PAUSED';
btn.className = 'px-3 py-1 rounded bg-slate-800 text-slate-400 border border-slate-700 text-xs font-mono font-bold';
}
}
// --- CUSTOM ACCESSIBLE SELECT COMPONENT ---
function toggleCustomSelect() {
const btn = document.getElementById('synth-select-btn');
const listbox = document.getElementById('synth-select-listbox');
const isExpanded = btn.getAttribute('aria-expanded') === 'true';
if (isExpanded) {
closeCustomSelect();
} else {
btn.setAttribute('aria-expanded', 'true');
listbox.classList.remove('hidden');
document.getElementById('select-arrow').style.transform = 'rotate(180deg)';
logAnnouncerEvent('Select listbox expanded. Use arrow keys to navigate options.', 'polite');
}
}
function closeCustomSelect() {
const btn = document.getElementById('synth-select-btn');
const listbox = document.getElementById('synth-select-listbox');
btn.setAttribute('aria-expanded', 'false');
listbox.classList.add('hidden');
document.getElementById('select-arrow').style.transform = 'rotate(0deg)';
}
function selectOption(val, optId) {
document.getElementById('select-current-val').innerText = val;
document.querySelectorAll('#synth-select-listbox li').forEach(li => {
li.setAttribute('aria-selected', 'false');
li.className = 'px-4 py-2 text-sm font-rajdhani cursor-pointer text-slate-300 flex items-center justify-between hover:bg-cyan-500/20 hover:text-cyan-300';
});
const selectedLi = document.getElementById(optId);
selectedLi.setAttribute('aria-selected', 'true');
selectedLi.className = 'px-4 py-2 text-sm font-rajdhani cursor-pointer bg-pink-500/20 text-pink-300 flex items-center justify-between hover:bg-pink-500/30';
closeCustomSelect();
logAnnouncerEvent(`Selected synth profile: ${val}`, 'assertive');
}
function handleSelectKeydown(e) {
if (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
toggleCustomSelect();
}
}
// --- GLASS MODAL ACCESSIBILITY ---
function openGlassModal() {
lastFocusedElement = document.activeElement;
const modal = document.getElementById('glass-modal-overlay');
modal.classList.remove('hidden');
document.getElementById('modal-input-1').focus();
logAnnouncerEvent('Glass Modal Dialog Opened. Focus trapped inside modal.', 'assertive');
}
function closeGlassModal() {
const modal = document.getElementById('glass-modal-overlay');
if (!modal.classList.contains('hidden')) {
modal.classList.add('hidden');
if (lastFocusedElement) lastFocusedElement.focus();
logAnnouncerEvent('Glass Modal Dialog Closed. Focus restored to trigger button.', 'polite');
}
}
function confirmModalAction() {
alert('Synthwave telemetry sequence initiated!');
closeGlassModal();
}
// --- TABS SYSTEM ---
function switchTab(tabIdx) {
for (let i = 1; i <= 3; i++) {
const tab = document.getElementById(`tab-${i}`);
const panel = document.getElementById(`panel-${i}`);
if (i === tabIdx) {
tab.setAttribute('aria-selected', 'true');
tab.setAttribute('tabindex', '0');
tab.className = 'px-4 py-2 text-sm font-orbitron font-bold border-b-2 border-pink-500 text-pink-400 focus:outline-none';
panel.classList.remove('hidden');
} else {
tab.setAttribute('aria-selected', 'false');
tab.setAttribute('tabindex', '-1');
tab.className = 'px-4 py-2 text-sm font-orbitron font-bold border-b-2 border-transparent text-slate-400 hover:text-cyan-300 focus:outline-none';
panel.classList.add('hidden');
}
}
logAnnouncerEvent(`Switched to Tab ${tabIdx}`, 'polite');
}
// --- REAL-TIME CHART ANIMATION ---
function startChartStream() {
if (chartInterval) clearInterval(chartInterval);
chartInterval = setInterval(() => {
if (!isChartStreaming) return;
const path = document.getElementById('chart-path');
const area = document.getElementById('chart-area');
// Randomize chart Y points slightly
const p1 = Math.floor(Math.random() * 60) + 30;
const p2 = Math.floor(Math.random() * 70) + 20;
const p3 = Math.floor(Math.random() * 80) + 20;
const p4 = Math.floor(Math.random() * 60) + 30;
const newD = `M 0 75 L 100 ${p1} L 200 ${p2} L 300 ${p3} L 400 ${p4} L 500 45`;
path.setAttribute('d', newD);
area.setAttribute('points', `0,150 0,75 100,${p1} 200,${p2} 300,${p3} 400,${p4} 500,45 500,150`);
const freq = (140 + Math.random() * 10).toFixed(1);
document.getElementById('chart-val').innerText = `${freq} MHz`;
}, 1200);
}
function toggleChartStream() {
isChartStreaming = !isChartStreaming;
const btn = document.getElementById('chart-pulse-btn');
if (isChartStreaming) {
btn.innerHTML = '<i class="fa-solid fa-pause mr-1"></i> PAUSE STREAM';
logAnnouncerEvent('Real-time chart data stream resumed', 'polite');
} else {
btn.innerHTML = '<i class="fa-solid fa-play mr-1"></i> RESUME STREAM';
logAnnouncerEvent('Real-time chart data stream paused', 'polite');
}
}
// --- FPS COUNTER ---
function initFpsCounter() {
let frameCount = 0;
let lastTime = performance.now();
const fpsElem = document.getElementById('fps-counter');
function updateFPS() {
const now = performance.now();
frameCount++;
if (now - lastTime >= 1000) {
fpsElem.innerText = frameCount;
frameCount = 0;
lastTime = now;
}
requestAnimationFrame(updateFPS);
}
requestAnimationFrame(updateFPS);
}
</script>
</body>
</html>