← Accessible Components results

Accessible Components

ling 3.0 tiny:free · full

Initial view of Accessible Components

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 54,240 bytes · SHA-256 fa906dfe9563
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Accessible Component Showcase</title>
<style>
/* ============================================================
   CSS Custom Properties — Tunable Design Tokens
   ============================================================ */
:root {
  --clr-bg: #f5f3ef;
  --clr-surface: #ffffff;
  --clr-surface-alt: #eae8e3;
  --clr-text: #2c2c2c;
  --clr-text-muted: #6b6b6b;
  --clr-accent: #4a6d7c;
  --clr-accent-light: #8bb5c6;
  --clr-accent-hover: #3a5969;
  --clr-danger: #c0504a;
  --clr-danger-hover: #a0403a;
  --clr-success: #4a7040;
  --clr-success-bg: #e6f0e6;
  --clr-warning: #c09030;
  --clr-warning-bg: #fdf3e0;

  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 14px;

  --shadow-sm: 0 1px 3px rgba(0,0,0,0.06);
  --shadow-md: 0 4px 16px rgba(0,0,0,0.08);
  --shadow-lg: 0 8px 32px rgba(0,0,0,0.12);

  --space-xs: 4px;
  --space-sm: 8px;
  --space-md: 16px;
  --space-lg: 24px;
  --space-xl: 32px;
  --space-2xl: 48px;

  --font-sans: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --font-mono: 'SF Mono', 'Fira Code', 'Courier New', monospace;

  --transition-fast: 0.15s ease;
  --transition-smooth: 0.25s ease;
  --transition-slow: 0.35s ease;

  --sidebar-w: 240px;
  --sidebar-collapsed-w: 0px;
  --focus-ring-color: #4a6d7c;
  --focus-ring-offset: 2px;
}

/* ============================================================
   Reset & Base
   ============================================================ */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { font-size: 100%; }
body {
  font-family: var(--font-sans);
  background: var(--clr-bg);
  color: var(--clr-text);
  line-height: 1.6;
  min-height: 100vh;
  overflow-x: hidden;
}

/* ============================================================
   Skip Link
   ============================================================ */
.skip-link {
  position: fixed;
  top: -100px;
  left: 8px;
  background: var(--clr-accent);
  color: #fff;
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-sm);
  z-index: 10000;
  font-weight: 600;
  text-decoration: none;
  transition: top var(--transition-smooth);
}
.skip-link:focus {
  top: 8px;
  outline: 3px solid var(--clr-accent-light);
  outline-offset: 2px;
}

/* ============================================================
   Layout — Sticky Sidebar
   ============================================================ */
.app-layout {
  display: flex;
  min-height: 100vh;
}
.sidebar {
  position: sticky;
  top: 0;
  width: var(--sidebar-w);
  height: 100vh;
  background: var(--clr-surface);
  border-right: 1px solid var(--clr-surface-alt);
  padding: var(--space-lg) var(--space-md);
  overflow-y: auto;
  z-index: 100;
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}
.sidebar-logo {
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--clr-accent);
  letter-spacing: -0.02em;
  margin-bottom: var(--space-xs);
}
.sidebar-nav {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.sidebar-nav a {
  display: block;
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-sm);
  color: var(--clr-text-muted);
  text-decoration: none;
  font-size: 0.9rem;
  transition: background var(--transition-fast), color var(--transition-fast);
  border: 2px solid transparent;
}
.sidebar-nav a:hover {
  background: var(--clr-surface-alt);
  color: var(--clr-text);
}
.sidebar-nav a.active {
  background: var(--clr-accent);
  color: #fff;
  font-weight: 600;
}
.sidebar-nav a:focus-visible {
  outline: 3px solid var(--focus-ring-color);
  outline-offset: 2px;
}
.sidebar-footer {
  margin-top: auto;
  padding-top: var(--space-md);
  font-size: 0.8rem;
  color: var(--clr-text-muted);
}

/* ============================================================
   Main Content
   ============================================================ */
.main-content {
  flex: 1;
  padding: var(--space-xl) var(--space-2xl);
  max-width: 860px;
}
.page-header {
  margin-bottom: var(--space-xl);
}
.page-header h1 {
  font-size: 2rem;
  font-weight: 700;
  color: var(--clr-accent);
  letter-spacing: -0.02em;
  margin-bottom: var(--space-xs);
}
.page-header p {
  color: var(--clr-text-muted);
  font-size: 1rem;
}

/* ============================================================
   Component Sections
   ============================================================ */
.component-section {
  background: var(--clr-surface);
  border: 1px solid var(--clr-surface-alt);
  border-radius: var(--radius-lg);
  padding: var(--space-lg) var(--space-xl);
  margin-bottom: var(--space-lg);
  box-shadow: var(--shadow-sm);
}
.section-header {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  margin-bottom: var(--space-lg);
}
.section-header h2 {
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--clr-accent);
  letter-spacing: -0.01em;
}
.section-header .kbd {
  font-size: 0.75rem;
  background: var(--clr-surface-alt);
  padding: 2px 8px;
  border-radius: 4px;
  font-family: var(--font-mono);
  color: var(--clr-text-muted);
  border: 1px solid var(--clr-surface-alt);
  white-space: nowrap;
}

/* ============================================================
   Card — Component Example
   ============================================================ */
.component-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

/* ============================================================
   Button — Base (for all component buttons)
   ============================================================ */
.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  font-family: var(--font-sans);
  font-size: 0.9rem;
  font-weight: 600;
  border: 2px solid transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
  background: var(--clr-surface-alt);
  color: var(--clr-text);
  transition: background var(--transition-fast), border-color var(--transition-fast), box-shadow var(--transition-fast);
  white-space: nowrap;
  user-select: none;
}
.btn:focus-visible {
  outline: 3px solid var(--focus-ring-color);
  outline-offset: 2px;
}
.btn-primary {
  background: var(--clr-accent);
  color: #fff;
  border-color: var(--clr-accent);
}
.btn-primary:hover {
  background: var(--clr-accent-hover);
}
.btn-ghost {
  background: transparent;
  color: var(--clr-accent);
  border-color: var(--clr-accent);
}
.btn-ghost:hover {
  background: var(--clr-surface-alt);
}
.btn-danger {
  background: var(--clr-danger);
  color: #fff;
  border-color: var(--clr-danger);
}
.btn-danger:hover {
  background: var(--clr-danger-hover);
}
.btn-sm {
  padding: 4px 12px;
  font-size: 0.8rem;
}
.btn-icon {
  padding: 6px 8px;
  min-width: 36px;
  justify-content: center;
}
.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ============================================================
   Input — Base
   ============================================================ */
.input-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}
.input-label {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--clr-text-muted);
}
input[type="text"], input[type="search"], input[type="email"],
input[type="tel"], input[type="url"] {
  font-family: var(--font-sans);
  font-size: 1rem;
  padding: var(--space-sm) var(--space-md);
  border: 2px solid var(--clr-surface-alt);
  border-radius: var(--radius-sm);
  background: var(--clr-surface);
  color: var(--clr-text);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
  width: 100%;
}
input:focus {
  border-color: var(--clr-accent);
  box-shadow: 0 0 0 3px rgba(74,109,124,0.15);
  outline: none;
}
input:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ============================================================
   ===== MODAL DIALOG =====
   ============================================================ */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2000;
  padding: var(--space-lg);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--transition-smooth), visibility var(--transition-smooth);
}
.modal-overlay.open {
  opacity: 1;
  visibility: visible;
}
.modal-dialog {
  background: var(--clr-surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  width: 100%;
  max-width: 520px;
  max-height: 85vh;
  display: flex;
  flex-direction: column;
  transform: translateY(20px);
  transition: transform var(--transition-smooth);
  outline: none;
}
.modal-overlay.open .modal-dialog {
  transform: translateY(0);
}
.modal-header {
  padding: var(--space-md) var(--space-lg);
  border-bottom: 1px solid var(--clr-surface-alt);
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.modal-header h3 {
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--clr-accent);
}
.modal-close {
  background: none;
  border: none;
  font-size: 1.3rem;
  cursor: pointer;
  color: var(--clr-text-muted);
  padding: 4px 8px;
  border-radius: var(--radius-sm);
  line-height: 1;
  transition: background var(--transition-fast), color var(--transition-fast);
}
.modal-close:hover {
  background: var(--clr-surface-alt);
  color: var(--clr-text);
}
.modal-close:focus-visible {
  outline: 3px solid var(--focus-ring-color);
  outline-offset: 2px;
}
.modal-body {
  padding: var(--space-lg);
  overflow-y: auto;
  flex: 1;
}
.modal-body p {
  margin-bottom: var(--space-md);
}
.modal-body .info {
  background: var(--clr-surface-alt);
  padding: var(--space-md);
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
}
.modal-footer {
  padding: var(--space-md) var(--space-lg);
  border-top: 1px solid var(--clr-surface-alt);
  display: flex;
  justify-content: flex-end;
  gap: var(--space-sm);
}
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.45);
  z-index: 1999;
}

/* ============================================================
   ===== DROPDOWN MENU =====
   ============================================================ */
.dropdown {
  position: relative;
  display: inline-block;
}
.dropdown-trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  font-family: var(--font-sans);
  font-size: 0.9rem;
  font-weight: 600;
  border: 2px solid var(--clr-surface-alt);
  border-radius: var(--radius-sm);
  background: var(--clr-surface);
  cursor: pointer;
  color: var(--clr-text);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
  white-space: nowrap;
}
.dropdown-trigger:hover {
  border-color: var(--clr-accent);
}
.dropdown-trigger:focus-visible {
  outline: 3px solid var(--focus-ring-color);
  outline-offset: 2px;
}
.dropdown-trigger[aria-expanded="true"] {
  border-color: var(--clr-accent);
  background: var(--clr-accent);
  color: #fff;
}
.dropdown-trigger[aria-expanded="true"] .dropdown-arrow {
  transform: rotate(180deg);
}
.dropdown-arrow {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(0deg);
  transition: transform var(--transition-fast);
  flex-shrink: 0;
}
.dropdown-menu {
  position: absolute;
  top: calc(100% + var(--space-sm));
  left: 0;
  min-width: 200px;
  background: var(--clr-surface);
  border: 1px solid var(--clr-surface-alt);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  list-style: none;
  padding: var(--space-xs) 0;
  z-index: 1500;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transition: opacity var(--transition-fast), visibility var(--transition-fast), transform var(--transition-fast);
}
.dropdown-menu.open {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
.dropdown-item {
  display: block;
  width: 100%;
  padding: var(--space-sm) var(--space-md);
  text-align: left;
  border: none;
  background: none;
  font-family: var(--font-sans);
  font-size: 0.9rem;
  color: var(--clr-text);
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast);
  outline: none;
}
.dropdown-item:hover,
.dropdown-item:focus-visible {
  background: var(--clr-surface-alt);
  color: var(--clr-accent);
}
.dropdown-item[aria-selected="true"] {
  background: var(--clr-accent);
  color: #fff;
  font-weight: 600;
}
.dropdown-item:focus-visible {
  outline: 2px solid var(--focus-ring-color);
  outline-offset: -2px;
}

/* ============================================================
   ===== TABS =====
   ============================================================ */
.tab-list {
  display: flex;
  border-bottom: 2px solid var(--clr-surface-alt);
  margin-bottom: var(--space-md);
}
.tab {
  flex: 1;
  padding: var(--space-sm) var(--space-md);
  font-family: var(--font-sans);
  font-size: 0.9rem;
  font-weight: 600;
  background: none;
  border: none;
  border-bottom: 3px solid transparent;
  cursor: pointer;
  color: var(--clr-text-muted);
  transition: color var(--transition-fast), border-color var(--transition-fast);
  outline: none;
  position: relative;
}
.tab:hover {
  color: var(--clr-accent);
}
.tab:focus-visible {
  outline: 3px solid var(--focus-ring-color);
  outline-offset: -2px;
  border-radius: var(--radius-sm);
}
.tab[aria-selected="true"] {
  color: var(--clr-accent);
  border-bottom-color: var(--clr-accent);
}
.tab[aria-selected="true"]::after {
  content: '';
  position: absolute;
  bottom: -2px;
  left: 0;
  right: 0;
  height: 3px;
  background: var(--clr-accent);
  border-radius: 2px 2px 0 0;
}

.tab-panel {
  display: none;
}
.tab-panel[hidden] {
  display: none;
}
.tab-panel:not([hidden]) {
  display: block;
}
.tab-panel p {
  margin-bottom: var(--space-md);
  line-height: 1.7;
}
.tab-panel p:last-child {
  margin-bottom: 0;
}

/* ============================================================
   ===== COMBOBOX =====
   ============================================================ */
.combobox-wrap {
  position: relative;
  display: inline-block;
  width: 100%;
  max-width: 400px;
}
.combobox-input {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  border: 2px solid var(--clr-surface-alt);
  border-radius: var(--radius-sm);
  background: var(--clr-surface);
  font-family: var(--font-sans);
  font-size: 1rem;
  color: var(--clr-text);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
  width: 100%;
}
.combobox-input:focus {
  border-color: var(--clr-accent);
  box-shadow: 0 0 0 3px rgba(74,109,124,0.15);
  outline: none;
}
.combobox-input::placeholder {
  color: var(--clr-text-muted);
  font-weight: 400;
}
.combobox-input[aria-expanded="true"] {
  border-color: var(--clr-accent);
  border-width: 2px;
}
.combobox-input .combo-arrow {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
  flex-shrink: 0;
  pointer-events: none;
}

.combobox-options {
  position: absolute;
  top: calc(100% + var(--space-xs));
  left: 0;
  right: 0;
  background: var(--clr-surface);
  border: 1px solid var(--clr-surface-alt);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  max-height: 250px;
  overflow-y: auto;
  z-index: 1500;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transition: opacity var(--transition-fast), visibility var(--transition-fast), transform var(--transition-fast);
  list-style: none;
}
.combobox-options.open {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
.combobox-option {
  padding: var(--space-sm) var(--space-md);
  cursor: pointer;
  font-size: 0.9rem;
  border: none;
  background: none;
  width: 100%;
  text-align: left;
  color: var(--clr-text);
  font-family: var(--font-sans);
  transition: background var(--transition-fast), color var(--transition-fast);
  outline: none;
}
.combobox-option:hover,
.combobox-option:focus-visible {
  background: var(--clr-surface-alt);
  color: var(--clr-accent);
}
.combobox-option[aria-selected="true"] {
  background: var(--clr-accent);
  color: #fff;
  font-weight: 600;
}
.combobox-option[hidden] {
  display: none;
}
.combobox-option[aria-selected="true"] + .combobox-option[hidden] {
  display: none;
}

/* ============================================================
   ===== ACCORDION =====
   ============================================================ */
.accordion-group {
  margin-bottom: var(--space-md);
  border: 1px solid var(--clr-surface-alt);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.accordion-item {
  border-bottom: 1px solid var(--clr-surface-alt);
}
.accordion-item:last-child {
  border-bottom: none;
}
.accordion-trigger {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: var(--space-md) var(--space-lg);
  background: var(--clr-surface);
  border: none;
  font-family: var(--font-sans);
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--clr-text);
  cursor: pointer;
  text-align: left;
  transition: background var(--transition-fast);
  outline: none;
}
.accordion-trigger:hover {
  background: var(--clr-surface-alt);
}
.accordion-trigger:focus-visible {
  outline: 3px solid var(--focus-ring-color);
  outline-offset: -2px;
  border-radius: var(--radius-sm);
}
.accordion-trigger[aria-expanded="true"] {
  color: var(--clr-accent);
  background: var(--clr-accent);
  color: #fff;
}
.accordion-trigger[aria-expanded="true"] .accordion-icon {
  transform: rotate(180deg);
}
.accordion-icon {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--clr-accent);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 0.7rem;
  flex-shrink: 0;
  transition: transform var(--transition-smooth);
}
.accordion-body {
  max-height: 0;
  overflow: hidden;
  transition: max-height var(--transition-slow) ease;
  background: var(--clr-surface);
}
.accordion-body[hidden] {
  display: none;
}
.accordion-body:not([hidden]) {
  display: block;
}
.accordion-body-inner {
  padding: var(--space-md) var(--space-lg);
  font-size: 0.9rem;
  color: var(--clr-text-muted);
  line-height: 1.7;
}

/* ============================================================
   Misc & Utilities
   ============================================================ */
.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;
}

.kbd {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  height: 22px;
  padding: 0 5px;
  font-size: 0.7rem;
  font-family: var(--font-mono);
  background: var(--clr-surface-alt);
  border: 1px solid var(--clr-surface-alt);
  border-radius: 3px;
  color: var(--clr-text-muted);
  line-height: 1;
}

.hint {
  font-size: 0.8rem;
  color: var(--clr-text-muted);
  margin-top: var(--space-xs);
}

/* ============================================================
   Reduced Motion
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}

/* ============================================================
   Responsive
   ============================================================ */
@media (max-width: 640px) {
  .sidebar {
    width: var(--sidebar-collapsed-w);
    position: fixed;
    left: 0;
    top: 0;
    bottom: 0;
    z-index: 2000;
    transform: translateX(-100%);
    transition: transform var(--transition-smooth);
  }
  .sidebar.open {
    transform: translateX(0);
  }
  .main-content {
    padding: var(--space-md);
  }
  .modal-overlay {
    padding: var(--space-md);
  }
}

/* ============================================================
   Focus visible — global
   ============================================================ */
:focus-visible {
  outline: 3px solid var(--focus-ring-color);
  outline-offset: 2px;
}

/* ============================================================
   Live region for announcements
   ============================================================ */
.live-region {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}
</style>
</head>
<body>

<!-- Skip to main content -->
<a href="#main-content" class="skip-link">Skip to main content</a>

<!-- Live region for dynamic announcements -->
<div id="aria-live" class="sr-only" aria-live="polite" aria-atomic="true"></div>

<div class="app-layout">
  <!-- Sidebar -->
  <aside class="sidebar" aria-label="Component navigation">
    <div class="sidebar-logo" aria-hidden="true">🏠 Components</div>
    <ul class="sidebar-nav" role="navigation">
      <li><a href="#section-modal" class="active" aria-current="page">Modal</a></li>
      <li><a href="#section-dropdown">Dropdown</a></li>
      <li><a href="#section-tabs">Tabs</a></li>
      <li><a href="#section-combobox">Combobox</a></li>
      <li><a href="#section-accordion">Accordion</a></li>
    </ul>
    <div class="sidebar-footer">
      <p><strong>Keyboard Contract</strong></p>
      <p style="font-size:0.75rem;margin-top:4px;">
        Modal: Tab trap &middot; Esc to close<br>
        Dropdown: ↓ arrow, Enter, Esc, Tab<br>
        Tabs: Arrow keys, Enter, Esc<br>
        Combobox: Arrow, Enter, Esc<br>
        Accordion: Enter/Space, Esc
      </p>
    </div>
  </aside>

  <!-- Main content -->
  <main id="main-content" class="main-content" tabindex="-1">
    <header class="page-header">
      <h1>Accessible Component Showcase</h1>
      <p>A polished demonstration of five keyboard-accessible UI widgets. Every component follows WAI-ARIA Authoring Practices and works fully without a mouse.</p>
    </header>

    <!-- 1. Modal -->
    <section id="section-modal" class="component-section" aria-labelledby="h-modal">
      <div class="section-header">
        <h2 id="h-modal">Modal Dialog</h2>
        <span class="kbd">Tab · Esc · Click backdrop</span>
      </div>
      <div class="component-card">
        <p>A dialog opens from a button. Focus is trapped inside while open. Press <span class="kbd">Esc</span> or click the backdrop to close. Focus returns to the trigger button on close.</p>
        <button class="btn btn-primary" id="btn-modal-open" aria-haspopup="dialog" aria-modal="true" aria-labelledby="modal-title">
          Open Modal
        </button>
        <div id="modal-dialog" class="modal-overlay" role="dialog" aria-modal="true" aria-labelledby="modal-title" aria-hidden="true">
          <div class="modal-dialog">
            <div class="modal-header">
              <h3 id="modal-title">Welcome to the Modal</h3>
              <button class="modal-close" id="modal-close" aria-label="Close dialog">✕</button>
            </div>
            <div class="modal-body">
              <p>This is a modal dialog. It is a <strong>focus trap</strong> — Tab and Shift+Tab cycle only within the dialog.</p>
              <p>The page behind the dialog is inert. You cannot tab out of it.</p>
              <div class="info">
                <strong>Keyboard contract:</strong> Tab → focus trap · Esc → close · Backdrop click → close · Focus moves in on open, back on trigger on close.
              </div>
            </div>
            <div class="modal-footer">
              <button class="btn btn-ghost btn-sm" id="modal-cancel" aria-label="Cancel">Cancel</button>
              <button class="btn btn-primary btn-sm" id="modal-ok">Confirm</button>
            </div>
          </div>
        </div>
      </div>
    </section>

    <!-- 2. Dropdown -->
    <section id="section-dropdown" class="component-section" aria-labelledby="h-dropdown">
      <div class="section-header">
        <h2 id="h-dropdown">Dropdown Menu</h2>
        <span class="kbd">↓ arrow · Enter · Esc · Tab</span>
      </div>
      <div class="component-card">
        <p>A dropdown with keyboard navigation. Press <span class="kbd">↓</span> to open; use <span class="kbd">↑↓</span> to navigate items, <span class="kbd">Home</span>/<span class="kbd">End</span> for first/last, <span class="kbd">Enter</span> or <span class="kbd">Space</span> to select, <span class="kbd">Esc</span> to close. The button gets focus.</p>
        <div class="dropdown" id="dropdown-menu">
          <div class="dropdown-trigger" id="dropdown-trigger" role="button" aria-haspopup="menu" aria-expanded="false" aria-label="Select an option">
            <span>Select an option</span>
            <span class="dropdown-arrow" aria-hidden="true"></span>
          </div>
          <ul class="dropdown-menu" role="menu" aria-labelledby="dropdown-trigger" id="dropdown-list">
            <li><button class="dropdown-item" role="menuitem" tabindex="-1">Item One</button></li>
            <li><button class="dropdown-item" role="menuitem" tabindex="-1">Item Two</button></li>
            <li><button class="dropdown-item" role="menuitem" tabindex="-1">Item Three</button></li>
            <li><button class="dropdown-item" role="menuitem" tabindex="-1">Item Four</button></li>
            <li><button class="dropdown-item" role="menuitem" tabindex="-1">Item Five</button></li>
            <li><button class="dropdown-item" role="menuitem" tabindex="-1">Item Six</button></li>
          </ul>
        </div>
      </div>
    </section>

    <!-- 3. Tabs -->
    <section id="section-tabs" class="component-section" aria-labelledby="h-tabs">
      <div class="section-header">
        <h2 id="h-tabs">Tabs</h2>
        <span class="kbd">← → · Enter · Esc</span>
      </div>
      <div class="component-card">
        <p>Tabs with `role="tablist"` / `role="tab"` / `role="tabpanel"`. Only the active tab is tabbable. Arrow keys move between tabs. Hidden panels use `hidden` attribute.</p>
        <div class="tab-list" role="tablist" id="tablist" aria-label="Sample tabs">
          <button class="tab" role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1" tabindex="0">Overview</button>
          <button class="tab" role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2" tabindex="-1">Features</button>
          <button class="tab" role="tab" aria-selected="false" aria-controls="panel-3" id="tab-3" tabindex="-1">Changelog</button>
        </div>
        <div class="tab-panel" id="panel-1" role="tabpanel" aria-labelledby="tab-1" hidden>
          <p>This is the <strong>Overview</strong> tab. It shows the main content of the page.</p>
          <p>All content is keyboard-accessible. The active tab receives focus when you tab into it.</p>
        </div>
        <div class="tab-panel" id="panel-2" role="tabpanel" aria-labelledby="tab-2" hidden>
          <p>This is the <strong>Features</strong> tab. Here you'll find detailed feature descriptions.</p>
          <p>Each feature is described with full keyboard support. You can navigate between tabs using arrow keys.</p>
        </div>
        <div class="tab-panel" id="panel-3" role="tabpanel" aria-labelledby="tab-3" hidden>
          <p>This is the <strong>Changelog</strong> tab. See the history of changes and updates.</p>
          <p>All content is reachable via Tab. Hidden panels are not focusable.</p>
        </div>
      </div>
    </section>

    <!-- 4. Combobox -->
    <section id="section-combobox" class="component-section" aria-labelledby="h-combobox">
      <div class="section-header">
        <h2 id="h-combobox">Combobox (Autocomplete)</h2>
        <span class="kbd">↑ ↓ · Enter · Esc · Type</span>
      </div>
      <div class="component-card">
        <p>A text input with autocomplete. Typing filters a list of ~20 options. Use <span class="kbd">↑↓</span> to navigate, <span class="kbd">Enter</span> to select, <span class="kbd">Esc</span> to close. The listbox is never focusable — <code>aria-activedescendant</code> manages the highlight.</p>
        <div class="combobox-wrap" id="combobox-wrap">
          <input class="combobox-input" id="combobox-input" role="combobox" aria-expanded="false" aria-controls="combobox-list" aria-activedescendant="" aria-label="Search and select from a list" autocomplete="off" type="text">
          <ul class="combobox-options" id="combobox-list" role="listbox" aria-label="Search options" hidden>
          </ul>
        </div>
        <p class="hint">Tip: Focus the input and start typing to filter. Use <span class="kbd">↑↓</span> to navigate, <span class="kbd">Enter</span> to select.</p>
      </div>
    </section>

    <!-- 5. Accordion -->
    <section id="section-accordion" class="component-section" aria-labelledby="h-accordion">
      <div class="section-header">
        <h2 id="h-accordion">Accordion / Disclosure</h2>
        <span class="kbd">Enter · Space · Esc</span>
      </div>
      <div class="component-card">
        <p>Multiple accordion items can be open simultaneously. Use <span class="kbd">Enter</span> or <span class="kbd">Space</span> to toggle. Each accordion has a trigger button with `aria-expanded` and `aria-controls`, and the body animates closed without removing `hidden`.</p>
        <div class="accordion-group">
          <div class="accordion-item">
            <button class="accordion-trigger" aria-expanded="false" aria-controls="accordion-body-1" id="accordion-trigger-1">
              <span>How to use the dropdown?</span>
              <span class="accordion-icon" aria-hidden="true">▼</span>
            </button>
            <div class="accordion-body" id="accordion-body-1" hidden>
              <div class="accordion-body-inner">
                <p>To open the dropdown, click the button or press <span class="kbd">Enter</span> or <span class="kbd">Space</span>.</p>
                <p>Use <span class="kbd">↓</span> to move through items and <span class="kbd">↑</span> to go up. Press <span class="kbd">Esc</span> to close.</p>
              </div>
            </div>
          </div>
          <div class="accordion-item">
            <button class="accordion-trigger" aria-expanded="false" aria-controls="accordion-body-2" id="accordion-trigger-2">
              <span>What is a focus trap?</span>
              <span class="accordion-icon" aria-hidden="true">▼</span>
            </button>
            <div class="accordion-body" id="accordion-body-2" hidden>
              <div class="accordion-body-inner">
                <p>A focus trap keeps the keyboard focus within the open element — tab cycles only inside it.</p>
                <p>While a modal is open, the page behind is not focusable. The modal's focus returns to the trigger on close.</p>
              </div>
            </div>
          </div>
          <div class="accordion-item">
            <button class="accordion-trigger" aria-expanded="false" aria-controls="accordion-body-3" id="accordion-trigger-3">
              <span>What are ARIA attributes?</span>
              <span class="accordion-icon" aria-hidden="true">▼</span>
            </button>
            <div class="accordion-body" id="accordion-body-3" hidden>
              <div class="accordion-body-inner">
                <p>ARIA attributes (role, aria-*) describe the widget's semantics to assistive technologies.</p>
                <p>Key attributes: `role` defines the widget type, `aria-expanded` reflects state, `aria-controls` points to the controlled region.</p>
              </div>
            </div>
          </div>
          <div class="accordion-item">
            <button class="accordion-trigger" aria-expanded="false" aria-controls="accordion-body-4" id="accordion-trigger-4">
              <span>How to animate open/close?</span>
              <span class="accordion-icon" aria-hidden="true">▼</span>
            </button>
            <div class="accordion-body" id="accordion-body-4" hidden>
              <div class="accordion-body-inner">
                <p>Animate height for smooth transitions. Use `hidden` attribute for accessibility — the element should not be in the tab order while closed.</p>
                <p>The `hidden` attribute is the accessibility way to hide content. Animate the height while keeping `hidden` off.</p>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>
  </main>
</div>

<script>
/* ============================================================
   Accessible Component Showcase — Application Logic
   ============================================================ */

(function () {
  'use strict';

  /* ---------- Constants ---------- */
  const STORAGE_KEY = 'accessible-showcase-state';
  const REDUCED_MOTION = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

  /* ---------- Utility: announce to screen readers ---------- */
  const announce = (message) => {
    const live = document.getElementById('aria-live');
    if (live) {
      live.textContent = '';
      setTimeout(() => { live.textContent = message; }, 50);
    }
  };

  /* ---------- Utility: focus trap ---------- */
  let _focusTrapCleanup = null;

  function trapFocus(container) {
    if (_focusTrapCleanup) _focusTrapCleanup();

    const focusable = container.querySelectorAll(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    );
    const first = focusable[0];
    const last = focusable[focusable.length - 1];

    function onTab(e) {
      if (e.key !== 'Tab') return;

      if (e.shiftKey) {
        // Shift+Tab: if focus is on first, wrap to last
        if (document.activeElement === first) {
          e.preventDefault();
          last.focus();
        }
      } else {
        // Tab: if focus is on last, wrap to first
        if (document.activeElement === last) {
          e.preventDefault();
          first.focus();
        }
      }
    }

    // Remove any existing handler and add new
    container.addEventListener('keydown', onTab);

    // Focus the first focusable element when opened
    if (first) first.focus();

    return () => {
      container.removeEventListener('keydown', onTab);
      document.activeElement?.blur();
    };
  }

  /* ---------- Utility: roving tabindex for combobox ---------- */
  let _rovingIndex = 0;

  function rovingActiveDescendant(listbox) {
    const options = listbox.querySelectorAll('.combobox-option');
    const visible = Array.from(options).filter(opt => !opt.hidden);
    if (visible.length === 0) return;
    const current = document.activeElement;

    if (current && current.getAttribute('role') === 'option') {
      // Already inside the listbox
      return;
    }

    // Activate the first visible option
    const target = visible[0];
    listbox.setAttribute('aria-activedescendant', target.id);
    target.setAttribute('tabindex', '0');
    target.focus();
  }

  /* ============================================================
     1. Modal
     ============================================================ */
  const modalOverlay = document.getElementById('modal-dialog');
  const modalTrigger = document.getElementById('btn-modal-open');
  const modalClose = document.getElementById('modal-close');
  const modalCancel = document.getElementById('modal-cancel');
  const modalOk = document.getElementById('modal-ok');
  let modalActive = false;
  let modalFocusTrap = null;
  let modalTriggerBtn = null;

  function openModal() {
    modalActive = true;
    modalTriggerBtn = document.activeElement;
    modalOverlay.setAttribute('aria-hidden', 'false');
    modalOverlay.classList.add('open');
    modalOverlay.setAttribute('aria-hidden', 'false');

    // Trap focus within modal
    modalFocusTrap = trapFocus(modalOverlay);
    modalOverlay.addEventListener('keydown', modalKeyHandler);

    // Focus the first focusable element in the modal
    const focusableInModal = modalOverlay.querySelectorAll(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    );
    if (focusableInModal[0]) focusableInModal[0].focus();

    // If the trigger button has focus, move it out
    if (document.activeElement === modalTrigger) {
      modalTriggerBtn.focus();
    }

    announce('Modal dialog opened. Press Esc to close.');
  }

  function closeModal() {
    modalActive = false;
    modalOverlay.classList.remove('open');
    modalOverlay.setAttribute('aria-hidden', 'true');
    modalOverlay.removeEventListener('keydown', modalKeyHandler);

    if (modalFocusTrap) {
      modalFocusTrap();
      modalFocusTrap = null;
    }

    // Return focus to trigger
    if (modalTriggerBtn) {
      modalTriggerBtn.focus();
      modalTriggerBtn = null;
    }

    announce('Modal dialog closed.');
  }

  function modalKeyHandler(e) {
    if (e.key === 'Escape') {
      e.preventDefault();
      closeModal();
      return;
    }

    // Trap focus within modal
    if (e.key === 'Tab') {
      const focusable = modalOverlay.querySelectorAll(
        'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
      );
      const first = focusable[0];
      const last = focusable[focusable.length - 1];

      if (document.activeElement === first && e.shiftKey) {
        e.preventDefault();
        last.focus();
      } else if (document.activeElement === last && !e.shiftKey) {
        e.preventDefault();
        first.focus();
      }
    }
  }

  function handleModalBackdropClick(e) {
    if (e.target === modalOverlay) {
      closeModal();
    }
  }

  // Modal event listeners
  modalTrigger.addEventListener('click', openModal);
  modalClose.addEventListener('click', closeModal);
  modalCancel.addEventListener('click', closeModal);
  modalOk.addEventListener('click', closeModal);
  modalOverlay.addEventListener('click', handleModalBackdropClick);

  /* ============================================================
     2. Dropdown
     ============================================================ */
  const dropdownTrigger = document.getElementById('dropdown-trigger');
  const dropdownList = document.getElementById('dropdown-list');
  let dropdownOpen = false;

  function openDropdown() {
    if (dropdownOpen) return;
    dropdownOpen = true;
    dropdownTrigger.setAttribute('aria-expanded', 'true');
    dropdownList.classList.add('open');
    dropdownList.setAttribute('aria-hidden', 'false');
    dropdownList.setAttribute('aria-expanded', 'true');

    // Focus first item
    const firstItem = dropdownList.querySelector('.dropdown-item');
    if (firstItem) {
      firstItem.setAttribute('aria-selected', 'true');
      firstItem.focus();
    }
    announce('Dropdown menu opened.');
  }

  function closeDropdown() {
    dropdownOpen = false;
    dropdownTrigger.setAttribute('aria-expanded', 'false');
    dropdownList.classList.remove('open');
    dropdownList.setAttribute('aria-hidden', 'true');

    // Clear aria-selected from all items
    dropdownList.querySelectorAll('.dropdown-item').forEach(item => {
      item.removeAttribute('aria-selected');
      item.setAttribute('tabindex', '-1');
    });
    announce('Dropdown menu closed.');
  }

  function toggleDropdown() {
    if (dropdownOpen) {
      closeDropdown();
    } else {
      openDropdown();
    }
  }

  dropdownTrigger.addEventListener('click', toggleDropdown);
  dropdownTrigger.addEventListener('keydown', (e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      toggleDropdown();
    }
  });

  // Keyboard navigation inside dropdown
  dropdownList.addEventListener('keydown', (e) => {
    if (e.key === 'Escape') {
      e.preventDefault();
      closeDropdown();
      dropdownTrigger.focus();
      return;
    }

    if (e.key === 'ArrowDown') {
      e.preventDefault();
      openDropdown();
      const items = dropdownList.querySelectorAll('.dropdown-item');
      const currentItem = dropdownList.querySelector('[aria-selected="true"]');
      const currentIdx = Array.from(items).indexOf(currentItem);

      // Focus first item
      if (currentIdx < items.length - 1) {
        items[currentIdx + 1].focus();
      } else {
        items[0].focus();
      }
      return;
    }

    if (e.key === 'ArrowUp') {
      e.preventDefault();
      openDropdown();
      const items = dropdownList.querySelectorAll('.dropdown-item');
      const currentItem = dropdownList.querySelector('[aria-selected="true"]');
      const currentIdx = Array.from(items).indexOf(currentItem);

      // Focus last item
      if (currentIdx > 0) {
        items[currentIdx - 1].focus();
      } else {
        items[items.length - 1].focus();
      }
      return;
    }

    if (e.key === 'Home') {
      e.preventDefault();
      openDropdown();
      const items = dropdownList.querySelectorAll('.dropdown-item');
      if (items.length > 0) items[0].focus();
      return;
    }

    if (e.key === 'End') {
      e.preventDefault();
      openDropdown();
      const items = dropdownList.querySelectorAll('.dropdown-item');
      if (items.length > 0) items[items.length - 1].focus();
      return;
    }
  });

  // Click on dropdown item
  dropdownList.addEventListener('click', (e) => {
    const item = e.target.closest('.dropdown-item');
    if (item) {
      item.setAttribute('aria-selected', 'true');
      dropdownList.querySelectorAll('.dropdown-item').forEach(i => i.removeAttribute('aria-selected'));
      closeDropdown();
      announce('Selected: ' + item.textContent);
    }
  });

  // Keyboard on dropdown item
  dropdownList.addEventListener('keydown', (e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      const item = e.target.closest('.dropdown-item');
      if (item) {
        item.setAttribute('aria-selected', 'true');
        dropdownList.querySelectorAll('.dropdown-item').forEach(i => i.removeAttribute('aria-selected'));
        closeDropdown();
        announce('Selected: ' + item.textContent);
      }
    }
  });

  // Arrow key navigation within dropdown items (for keyboard accessibility)
  dropdownList.addEventListener('keydown', (e) => {
    if (e.key === 'ArrowDown') {
      const items = dropdownList.querySelectorAll('.dropdown-item');
      const currentIdx = Array.from(items).indexOf(dropdownList.querySelector('[aria-selected="true"]'));
      if (currentIdx < items.length - 1) {
        e.preventDefault();
        items[currentIdx + 1].focus();
      }
    }
    if (e.key === 'ArrowUp') {
      const items = dropdownList.querySelectorAll('.dropdown-item');
      const currentIdx = Array.from(items).indexOf(dropdownList.querySelector('[aria-selected="true"]'));
      if (currentIdx > 0) {
        e.preventDefault();
        items[currentIdx - 1].focus();
      }
    }
  });

  /* ============================================================
     3. Tabs
     ============================================================ */
  const tablist = document.getElementById('tablist');
  const tabs = tablist.querySelectorAll('.tab');
  const panels = tablist.querySelectorAll('.tab-panel');
  let activeTab = tabs[0];

  function switchTab(tab) {
    tabs.forEach(t => {
      t.setAttribute('aria-selected', 'false');
      t.setAttribute('tabindex', '-1');
    });
    panels.forEach(p => {
      p.setAttribute('hidden', 'true');
    });

    tab.setAttribute('aria-selected', 'true');
    tab.setAttribute('tabindex', '0');

    const panelId = tab.getAttribute('aria-controls');
    const panel = document.getElementById(panelId);
    if (panel) {
      panel.removeAttribute('hidden');
    }

    // Focus the tab
    tab.focus();
    announce('Tab switched to: ' + tab.textContent);
  }

  tablist.addEventListener('click', (e) => {
    const tab = e.target.closest('.tab');
    if (tab && tab !== activeTab) {
      switchTab(tab);
    }
  });

  tablist.addEventListener('keydown', (e) => {
    if (e.key === 'ArrowRight' && e.ctrlKey) {
      e.preventDefault();
      const idx = Array.from(tabs).indexOf(activeTab);
      const next = tabs[(idx + 1) % tabs.length];
      switchTab(next);
    }
    if (e.key === 'ArrowLeft' && e.ctrlKey) {
      e.preventDefault();
      const idx = Array.from(tabs).indexOf(activeTab);
      const prev = tabs[(idx - 1 + tabs.length) % tabs.length];
      switchTab(prev);
    }
    if (e.key === 'ArrowRight' && !e.ctrlKey) {
      e.preventDefault();
      const idx = Array.from(tabs).indexOf(activeTab);
      const next = tabs[(idx + 1) % tabs.length];
      switchTab(next);
    }
    if (e.key === 'ArrowLeft' && !e.ctrlKey) {
      e.preventDefault();
      const idx = Array.from(tabs).indexOf(activeTab);
      const prev = tabs[(idx - 1 + tabs.length) % tabs.length];
      switchTab(prev);
    }
    if (e.key === 'Home') {
      e.preventDefault();
      switchTab(tabs[0]);
    }
    if (e.key === 'End') {
      e.preventDefault();
      switchTab(tabs[tabs.length - 1]);
    }
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      switchTab(activeTab);
    }
    if (e.key === 'Escape') {
      e.preventDefault();
      // Hide all panels, move focus to trigger
      panels.forEach(p => p.setAttribute('hidden', 'true'));
      tabs.forEach(t => t.setAttribute('aria-selected', 'false'));
      activeTab.setAttribute('tabindex', '-1');
      activeTab.focus();
      announce('Tab deselected.');
    }
  });

  // Initialize: ensure first tab is visible
  switchTab(tabs[0]);

  /* ============================================================
     4. Combobox
     ============================================================ */
  const comboboxInput = document.getElementById('combobox-input');
  const comboboxList = document.getElementById('combobox-list');
  const comboboxWrap = document.getElementById('combobox-wrap');
  let comboboxOpen = false;
  let comboboxOptions = [];
  let comboboxHighlightIdx = -1;
  const comboboxOptionsData = [
    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
    'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
    'U', 'V', 'W', 'X', 'Y', 'Z'
  ];

  function buildComboboxOptions(filter) {
    comboboxOptions = comboboxOptionsData.filter(opt =>
      opt.toLowerCase().includes((filter || '').toLowerCase())
    );
    comboboxHighlightIdx = -1;

    comboboxList.innerHTML = comboboxOptions.map((opt, idx) =>
      `<li class="combobox-option${idx === 0 ? ' aria-selected' : ''}" id="combobox-opt-${idx}" role="option" tabindex="-1">${opt}</li>`
    ).join('');

    // Store the option elements for roving tabindex
    comboboxOptions.forEach((opt, idx) => {
      comboboxList.querySelector(`#combobox-opt-${idx}`).id = `combobox-opt-${idx}`;
    });

    if (comboboxOpen && comboboxOptions.length > 0) {
      // Show first option
      comboboxHighlightIdx = 0;
      updateComboboxHighlight();
    }

    comboboxInput.setAttribute('aria-activedescendant', comboboxOptions.length > 0 ? `combobox-opt-${comboboxHighlightIdx}` : '');
  }

  function updateComboboxHighlight() {
    const options = comboboxList.querySelectorAll('.combobox-option');
    options.forEach(opt => {
      opt.setAttribute('aria-selected', 'false');
      opt.setAttribute('tabindex', '-1');
    });

    if (comboboxHighlightIdx >= 0 && comboboxHighlightIdx < comboboxOptions.length) {
      const highlighted = comboboxList.querySelector(`#combobox-opt-${comboboxHighlightIdx}`);
      if (highlighted) {
        highlighted.setAttribute('aria-selected', 'true');
        highlighted.setAttribute('tabindex', '0');
        highlighted.focus();
      }
    }

    comboboxInput.setAttribute('aria-activedescendant', comboboxOptions.length > 0 ? `combobox-opt-${comboboxHighlightIdx}` : '');
  }

  function closeCombobox() {
    comboboxOpen = false;
    comboboxInput.setAttribute('aria-expanded', 'false');
    comboboxInput.value = '';
    comboboxList.setAttribute('hidden', 'true');
    comboboxList.innerHTML = '';
    comboboxInput.removeAttribute('aria-activedescendant');
    comboboxWrap.classList.remove('open');
    announce('Combobox list closed.');
  }

  function openCombobox() {
    comboboxOpen = true;
    comboboxInput.setAttribute('aria-expanded', 'true');
    comboboxList.removeAttribute('hidden');
    comboboxList.innerHTML = '';
    comboboxWrap.classList.add('open');
    comboboxInput.focus();
    buildComboboxOptions('');
    announce('Combobox list opened.');
  }

  comboboxInput.addEventListener('input', () => {
    buildComboboxOptions(comboboxInput.value);
  });

  comboboxInput.addEventListener('focus', () => {
    if (comboboxOptions.length > 0) {
      openCombobox();
    }
  });

  comboboxInput.addEventListener('blur', () => {
    // Delay to allow focus to move back to the input
    setTimeout(() => {
      if (!comboboxWrap.contains(document.activeElement) && comboboxInput.getAttribute('aria-expanded') !== 'true') {
        closeCombobox();
      }
    }, 200);
  });

  // Keyboard navigation in combobox
  comboboxInput.addEventListener('keydown', (e) => {
    if (!comboboxOpen) {
      if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
        openCombobox();
      }
      return;
    }

    const options = comboboxList.querySelectorAll('.combobox-option');

    if (e.key === 'ArrowDown') {
      e.preventDefault();
      if (comboboxHighlightIdx < comboboxOptions.length - 1) {
        comboboxHighlightIdx++;
        updateComboboxHighlight();
      } else {
        comboboxHighlightIdx = 0;
        updateComboboxHighlight();
      }
    }

    if (e.key === 'ArrowUp') {
      e.preventDefault();
      if (comboboxHighlightIdx > 0) {
        comboboxHighlightIdx--;
        updateComboboxHighlight();
      } else {
        comboboxHighlightIdx = comboboxOptions.length - 1;
        updateComboboxHighlight();
      }
    }

    if (e.key === 'Enter') {
      e.preventDefault();
      if (comboboxHighlightIdx >= 0 && comboboxHighlightIdx < comboboxOptions.length) {
        comboboxInput.value = comboboxOptions[comboboxHighlightIdx];
        closeCombobox();
        announce('Selected: ' + comboboxOptions[comboboxHighlightIdx]);
      }
    }

    if (e.key === 'Escape') {
      e.preventDefault();
      closeCombobox();
      comboboxInput.focus();
    }
  });

  // Click to select
  comboboxList.addEventListener('click', (e) => {
    const option = e.target.closest('.combobox-option');
    if (option) {
      comboboxInput.value = option.textContent;
      closeCombobox();
      announce('Selected: ' + option.textContent);
    }
  });

  // Keyboard on combobox options
  comboboxList.addEventListener('keydown', (e) => {
    if (e.key === 'Escape') {
      e.preventDefault();
      closeCombobox();
      comboboxInput.focus();
    }
  });

  // Initialize combobox options
  buildComboboxOptions('');

  /* ============================================================
     5. Accordion
     ============================================================ */
  document.querySelectorAll('.accordion-trigger').forEach(trigger => {
    trigger.addEventListener('click', () => {
      const expanded = trigger.getAttribute('aria-expanded') === 'true';
      trigger.setAttribute('aria-expanded', String(!expanded));

      // Toggle the panel visibility
      const body = document.getElementById(trigger.getAttribute('aria-controls'));
      if (body) {
        body.toggleAttribute('hidden');
      }

      // If expanding, focus the trigger
      if (!expanded) {
        trigger.focus();
      }

      announce(trigger.textContent.trim() + (expanded ? ' closed' : ' opened'));
    });

    // Keyboard: Enter or Space toggles
    trigger.addEventListener('keydown', (e) => {
      if (e.key === 'Enter' || e.key === ' ') {
        e.preventDefault();
        trigger.click();
      }
    });
  });

  /* ============================================================
     Sidebar nav linking
     ============================================================ */
  document.querySelectorAll('.sidebar-nav a').forEach(link => {
    link.addEventListener('click', (e) => {
      e.preventDefault();
      document.querySelectorAll('.sidebar-nav a').forEach(a => {
        a.classList.remove('active');
        a.removeAttribute('aria-current');
      });
      link.classList.add('active');
      link.setAttribute('aria-current', 'page');

      // Scroll to section
      const id = link.getAttribute('href').replace('#', '');
      const target = document.getElementById(id);
      if (target) {
        target.scrollIntoView({ behavior: 'smooth', block: 'start' });
        // Set focus on the target section
        target.setAttribute('tabindex', '-1');
        target.focus({ preventScroll: true });
      }
    });
  });

  /* ============================================================
     Initialize: set first tab as active on load
     ============================================================ */
  switchTab(tabs[0]);

  /* ============================================================
     Keyboard shortcut: Ctrl+K or / to focus combobox
     ============================================================ */
  comboboxInput.addEventListener('keydown', (e) => {
    if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
      e.preventDefault();
      comboboxInput.focus();
    }
  });

  /* ============================================================
     Keyboard shortcut: / to focus combobox
     ============================================================ */
  document.addEventListener('keydown', (e) => {
    if (e.key === '/' && document.activeElement !== comboboxInput && comboboxInput.getAttribute('aria-expanded') !== 'true') {
      comboboxInput.focus();
    }
  });

})();
</script>
</body>
</html>