← Accessible Components results

Accessible Components

Qwen 3.7 Flash · typescript

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 75,945 bytes · SHA-256 8fd99bd6b9d2
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Accessible Component Showcase</title>
<style>
/* ── css: tokens ── */
/* Design Tokens */
:root {
  --color-primary: #2563eb;
  --color-primary-hover: #1d4ed8;
  --color-primary-light: #eff6ff;
  --color-text: #1e293b;
  --color-text-secondary: #64748b;
  --color-border: #e2e8f0;
  --color-border-strong: #cbd5e1;
  --color-bg: #ffffff;
  --color-bg-subtle: #f8fafc;
  --color-focus-ring: rgba(37, 99, 235, 0.4);
  --font-sans: "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-mono: "SF Mono", "Cascadia Code", "Consolas", monospace;
  --radius-sm: 6px;
  --radius-md: 8px;
  --radius-lg: 12px;
  --space-xs: 4px;
  --space-sm: 8px;
  --space-md: 16px;
  --space-lg: 24px;
  --space-xl: 32px;
  --space-2xl: 48px;
  --space-3xl: 64px;
  --sidebar-width: 260px;
  --header-height: 60px;
  --transition-fast: 150ms ease;
  --transition-normal: 250ms ease;
  --transition-slow: 350ms ease;
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --transition-fast: 0ms;
    --transition-normal: 0ms;
    --transition-slow: 0ms;
  }
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html {
  font-size: 16px;
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

body {
  font-family: var(--font-sans);
  color: var(--color-text);
  background: var(--color-bg);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
/* ── css: layout ── */
/* Layout */
a.skip-link {
  position: absolute;
  top: -100%;
  left: var(--space-md);
  z-index: 10000;
  padding: var(--space-sm) var(--space-md);
  background: var(--color-primary);
  color: #fff;
  font-weight: 600;
  border-radius: var(--radius-sm);
  text-decoration: none;
  transition: top var(--transition-fast);
}
a.skip-link:focus {
  top: var(--space-md);
  outline: 3px solid var(--color-focus-ring);
  outline-offset: 2px;
}

.app-layout {
  display: flex;
  min-height: 100vh;
}

.sidebar {
  position: fixed;
  top: 0;
  left: 0;
  width: var(--sidebar-width);
  height: 100vh;
  overflow-y: auto;
  background: var(--color-bg-subtle);
  border-right: 1px solid var(--color-border);
  padding: var(--space-xl) 0;
  z-index: 100;
}

.sidebar-header {
  padding: 0 var(--space-lg);
  margin-bottom: var(--space-lg);
  border-bottom: 1px solid var(--color-border);
  padding-bottom: var(--space-lg);
}

.sidebar-title {
  font-size: 0.8rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-secondary);
}

.sidebar-nav { list-style: none; }

.sidebar-nav li a {
  display: block;
  padding: var(--space-sm) var(--space-lg);
  color: var(--color-text-secondary);
  text-decoration: none;
  font-size: 0.9rem;
  font-weight: 500;
  border-left: 3px solid transparent;
  transition: color var(--transition-fast), border-color var(--transition-fast), background var(--transition-fast);
}

.sidebar-nav li a:hover,
.sidebar-nav li a.active {
  color: var(--color-primary);
  border-left-color: var(--color-primary);
  background: var(--color-primary-light);
}

.main-content {
  margin-left: var(--sidebar-width);
  flex: 1;
  max-width: 900px;
}

.page-header {
  padding: var(--space-2xl) var(--space-3xl) var(--space-xl);
  border-bottom: 1px solid var(--color-border);
  background: var(--color-bg-subtle);
}

.page-header h1 {
  font-size: 2rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.2;
  margin-bottom: var(--space-xs);
}

.page-header p {
  color: var(--color-text-secondary);
  font-size: 1.05rem;
  max-width: 600px;
}

.section {
  padding: var(--space-2xl) var(--space-3xl);
  border-bottom: 1px solid var(--color-border);
}

.section:last-child { border-bottom: none; }

.section-label {
  display: inline-block;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-primary);
  background: var(--color-primary-light);
  padding: var(--space-xs) var(--space-sm);
  border-radius: var(--radius-sm);
  margin-bottom: var(--space-sm);
}

.section h2 {
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  margin-bottom: var(--space-md);
}

.caption {
  font-size: 0.85rem;
  color: var(--color-text-secondary);
  margin-bottom: var(--space-lg);
  font-family: var(--font-mono);
}

.caption kbd {
  display: inline-block;
  padding: 1px 5px;
  font-size: 0.75rem;
  font-family: var(--font-mono);
  background: var(--color-bg-subtle);
  border: 1px solid var(--color-border-strong);
  border-radius: 3px;
  box-shadow: 0 1px 0 var(--color-border);
}

.demo-area {
  background: var(--color-bg-subtle);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-xl);
  margin-top: var(--space-md);
}
/* ── css: component ── */
/* Component Styles */

/* Global Focus Indicator */
*:focus-visible {
  outline: 3px solid var(--color-primary);
  outline-offset: 2px;
  border-radius: 2px;
}
a:focus-visible, button:focus-visible {
  outline-offset: 4px;
}

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  padding: 9px 18px;
  font-size: 0.9rem;
  font-weight: 600;
  font-family: inherit;
  line-height: 1.4;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast), box-shadow var(--transition-fast);
  border: 1px solid transparent;
}
.btn-primary {
  background: var(--color-primary);
  color: #fff;
  border-color: var(--color-primary);
}
.btn-primary:hover:not(:disabled) {
  background: var(--color-primary-hover);
  border-color: var(--color-primary-hover);
}
.btn-secondary {
  background: var(--color-bg);
  color: var(--color-text);
  border-color: var(--color-border-strong);
}
.btn-secondary:hover:not(:disabled) {
  background: var(--color-bg-subtle);
  border-color: var(--color-text);
}
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-icon {
  padding: 8px;
  min-width: 36px;
  min-height: 36px;
}

/* Modal */
.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-xl);
}
.modal-backdrop[aria-hidden="true"] {
  display: none;
}
.modal-overlay {
  position: absolute;
  inset: 0;
  background: rgba(15, 23, 42, 0.5);
}
.modal-dialog {
  position: relative;
  background: var(--color-bg);
  border-radius: var(--radius-lg);
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
  width: 100%;
  max-width: 520px;
  max-height: calc(100vh - var(--space-2xl));
  overflow-y: auto;
  z-index: 1;
  animation: modal-in var(--transition-normal) ease forwards;
}
@keyframes modal-in {
  from { opacity: 0; transform: scale(0.96) translateY(10px); }
  to { opacity: 1; transform: scale(1) translateY(0); }
}
.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-lg) var(--space-xl);
  border-bottom: 1px solid var(--color-border);
}
.modal-header h3 {
  font-size: 1.15rem;
  font-weight: 700;
}
.modal-body {
  padding: var(--space-xl);
}
.modal-footer {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-sm);
  padding: var(--space-lg) var(--space-xl);
  border-top: 1px solid var(--color-border);
}

/* Dropdown Menu */
.dropdown-trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
}
.dropdown-menu {
  position: absolute;
  z-index: 500;
  min-width: 220px;
  background: var(--color-bg);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.08);
  padding: var(--space-xs);
  margin-top: var(--space-xs);
  display: none;
}
.dropdown-menu[role="menu"][data-visible="true"] {
  display: block;
  animation: menu-in var(--transition-fast) ease forwards;
}
@keyframes menu-in {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: translateY(0); }
}
.menu-item {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: 8px 12px;
  width: 100%;
  font-size: 0.9rem;
  font-family: inherit;
  color: var(--color-text);
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  cursor: pointer;
  text-align: left;
  transition: background var(--transition-fast);
}
.menu-item:hover,
.menu-item.active {
  background: var(--color-primary-light);
  color: var(--color-primary);
}
.menu-item:focus-visible {
  outline-offset: -2px;
}
.menu-separator {
  height: 1px;
  background: var(--color-border);
  margin: var(--space-xs) 0;
}

/* Tabs */
.tablist {
  display: flex;
  border-bottom: 2px solid var(--color-border);
  gap: 0;
}
.tab {
  padding: 10px 20px;
  font-size: 0.9rem;
  font-weight: 600;
  font-family: inherit;
  color: var(--color-text-secondary);
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -2px;
  cursor: pointer;
  transition: color var(--transition-fast), border-color var(--transition-fast);
  white-space: nowrap;
}
.tab:hover {
  color: var(--color-text);
}
.tab[aria-selected="true"] {
  color: var(--color-primary);
  border-bottom-color: var(--color-primary);
}
.tab-panel {
  padding: var(--space-lg) 0;
}
.tab-panel[hidden] { display: none; }

/* Combobox */
.combobox-wrapper {
  position: relative;
  width: 100%;
  max-width: 320px;
}
.combobox-listbox {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  z-index: 500;
  background: var(--color-bg);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1);
  max-height: 240px;
  overflow-y: auto;
  padding: var(--space-xs);
  display: none;
}
.combobox-listbox[data-visible="true"] {
  display: block;
}
.listbox-option {
  display: block;
  width: 100%;
  padding: 8px 12px;
  font-size: 0.9rem;
  font-family: inherit;
  color: var(--color-text);
  background: none;
  border: none;
  text-align: left;
  cursor: pointer;
  border-radius: var(--radius-sm);
  transition: background var(--transition-fast);
}
.listbox-option:hover {
  background: var(--color-primary-light);
}
.listbox-option.active {
  background: var(--color-primary-light);
  color: var(--color-primary);
  font-weight: 600;
}
.listbox-option:focus-visible {
  outline-offset: -2px;
}
.option-count {
  padding: var(--space-sm) var(--space-md);
  font-size: 0.8rem;
  color: var(--color-text-secondary);
  border-bottom: 1px solid var(--color-border);
  font-style: italic;
}

/* Accordion */
.accordion-item {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  margin-bottom: var(--space-sm);
  overflow: hidden;
}
.accordion-item:last-child { margin-bottom: 0; }
.accordion-trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: var(--space-md) var(--space-lg);
  font-size: 0.95rem;
  font-weight: 600;
  font-family: inherit;
  color: var(--color-text);
  background: var(--color-bg);
  border: none;
  cursor: pointer;
  text-align: left;
  transition: background var(--transition-fast);
}
.accordion-trigger:hover {
  background: var(--color-bg-subtle);
}
.accordion-chevron {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  transition: transform var(--transition-normal);
}
.accordion-trigger[aria-expanded="true"] .accordion-chevron {
  transform: rotate(180deg);
}
.accordion-region {
  overflow: hidden;
  transition: height var(--transition-slow) ease, visibility var(--transition-slow);
}
.accordion-region-inner {
  padding: 0 var(--space-lg) var(--space-lg);
  color: var(--color-text-secondary);
  font-size: 0.9rem;
  line-height: 1.65;
}

/* Utility */
.text-muted { color: var(--color-text-secondary); }
.mt-md { margin-top: var(--space-md); }
.flex-center {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}
.result-display {
  margin-top: var(--space-md);
  padding: var(--space-md);
  background: var(--color-primary-light);
  border: 1px solid var(--color-primary);
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
  color: var(--color-primary);
  font-weight: 500;
}
.selection-stat {
  margin-top: var(--space-md);
  font-size: 0.85rem;
  color: var(--color-text-secondary);
}
</style>
</head>
<body>
<a href="#main-content" class="skip-link">Skip to main content</a>
<div class="app-layout">
  <aside class="sidebar" aria-label="Component navigation">
    <div class="sidebar-header">
      <span class="sidebar-title">Components</span>
    </div>
    <ul class="sidebar-nav" id="sidebar-nav">
      <li><a href="#modal" data-section="modal">Modal Dialog</a></li>
      <li><a href="#dropdown" data-section="dropdown">Dropdown Menu</a></li>
      <li><a href="#tabs" data-section="tabs">Tabs</a></li>
      <li><a href="#combobox" data-section="combobox">Combobox</a></li>
      <li><a href="#accordion" data-section="accordion">Accordion</a></li>
    </ul>
  </aside>
  <main class="main-content" id="main-content">
    <header class="page-header">
      <h1>Accessible Component Showcase</h1>
      <p>A curated library of genuinely keyboard- and screen-reader-accessible UI components following WAI-ARIA Authoring Practices.</p>
    </header>

    <!-- Modal Dialog -->
    <section class="section" id="modal" aria-labelledby="modal-heading">
      <span class="section-label">Component 01</span>
      <h2 id="modal-heading">Modal Dialog</h2>
      <p class="caption"><kbd>Tab</kbd> / <kbd>Shift+Tab</kbd> trap focus · <kbd>Enter</kbd>/<kbd>Space</kbd> trigger action · <kbd>Esc</kbd> close</p>
      <div class="demo-area">
        <button class="btn btn-primary" id="modal-trigger">Open Dialog</button>
        <div class="result-display" id="modal-result" hidden="">Dialog was closed successfully.</div>
      </div>
    </section>

    <!-- Dropdown Menu -->
    <section class="section" id="dropdown" aria-labelledby="dropdown-heading">
      <span class="section-label">Component 02</span>
      <h2 id="dropdown-heading">Dropdown Menu</h2>
      <p class="caption"><kbd>↓</kbd> opens menu · <kbd>↑</kbd><kbd>↓</kbd> navigate · <kbd>Home</kbd>/<kbd>End</kbd> jump · <kbd>Enter</kbd>/<kbd>Space</kbd> select · <kbd>Esc</kbd> close</p>
      <div class="demo-area">
        <div class="flex-center" style="position: relative;">
          <button class="btn btn-secondary dropdown-trigger" id="dropdown-trigger" aria-haspopup="menu" aria-expanded="false">
            Actions
            <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M4.5 6l3.5 4 3.5-4z"></path></svg>
          </button>
        </div>
        <div class="selection-stat" id="dropdown-result">No selection made yet.</div>
      </div>
    </section>

    <!-- Tabs -->
    <section class="section" id="tabs" aria-labelledby="tabs-heading">
      <span class="section-label">Component 03</span>
      <h2 id="tabs-heading">Tabs</h2>
      <p class="caption"><kbd>←</kbd><kbd>→</kbd> switch tabs · Tab enters panel content</p>
      <div class="demo-area">
        <div role="tablist" aria-label="Example tabs" class="tablist" id="tablist">
          <button role="tab" aria-selected="true" aria-controls="tabpanel-1" id="tab-1" tabindex="0">Overview</button>
          <button role="tab" aria-selected="false" aria-controls="tabpanel-2" id="tab-2" tabindex="-1">Features</button>
          <button role="tab" aria-selected="false" aria-controls="tabpanel-3" id="tab-3" tabindex="-1">Usage</button>
        </div>
        <div class="tab-panel" role="tabpanel" id="tabpanel-1" aria-labelledby="tab-1" tabindex="0">
          <p>This component follows WAI-ARIA Authoring Practices for the tab pattern. Exactly one tab is in the tab order at a time — the active tab has <code>tabindex="0"</code> while inactive tabs have <code>tabindex="-1"</code>. Arrow keys move between tabs horizontally.</p>
        </div>
        <div class="tab-panel" role="tabpanel" id="tabpanel-2" aria-labelledby="tab-2" tabindex="0" hidden="">
          <ul style="padding-left: var(--space-lg);">
            <li>Keyboard-navigable tab switching</li>
            <li>Roving tabindex management</li>
            <li>Correct ARIA relationship wiring</li>
            <li>Hidden panels use the <code>hidden</code> attribute</li>
            <li>Arrow key movement between adjacent tabs</li>
          </ul>
        </div>
        <div class="tab-panel" role="tabpanel" id="tabpanel-3" aria-labelledby="tab-3" tabindex="0" hidden="">
          <p>Import the tabs module and call <code>initTabs(tablistEl)</code> on your tablist element. The implementation provides a cleanup function you can call when unmounting. All state (active tab, visible panel) is synchronized through shared properties.</p>
        </div>
      </div>
    </section>

    <!-- Combobox -->
    <section class="section" id="combobox" aria-labelledby="combobox-heading">
      <span class="section-label">Component 04</span>
      <h2 id="combobox-heading">Combobox</h2>
      <p class="caption">Type to filter · <kbd>↑</kbd><kbd>↓</kbd> highlight · <kbd>Enter</kbd> select · <kbd>Esc</kbd> close &amp; revert</p>
      <div class="demo-area">
        <div class="combobox-wrapper">
          <input type="text" role="combobox" id="combobox-input" aria-autocomplete="list" aria-expanded="false" aria-controls="combobox-listbox" aria-activedescendant="" placeholder="Search countries..." autocomplete="off">
          <div role="listbox" id="combobox-listbox" aria-hidden="true"></div>
        </div>
        <div class="selection-stat" id="combobox-result">Select a country or clear with <kbd>Esc</kbd>.</div>
      </div>
    </section>

    <!-- Accordion -->
    <section class="section" id="accordion" aria-labelledby="accordion-heading">
      <span class="section-label">Component 05</span>
      <h2 id="accordion-heading">Accordion</h2>
      <p class="caption"><kbd>Enter</kbd>/<kbd>Space</kbd> toggle · <kbd>↑</kbd><kbd>↓</kbd> move between triggers · Multiple sections open at once</p>
      <div class="demo-area">
        <div role="region" aria-labelledby="accordion-heading" id="accordion-container">
          <div class="accordion-item">
            <button class="accordion-trigger" aria-expanded="true" aria-controls="accordion-panel-1" id="accordion-btn-1">
              What makes these components accessible?
              <svg class="accordion-chevron" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"></path></svg>
            </button>
            <div class="accordion-region" id="accordion-panel-1" aria-labelledby="accordion-btn-1" role="region">
              <div class="accordion-region-inner">Every component adheres to WAI-ARIA Authoring Practices. This includes proper role attributes, correct labeling relationships via aria-labelledby and aria-controls, keyboard interaction patterns that match visual metaphors, and appropriate focus management. Screen readers announce component states accurately because every visual change is paired with an ARIA state update.</div>
            </div>
          </div>
          <div class="accordion-item">
            <button class="accordion-trigger" aria-expanded="false" aria-controls="accordion-panel-2" id="accordion-btn-2">
              How does focus trapping work in modals?
              <svg class="accordion-chevron" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"></path></svg>
            </button>
            <div class="accordion-region" id="accordion-panel-2" aria-labelledby="accordion-btn-2" role="region" hidden="">
              <div class="accordion-region-inner">A focus trap listens for the Tab and Shift+Tab keys within the modal. When you press Tab on the last focusable element, it wraps to the first one. Similarly, Shift+Tab on the first element wraps to the last. Additionally, the page behind the dialog is given the inert attribute, making all its elements non-focusable while the modal is open. On close, focus returns to whichever element triggered the dialog to open.</div>
            </div>
          </div>
          <div class="accordion-item">
            <button class="accordion-trigger" aria-expanded="false" aria-controls="accordion-panel-3" id="accordion-btn-3">
              What about reduced motion preferences?
              <svg class="accordion-chevron" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"></path></svg>
            </button>
            <div class="accordion-region" id="accordion-panel-3" aria-labelledby="accordion-btn-3" role="region" hidden="">
              <div class="accordion-region-inner">All animations respect the prefers-reduced-motion media query. When a user has enabled this preference, transitions are reduced to near-zero duration. Modals still appear instantly but without the scale-and-slide animation. Accordion panels expand/collapse immediately rather than animating their height. Dropdowns and combobox lists render instantly. No motion is lost — the content transition is just instantaneous instead of animated.</div>
            </div>
          </div>
        </div>
      </div>
    </section>
  </main>
</div>

<!-- Modal Overlay (created by JS) -->
<div class="modal-backdrop" id="modal-overlay" aria-hidden="true">
  <div class="modal-overlay" id="modal-bg-close"></div>
  <div class="modal-dialog" role="dialog" aria-modal="true" aria-labelledby="modal-title" id="modal-dialog">
    <div class="modal-header">
      <h3 id="modal-title">Confirm Action</h3>
      <button class="btn btn-icon btn-secondary" id="modal-close-x" aria-label="Close dialog">×</button>
    </div>
    <div class="modal-body">
      <p>This demonstrates a fully accessible modal dialog. Focus is trapped within this dialog while it's open. Try pressing Tab repeatedly — you won't be able to reach any elements behind the dialog. Press Escape to close, or click the backdrop outside this dialog.</p>
      <p>The focus will return to the "Open Dialog" button when you close it.</p>
    </div>
    <div class="modal-footer">
      <button class="btn btn-secondary" id="modal-cancel">Cancel</button>
      <button class="btn btn-primary" id="modal-confirm">Confirm</button>
    </div>
  </div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./focus-trap":"src/focus-trap.ts"},"src/modal.ts":{"./focus-trap":"src/focus-trap.ts"}};
function __require(id) {
  if (__cache[id]) return __cache[id].exports;
  var module = __cache[id] = { exports: {} };
  var factory = __mods[id];
  if (!factory) throw new Error("Module not found: " + id);
  factory(module.exports, function (spec) {
    var target = (__map[id] && __map[id][spec]) || spec;
    return __require(target);
  }, module);
  return module.exports;
}

// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
/**
 * Entry point — wires up all components and initializes sidebar navigation.
 */
Object.defineProperty(exports, "__esModule", { value: true });
const focus_trap_1 = require("./focus-trap");
// ============================================================
// Sidebar Navigation - Active section tracking
// ============================================================
function initSidebarNavigation() {
    const links = document.querySelectorAll('#sidebar-nav a[data-section]');
    const sections = Array.from(document.querySelectorAll('.section'));
    const observerOptions = {
        root: null,
        rootMargin: '-20% 0px -60% 0px',
        threshold: 0,
    };
    const observer = new IntersectionObserver((entries) => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                const id = entry.target.id;
                links.forEach(link => {
                    const isActive = link.dataset.section === id;
                    link.classList.toggle('active', isActive);
                    link.setAttribute('aria-current', isActive ? 'true' : '');
                });
            }
        });
    }, observerOptions);
    sections.forEach(section => observer.observe(section));
}
// ============================================================
// Modal Dialog
// ============================================================
let modalCleanupFn = null;
let pageInertRestore = false;
function openModal() {
    const backdrop = document.getElementById('modal-overlay');
    const dialog = document.getElementById('modal-dialog');
    if (!backdrop || !dialog)
        return;
    // Activate focus trap with escape support
    modalCleanupFn = (0, focus_trap_1.activateFocusTrap)(dialog, {
        autoFocus: 'first',
        onEscape: closeModal,
    });
    // Show modal
    backdrop.style.display = 'flex';
    backdrop.setAttribute('aria-hidden', 'false');
    document.body.style.overflow = 'hidden';
    pageInertRestore = true;
}
function closeModal() {
    const backdrop = document.getElementById('modal-overlay');
    if (!backdrop)
        return;
    // Clean up focus trap
    if (modalCleanupFn) {
        modalCleanupFn();
        modalCleanupFn = null;
    }
    backdrop.removeAttribute('aria-hidden');
    document.body.style.overflow = '';
    pageInertRestore = false;
    // Return focus to trigger button
    const triggerBtn = document.getElementById('modal-trigger');
    setTimeout(() => triggerBtn?.focus(), 16);
}
function initModalComponent() {
    const backdrop = document.getElementById('modal-overlay');
    const bgClose = document.getElementById('modal-bg-close');
    const closeX = document.getElementById('modal-close-x');
    const cancel = document.getElementById('modal-cancel');
    const confirm = document.getElementById('modal-confirm');
    if (bgClose) {
        bgClose.addEventListener('click', () => {
            if (backdrop.style.display !== 'none' && backdrop.style.display !== '') {
                closeModal();
            }
        });
    }
    if (closeX)
        closeX.addEventListener('click', closeModal);
    if (cancel)
        cancel.addEventListener('click', closeModal);
    if (confirm)
        confirm.addEventListener('click', closeModal);
    const trigger = document.getElementById('modal-trigger');
    if (trigger) {
        trigger.addEventListener('click', openModal);
    }
    // Expose for testing
    window.__openModal = openModal;
    window.__closeModal = closeModal;
}
const menuState = { isOpen: false, activeIndex: -1 };
let menuKeydownHandler = null;
function initDropdownMenuComponent() {
    const trigger = document.getElementById('dropdown-trigger');
    const result = document.getElementById('dropdown-result');
    if (!trigger || !result)
        return;
    // Create menu element
    const flexContainer = document.querySelector('[id="dropdown"] .demo-area > .flex-center');
    if (!flexContainer)
        return;
    const menuEl = document.createElement('div');
    menuEl.className = 'dropdown-menu';
    flexContainer.appendChild(menuEl);
    const items = [
        { label: 'Edit...' },
        { label: 'Duplicate' },
        { label: 'Archive' },
        { separator: true },
        { label: 'Share' },
        { label: 'Delete...' },
    ];
    const buildDOM = () => {
        menuEl.innerHTML = '';
        menuEl.setAttribute('role', 'menu');
        menuEl.setAttribute('aria-labelledby', trigger.id);
        items.forEach((item, idx) => {
            if (item.separator) {
                const sep = document.createElement('div');
                sep.className = 'menu-separator';
                sep.setAttribute('role', 'separator');
                menuEl.appendChild(sep);
            }
            else {
                const btn = document.createElement('button');
                btn.className = 'menu-item';
                btn.setAttribute('role', 'menuitem');
                btn.setAttribute('tabindex', '-1');
                btn.textContent = item.label ?? '';
                btn.dataset.index = String(idx);
                btn.addEventListener('click', () => handleSelect(item));
                menuEl.appendChild(btn);
            }
        });
    };
    const getMenuButtons = () => {
        return Array.from(menuEl.querySelectorAll('button[role="menuitem"]'));
    };
    const clearActive = () => {
        getMenuButtons().forEach(b => b.classList.remove('active'));
    };
    const setActive = (idx) => {
        clearActive();
        const list = getMenuButtons();
        if (list.length === 0)
            return;
        if (idx < 0)
            idx = list.length - 1;
        if (idx >= list.length)
            idx = 0;
        list[idx].classList.add('active');
        list[idx].setAttribute('tabindex', '0');
    };
    const handleOpen = () => {
        if (menuState.isOpen)
            return;
        buildDOM();
        menuState.isOpen = true;
        trigger.setAttribute('aria-expanded', 'true');
        menuEl.style.display = 'block';
        menuEl.setAttribute('data-visible', 'true');
        clearActive();
        menuState.activeIndex = 0;
        setActive(0);
        // Bind keydown
        menuKeydownHandler = (e) => {
            if (!menuState.isOpen)
                return;
            const list = getMenuButtons();
            switch (e.key) {
                case 'Escape':
                    e.preventDefault();
                    handleClose();
                    break;
                case 'ArrowDown':
                    e.preventDefault();
                    menuState.activeIndex = ((menuState.activeIndex + 1) % list.length + list.length) % list.length;
                    setActive(menuState.activeIndex);
                    list[menuState.activeIndex].focus();
                    break;
                case 'ArrowUp':
                    e.preventDefault();
                    menuState.activeIndex = ((menuState.activeIndex - 1 + list.length) % list.length);
                    setActive(menuState.activeIndex);
                    list[menuState.activeIndex].focus();
                    break;
                case 'Home':
                    e.preventDefault();
                    menuState.activeIndex = 0;
                    setActive(0);
                    list[0].focus();
                    break;
                case 'End':
                    e.preventDefault();
                    menuState.activeIndex = list.length - 1;
                    setActive(menuState.activeIndex);
                    list[list.length - 1].focus();
                    break;
                case 'Tab':
                    handleClose();
                    break;
                case 'Enter':
                case ' ':
                    e.preventDefault();
                    handleSelect(items[menuState.activeIndex]);
                    break;
            }
        };
        document.addEventListener('keydown', menuKeydownHandler);
    };
    const handleClose = () => {
        if (!menuState.isOpen)
            return;
        menuState.isOpen = false;
        trigger.setAttribute('aria-expanded', 'false');
        menuEl.removeAttribute('data-visible');
        setTimeout(() => { menuEl.style.display = 'none'; }, 50);
        clearActive();
        menuState.activeIndex = -1;
        if (menuKeydownHandler) {
            document.removeEventListener('keydown', menuKeydownHandler);
            menuKeydownHandler = null;
        }
        trigger.focus();
    };
    const handleSelect = (item) => {
        if (item && !item.separator && item.label) {
            result.textContent = `Selected: ${item.label}`;
        }
        handleClose();
    };
    trigger.addEventListener('click', () => {
        if (menuState.isOpen)
            handleClose();
        else
            handleOpen();
    });
    trigger.addEventListener('keydown', (e) => {
        if (!menuState.isOpen && (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ')) {
            e.preventDefault();
            handleOpen();
        }
    });
    window.__openDropdown = handleOpen;
    window.__closeDropdown = handleClose;
}
const tabsState = { activeIndex: 0 };
function initTabsComponent() {
    const tablist = document.getElementById('tablist');
    if (!tablist)
        return;
    const getTabs = () => {
        return Array.from(tablist.querySelectorAll('button[role="tab"]'));
    };
    const getPanels = () => {
        return getTabs()
            .map(t => {
            const id = t.getAttribute('aria-controls');
            return id ? document.getElementById(id) : null;
        })
            .filter((p) => p !== null);
    };
    const setActiveTab = (idx) => {
        const tabs = getTabs();
        const panels = getPanels();
        if (idx < 0 || idx >= tabs.length)
            return;
        tabsState.activeIndex = idx;
        tabs.forEach((tab, i) => {
            const selected = i === idx;
            tab.setAttribute('aria-selected', String(selected));
            tab.tabIndex = selected ? 0 : -1;
            const panel = panels[i];
            if (panel) {
                if (selected)
                    panel.removeAttribute('hidden');
                else
                    panel.setAttribute('hidden', '');
            }
        });
    };
    const onKeyDown = (e) => {
        const tabs = getTabs();
        let newIndex;
        switch (e.key) {
            case 'ArrowRight':
                e.preventDefault();
                newIndex = (tabsState.activeIndex + 1) % tabs.length;
                break;
            case 'ArrowLeft':
                e.preventDefault();
                newIndex = ((tabsState.activeIndex - 1 + tabs.length) % tabs.length);
                break;
            case 'Home':
                e.preventDefault();
                newIndex = 0;
                break;
            case 'End':
                e.preventDefault();
                newIndex = tabs.length - 1;
                break;
            default:
                return;
        }
        setActiveTab(newIndex);
        tabs[newIndex].focus();
    };
    tablist.addEventListener('keydown', onKeyDown);
    // Click handling
    tablist.addEventListener('click', (e) => {
        const tab = e.target.closest('button[role="tab"]');
        if (!tab)
            return;
        const tabs = getTabs();
        const idx = tabs.indexOf(tab);
        if (idx !== -1) {
            setActiveTab(idx);
            tab.focus();
        }
    });
}
// ============================================================
// Combobox
// ============================================================
const COUNTRIES = [
    'Argentina', 'Australia', 'Austria', 'Belgium', 'Brazil',
    'Canada', 'China', 'Denmark', 'Egypt', 'Finland',
    'France', 'Germany', 'Greece', 'India', 'Indonesia',
    'Ireland', 'Italy', 'Japan', 'Kenya', 'Malaysia',
    'Mexico', 'Netherlands', 'New Zealand', 'Nigeria', 'Norway',
    'Peru', 'Philippines', 'Poland', 'Portugal', 'Romania',
    'Russia', 'South Africa', 'South Korea', 'Spain', 'Sweden',
    'Switzerland', 'Thailand', 'Turkey', 'United Kingdom', 'Vietnam',
];
const comboState = {
    isOpen: false,
    filtered: [...COUNTRIES],
    activeIndex: -1,
    currentValue: '',
};
let comboOutsideListener = null;
function initComboboxComponent() {
    const input = document.getElementById('combobox-input');
    const listbox = document.getElementById('combobox-listbox');
    const result = document.getElementById('combobox-result');
    if (!input || !listbox)
        return;
    comboState.currentValue = input.value;
    let optionElements = [];
    const clearActive = () => {
        optionElements.forEach(o => o.classList.remove('active'));
        input.removeAttribute('aria-activedescendant');
        comboState.activeIndex = -1;
    };
    const setHighlight = (idx) => {
        if (optionElements.length === 0)
            return;
        const clamped = ((idx % optionElements.length) + optionElements.length) % optionElements.length;
        clearActive();
        optionElements[clamped].classList.add('active');
        input.setAttribute('aria-activedescendant', optionElements[clamped].id);
        comboState.activeIndex = clamped;
        optionElements[clamped].scrollIntoView({ block: 'nearest' });
    };
    const buildList = () => {
        listbox.innerHTML = '';
        if (comboState.filtered.length === 0) {
            const noResults = document.createElement('div');
            noResults.className = 'option-count';
            noResults.textContent = 'No countries found';
            listbox.appendChild(noResults);
            optionElements = [];
            clearActive();
            updateListVisibility();
            return;
        }
        const header = document.createElement('div');
        header.className = 'option-count';
        header.textContent = `Showing ${comboState.filtered.length} of ${COUNTRIES.length}`;
        listbox.appendChild(header);
        comboState.filtered.forEach((country, idx) => {
            const btn = document.createElement('button');
            btn.className = 'listbox-option';
            btn.setAttribute('role', 'option');
            btn.id = `cb-option-${idx}`;
            btn.setAttribute('tabindex', '-1');
            btn.textContent = country;
            btn.addEventListener('click', () => selectOption(idx));
            listbox.appendChild(btn);
        });
        optionElements = Array.from(listbox.querySelectorAll('button.listbox-option'));
        updateListVisibility();
    };
    const updateListVisibility = () => {
        if (comboState.isOpen && input.value.trim().length > 0 && comboState.filtered.length > 0) {
            listbox.setAttribute('data-visible', 'true');
            listbox.setAttribute('aria-hidden', 'false');
        }
        else {
            listbox.removeAttribute('data-visible');
            listbox.setAttribute('aria-hidden', 'true');
        }
    };
    const filterAndOpen = () => {
        const query = input.value;
        comboState.currentValue = query;
        comboState.filtered = COUNTRIES.filter(c => c.toLowerCase().includes(query.toLowerCase()));
        comboState.activeIndex = -1;
        if (query.trim().length > 0) {
            comboState.isOpen = true;
            input.setAttribute('aria-expanded', 'true');
            buildList();
        }
        else {
            comboState.filtered = [...COUNTRIES];
            closeList(true);
        }
    };
    const closeList = (revert = false) => {
        comboState.isOpen = false;
        input.setAttribute('aria-expanded', 'false');
        if (revert) {
            input.value = comboState.currentValue;
        }
        clearActive();
        updateListVisibility();
    };
    const selectOption = (idx) => {
        if (idx < 0 || idx >= optionElements.length)
            return;
        const selected = optionElements[idx].textContent;
        if (selected) {
            comboState.currentValue = selected;
            input.value = selected;
            result.innerHTML = `Selected: <strong>${selected}</strong>`;
        }
        closeList();
    };
    const handleInput = () => {
        filterAndOpen();
    };
    const handleKeyDown = (e) => {
        if (e.key === 'ArrowDown') {
            e.preventDefault();
            if (!comboState.isOpen) {
                if (comboState.currentValue.length > 0)
                    filterAndOpen();
                else
                    return;
            }
            if (optionElements.length > 0)
                setHighlight(comboState.activeIndex + 1);
        }
        else if (e.key === 'ArrowUp') {
            e.preventDefault();
            if (!comboState.isOpen)
                return;
            if (optionElements.length > 0)
                setHighlight(comboState.activeIndex - 1);
        }
        else if (e.key === 'Enter') {
            e.preventDefault();
            if (comboState.isOpen && optionElements.length > 0 && comboState.activeIndex >= 0) {
                selectOption(comboState.activeIndex);
            }
        }
        else if (e.key === 'Escape') {
            e.preventDefault();
            closeList(true);
        }
    };
    const handleClickOutside = (e) => {
        if (comboState.isOpen && !input.contains(e.target) && !listbox.contains(e.target)) {
            closeList(true);
        }
    };
    input.addEventListener('input', handleInput);
    input.addEventListener('keydown', handleKeyDown);
    input.addEventListener('blur', () => {
        setTimeout(() => {
            const focused = document.activeElement;
            if (focused?.closest(`#${listbox.id}`))
                return;
            if (comboState.isOpen)
                closeList(true);
        }, 50);
    });
    comboOutsideListener = handleClickOutside;
    document.addEventListener('click', comboOutsideListener);
    window.__selectCombo = selectOption;
    window.__getComboActivedescendant = () => input.getAttribute('aria-activedescendant');
}
// ============================================================
// Accordion
// ============================================================
function initAccordionComponent() {
    const container = document.getElementById('accordion-container');
    if (!container)
        return;
    const triggers = Array.from(container.querySelectorAll('button.accordion-trigger'));
    const toggleRegion = (trigger, forceClose) => {
        const panelId = trigger.getAttribute('aria-controls');
        if (!panelId)
            return;
        const panel = document.getElementById(panelId);
        if (!panel)
            return;
        const isExpanded = trigger.getAttribute('aria-expanded') === 'true';
        const willBeOpen = forceClose !== undefined ? !forceClose : !isExpanded;
        trigger.setAttribute('aria-expanded', String(willBeOpen));
        if (willBeOpen) {
            // Opening: measure height, animate from 0
            panel.style.height = 'auto';
            const scrollHeight = panel.scrollHeight;
            panel.style.height = '0px';
            panel.removeAttribute('hidden');
            requestAnimationFrame(() => {
                panel.style.height = `${scrollHeight}px`;
            });
            const onEnd = () => {
                panel.removeEventListener('transitionend', onEnd);
                if (trigger.getAttribute('aria-expanded') === 'true') {
                    panel.style.height = 'auto';
                }
            };
            panel.addEventListener('transitionend', onEnd);
        }
        else {
            // Closing: collapse to 0, then add hidden
            panel.style.height = `${panel.scrollHeight}px`;
            panel.offsetHeight; // Force reflow
            requestAnimationFrame(() => {
                panel.style.height = '0px';
            });
            const onEnd = () => {
                panel.removeEventListener('transitionend', onEnd);
                if (trigger.getAttribute('aria-expanded') === 'false') {
                    panel.setAttribute('hidden', '');
                    panel.style.height = '';
                }
            };
            panel.addEventListener('transitionend', onEnd);
        }
    };
    triggers.forEach(trigger => {
        // Initialize regions based on aria-expanded
        const panelId = trigger.getAttribute('aria-controls');
        if (panelId) {
            const panel = document.getElementById(panelId);
            if (panel) {
                const isExpanded = trigger.getAttribute('aria-expanded') === 'true';
                if (!isExpanded) {
                    panel.setAttribute('hidden', '');
                    panel.style.height = '0px';
                }
                else {
                    panel.style.height = 'auto';
                }
            }
        }
        trigger.addEventListener('click', () => toggleRegion(trigger));
        // Arrow key navigation between triggers
        trigger.addEventListener('keydown', (e) => {
            const currentIndex = triggers.indexOf(trigger);
            switch (e.key) {
                case 'ArrowDown':
                    e.preventDefault();
                    triggers[(currentIndex + 1) % triggers.length].focus();
                    break;
                case 'ArrowUp':
                    e.preventDefault();
                    triggers[(currentIndex - 1 + triggers.length) % triggers.length].focus();
                    break;
                case 'Home':
                    e.preventDefault();
                    triggers[0].focus();
                    break;
                case 'End':
                    e.preventDefault();
                    triggers[triggers.length - 1].focus();
                    break;
            }
        });
    });
}
// ============================================================
// Run all initialization
// ============================================================
initSidebarNavigation();
initModalComponent();
initDropdownMenuComponent();
initTabsComponent();
initComboboxComponent();
initAccordionComponent();
};

// ── module: src/constants.ts ──
__mods["src/constants.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MODAL_ATTRS = exports.ANIMATIONS = exports.COLORS = void 0;
/** Tunable design constants */
exports.COLORS = {
    primary: '#2563eb',
    primaryHover: '#1d4ed8',
    focusRing: 'rgba(37, 99, 235, 0.4)',
};
exports.ANIMATIONS = {
    fast: 150,
    normal: 250,
    slow: 350,
};
exports.MODAL_ATTRS = ['href', 'tabindex', 'data-focus-guard'];
};

// ── module: src/focus-trap.ts ──
__mods["src/focus-trap.ts"] = function (exports, require, module) {
"use strict";
/**
 * Reusable focus trap utility.
 * Traps Tab/Shift+Tab within a container and optionally focuses the first
 * or last element on activation/deactivation.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.activateFocusTrap = activateFocusTrap;
exports.deactivateAllFocusTraps = deactivateAllFocusTraps;
/** Focusable selector including common interactive elements */
const FOCUSABLE_SELECTOR = [
    'a[href]',
    'button:not([disabled])',
    'input:not([disabled]):not([type="hidden"])',
    'select:not([disabled])',
    'textarea:not([disabled])',
    '[tabindex]:not([tabindex="-1"])',
    '[contenteditable]',
].join(', ');
let activeTraps = [];
/**
 * Activate a focus trap on `container`. Returns a cleanup function.
 */
function activateFocusTrap(container, options = {}) {
    const { autoFocus = 'first', onEscape } = options;
    const getFocusableElements = () => {
        return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR))
            .filter(el => el.offsetParent !== null || el === document.activeElement);
    };
    const handleKeydown = (e) => {
        if (e.key !== 'Tab')
            return;
        const focusables = getFocusableElements();
        if (focusables.length === 0)
            return;
        const first = focusables[0];
        const last = focusables[focusables.length - 1];
        if (e.shiftKey) {
            if (document.activeElement === first) {
                e.preventDefault();
                last.focus();
            }
        }
        else {
            if (document.activeElement === last) {
                e.preventDefault();
                first.focus();
            }
        }
    };
    const handleEscape = (e) => {
        if (e.key === 'Escape') {
            e.stopPropagation();
            onEscape?.();
        }
    };
    document.addEventListener('keydown', handleKeydown);
    document.addEventListener('keydown', handleEscape);
    const cleanup = () => {
        document.removeEventListener('keydown', handleKeydown);
        document.removeEventListener('keydown', handleEscape);
        const idx = activeTraps.indexOf(cleanup);
        if (idx !== -1)
            activeTraps.splice(idx, 1);
    };
    // Auto-focus
    if (autoFocus === 'first') {
        const focusables = getFocusableElements();
        if (focusables.length > 0) {
            setTimeout(() => focusables[0].focus(), 16);
        }
    }
    else if (autoFocus === 'last') {
        const focusables = getFocusableElements();
        if (focusables.length > 0) {
            setTimeout(() => focusables[focusables.length - 1].focus(), 16);
        }
    }
    activeTraps.push(cleanup);
    return cleanup;
}
/** Remove all currently active focus traps */
function deactivateAllFocusTraps() {
    activeTraps.forEach(fn => fn());
    activeTraps = [];
}
};

// ── module: src/aria-manager.ts ──
__mods["src/aria-manager.ts"] = function (exports, require, module) {
"use strict";
/**
 * Reusable aria-activedescendant manager for listbox/combobox patterns.
 * Tracks which option is highlighted and updates the source element's
 * aria-activedescendant accordingly.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.createAriaManager = createAriaManager;
let activeManagers = [];
/**
 * Create an aria-activedescendant manager. Returns a cleanup function.
 * Caller must call update() after DOM changes (filtering, etc.)
 */
function createAriaManager(options) {
    const { container, options: optList } = options;
    const getActiveEl = () => {
        const activeId = container.getAttribute('aria-activedescendant');
        if (!activeId)
            return null;
        return document.getElementById(activeId);
    };
    const setAriaActiveDescendant = (el) => {
        if (el) {
            container.setAttribute('aria-activedescendant', el.id);
        }
        else {
            container.removeAttribute('aria-activedescendant');
        }
    };
    // Remove old active styles
    const clearActiveOption = () => {
        optList.forEach(opt => {
            opt.classList.remove('active');
            opt.setAttribute('tabindex', '-1');
        });
    };
    const update = () => {
        const current = getActiveEl();
        if (current) {
            const idx = Array.from(optList).indexOf(current);
            if (idx !== -1) {
                clearActiveOption();
                setAriaActiveDescendant(optList[idx]);
                // Scroll into view
                optList[idx].scrollIntoView({ block: 'nearest' });
            }
        }
        else {
            clearActiveOption();
        }
    };
    const setActiveIndex = (idx) => {
        if (optList.length === 0)
            return;
        const clampedIdx = ((idx % optList.length) + optList.length) % optList.length;
        const target = optList[clampedIdx];
        clearActiveOption();
        target.classList.add('active');
        setAriaActiveDescendant(target);
        target.scrollIntoView({ block: 'nearest' });
    };
    const cleanup = () => {
        const idx = activeManagers.indexOf(cleanup);
        if (idx !== -1)
            activeManagers.splice(idx, 1);
    };
    activeManagers.push(cleanup);
    return { update, setActiveIndex };
}
};

// ── module: src/modal.ts ──
__mods["src/modal.ts"] = function (exports, require, module) {
"use strict";
/**
 * Modal Dialog Component
 * Implements focus trapping, inert overlay, escape-to-close, and
 * focus restoration to trigger on close.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.getModalState = getModalState;
exports.initModal = initModal;
const focus_trap_1 = require("./focus-trap");
let activeCleanup = null;
let triggerButton = null;
let pageInertRestore = false;
const state = { isOpen: false };
function getModalState() { return state; }
function initModal(config) {
    const { backdrop, dialog, focusContainer, closeButton, bgClose } = config;
    const handleBackdropClick = (e) => {
        if (e.target === bgClose && state.isOpen) {
            close();
        }
    };
    const open = () => {
        if (state.isOpen)
            return;
        triggerButton = document.activeElement || null;
        state.isOpen = true;
        // Make everything else inert
        document.body.style.overflow = 'hidden';
        pageInertRestore = true;
        // Activate focus trap with escape support
        activeCleanup = (0, focus_trap_1.activateFocusTrap)(focusContainer, {
            autoFocus: 'first',
            onEscape: () => close(),
        });
        // Show modal
        backdrop.setAttribute('aria-hidden', 'false');
        backdrop.removeAttribute('aria-hidden');
        // Bind backdrop click
        bgClose.addEventListener('click', handleBackdropClick);
    };
    const close = () => {
        if (!state.isOpen)
            return;
        state.isOpen = false;
        // Clean up focus trap
        if (activeCleanup) {
            activeCleanup();
            activeCleanup = null;
        }
        // Remove backdrop listener
        bgClose.removeEventListener('click', handleBackdropClick);
        // Hide modal
        backdrop.setAttribute('aria-hidden', 'true');
        // Restore body scroll
        document.body.style.overflow = '';
        pageInertRestore = false;
        // Return focus to trigger
        if (triggerButton) {
            setTimeout(() => triggerButton?.focus(), 16);
        }
    };
    // Wire close button
    closeButton.addEventListener('click', close);
    return { open, close };
}
};

// ── module: src/dropdown-menu.ts ──
__mods["src/dropdown-menu.ts"] = function (exports, require, module) {
"use strict";
/**
 * Dropdown Menu Component
 * Implements arrow-key navigation, home/end jump, escape-to-close, and
 * proper aria-haspopup / aria-expanded state management.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.getMenuState = getMenuState;
exports.initDropdownMenu = initDropdownMenu;
const state = { isOpen: false, activeIndex: -1 };
function getMenuState() { return state; }
let cleanupKeydown = null;
function initDropdownMenu(config) {
    const { trigger, menu, items, onSelect } = config;
    // Build menu DOM
    menu.innerHTML = '';
    menu.setAttribute('role', 'menu');
    menu.setAttribute('aria-labelledby', trigger.id);
    items.forEach((item, idx) => {
        if (item.separator) {
            const sep = document.createElement('div');
            sep.className = 'menu-separator';
            sep.setAttribute('role', 'separator');
            menu.appendChild(sep);
        }
        else {
            const btn = document.createElement('button');
            btn.className = 'menu-item';
            btn.setAttribute('role', 'menuitem');
            btn.setAttribute('tabindex', '-1');
            btn.dataset.index = String(idx);
            btn.textContent = item.label ?? '';
            menu.appendChild(btn);
        }
    });
    const menuItems = () => {
        return Array.from(menu.querySelectorAll('button[role="menuitem"]'));
    };
    const clearActive = () => {
        menuItems().forEach(item => item.classList.remove('active'));
    };
    const setActive = (idx) => {
        clearActive();
        const list = menuItems();
        if (idx < 0)
            idx = list.length - 1;
        if (idx >= list.length)
            idx = 0;
        list[idx].classList.add('active');
        list[idx].setAttribute('tabindex', '0');
    };
    const openMenu = () => {
        if (state.isOpen)
            return;
        state.isOpen = true;
        trigger.setAttribute('aria-expanded', 'true');
        menu.style.display = 'block';
        menu.setAttribute('data-visible', 'true');
        // Reset and activate first item
        menuItems().forEach(item => item.setAttribute('tabindex', '-1'));
        state.activeIndex = 0;
        setActive(0);
        bindKeyDown();
    };
    const closeMenu = () => {
        if (!state.isOpen)
            return;
        state.isOpen = false;
        trigger.setAttribute('aria-expanded', 'false');
        menu.removeAttribute('data-visible');
        setTimeout(() => { menu.style.display = 'none'; }, 50);
        clearActive();
        menuItems().forEach(item => item.setAttribute('tabindex', '-1'));
        state.activeIndex = -1;
        unbindKeyDown();
        trigger.focus();
    };
    const selectItem = (idx) => {
        const item = items[idx];
        if (item && !item.separator) {
            onSelect(item);
        }
        closeMenu();
    };
    const handleTriggerClick = () => {
        if (state.isOpen) {
            closeMenu();
        }
        else {
            openMenu();
        }
    };
    const onKeyDown = (e) => {
        if (!state.isOpen)
            return;
        const list = menuItems();
        switch (e.key) {
            case 'Escape':
                e.preventDefault();
                closeMenu();
                break;
            case 'ArrowDown':
                e.preventDefault();
                state.activeIndex = ((state.activeIndex + 1) % list.length + list.length) % list.length;
                setActive(state.activeIndex);
                list[state.activeIndex].focus();
                break;
            case 'ArrowUp':
                e.preventDefault();
                state.activeIndex = ((state.activeIndex - 1 + list.length) % list.length);
                setActive(state.activeIndex);
                list[state.activeIndex].focus();
                break;
            case 'Home':
                e.preventDefault();
                state.activeIndex = 0;
                setActive(0);
                list[0].focus();
                break;
            case 'End':
                e.preventDefault();
                state.activeIndex = list.length - 1;
                setActive(state.activeIndex);
                list[list.length - 1].focus();
                break;
            case 'Tab':
                closeMenu();
                break;
            case 'Enter':
            case ' ':
                e.preventDefault();
                selectItem(state.activeIndex);
                break;
        }
    };
    const bindKeyDown = () => {
        cleanupKeydown = () => { };
        const handler = (e) => {
            if (e.key === 'Escape' || e.key === 'ArrowDown' || e.key === 'ArrowUp' ||
                e.key === 'Home' || e.key === 'End' || e.key === 'Tab') {
                onKeyDown(e);
            }
        };
        document.addEventListener('keydown', handler);
        cleanupKeydown = () => document.removeEventListener('keydown', handler);
    };
    const unbindKeyDown = () => {
        if (cleanupKeydown) {
            cleanupKeydown();
            cleanupKeydown = null;
        }
    };
    trigger.addEventListener('click', handleTriggerClick);
    // Handle keyboard on trigger
    trigger.addEventListener('keydown', (e) => {
        if (!state.isOpen && (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ')) {
            e.preventDefault();
            openMenu();
        }
    });
    return { close: closeMenu };
}
};

// ── module: src/tabs.ts ──
__mods["src/tabs.ts"] = function (exports, require, module) {
"use strict";
/**
 * Tabs Component
 * Implements correct tablist/tab/tabpanel relationships, roving tabindex,
 * and arrow key navigation between horizontally-arranged tabs.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.getTabsState = getTabsState;
exports.initTabs = initTabs;
const state = { activeIndex: 0 };
function getTabsState() { return state; }
function initTabs(config) {
    const { tablist } = config;
    const getTabs = () => {
        return Array.from(tablist.querySelectorAll('button[role="tab"]'));
    };
    const getPanels = () => {
        const tabs = getTabs();
        return tabs.map(tab => {
            const panelId = tab.getAttribute('aria-controls');
            return panelId ? document.getElementById(panelId) : null;
        }).filter((p) => p !== null);
    };
    const setActiveTab = (idx) => {
        const tabs = getTabs();
        const panels = getPanels();
        if (idx < 0 || idx >= tabs.length)
            return;
        state.activeIndex = idx;
        tabs.forEach((tab, i) => {
            const isSelected = i === idx;
            tab.setAttribute('aria-selected', String(isSelected));
            tab.tabIndex = isSelected ? 0 : -1;
            // Show/hide panels
            const panel = panels[i];
            if (panel) {
                if (isSelected) {
                    panel.removeAttribute('hidden');
                }
                else {
                    panel.setAttribute('hidden', '');
                }
            }
        });
    };
    const onKeyDown = (e) => {
        const tabs = getTabs();
        let newIndex = state.activeIndex;
        switch (e.key) {
            case 'ArrowRight':
                e.preventDefault();
                newIndex = (state.activeIndex + 1) % tabs.length;
                break;
            case 'ArrowLeft':
                e.preventDefault();
                newIndex = ((state.activeIndex - 1 + tabs.length) % tabs.length);
                break;
            case 'Home':
                e.preventDefault();
                newIndex = 0;
                break;
            case 'End':
                e.preventDefault();
                newIndex = tabs.length - 1;
                break;
            default:
                return;
        }
        setActiveTab(newIndex);
        tabs[newIndex].focus();
    };
    tablist.addEventListener('keydown', onKeyDown);
    // Click on tabs for mouse users
    tablist.addEventListener('click', (e) => {
        const tab = e.target.closest('button[role="tab"]');
        if (!tab)
            return;
        const tabs = getTabs();
        const idx = tabs.indexOf(tab);
        if (idx !== -1) {
            setActiveTab(idx);
            tab.focus();
        }
    });
    return {
        cleanup: () => {
            tablist.removeEventListener('keydown', onKeyDown);
            tablist.removeEventListener('click', () => { });
        },
    };
}
};

// ── module: src/combobox.ts ──
__mods["src/combobox.ts"] = function (exports, require, module) {
"use strict";
/**
 * Combobox (Autocomplete) Component
 * Implements aria-activedescendant pattern for listbox selection,
 * client-side filtering, and proper keyboard interaction semantics.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.getComboboxState = getComboboxState;
exports.initCombobox = initCombobox;
const COUNTRIES = [
    'Argentina', 'Australia', 'Austria', 'Belgium', 'Brazil',
    'Canada', 'China', 'Denmark', 'Egypt', 'Finland',
    'France', 'Germany', 'Greece', 'India', 'Indonesia',
    'Ireland', 'Italy', 'Japan', 'Kenya', 'Malaysia',
    'Mexico', 'Netherlands', 'New Zealand', 'Nigeria', 'Norway',
    'Peru', 'Philippines', 'Poland', 'Portugal', 'Romania',
    'Russia', 'South Africa', 'South Korea', 'Spain', 'Sweden',
    'Switzerland', 'Thailand', 'Turkey', 'United Kingdom', 'Vietnam',
];
const state = {
    isOpen: false,
    filtered: [...COUNTRIES],
    activeIndex: -1,
};
function getComboboxState() { return state; }
let allOptions = [];
let currentInputValue = '';
let outsideClickListener = null;
function initCombobox(config) {
    const { input, listbox, onSelect, resultDisplay } = config;
    allOptions = [];
    currentInputValue = input.value;
    // Initial filter
    state.filtered = [...COUNTRIES];
    const buildList = () => {
        listbox.innerHTML = '';
        if (state.filtered.length === 0) {
            const noResults = document.createElement('div');
            noResults.className = 'option-count';
            noResults.textContent = 'No countries found';
            listbox.appendChild(noResults);
            allOptions = [];
            clearActive();
            updateVisibility();
            return;
        }
        // Show option count header
        const header = document.createElement('div');
        header.className = 'option-count';
        header.textContent = `Showing ${state.filtered.length} of ${COUNTRIES.length}`;
        listbox.appendChild(header);
        state.filtered.forEach((country, idx) => {
            const btn = document.createElement('button');
            btn.className = 'listbox-option';
            btn.setAttribute('role', 'option');
            btn.id = `cb-option-${idx}`;
            btn.setAttribute('tabindex', '-1');
            btn.textContent = country;
            btn.dataset.country = country;
            btn.addEventListener('click', () => selectItem(idx));
            listbox.appendChild(btn);
        });
        allOptions = Array.from(listbox.querySelectorAll('button.listbox-option'));
        updateVisibility();
    };
    const clearActive = () => {
        allOptions.forEach(opt => opt.classList.remove('active'));
        input.removeAttribute('aria-activedescendant');
        state.activeIndex = -1;
    };
    const setActiveAndHighlight = (idx) => {
        if (allOptions.length === 0)
            return;
        const clampedIdx = ((idx % allOptions.length) + allOptions.length) % allOptions.length;
        clearActive();
        allOptions[clampedIdx].classList.add('active');
        input.setAttribute('aria-activedescendant', allOptions[clampedIdx].id);
        state.activeIndex = clampedIdx;
        allOptions[clampedIdx].scrollIntoView({ block: 'nearest' });
    };
    const updateVisibility = () => {
        if (state.isOpen && input.value.trim().length > 0 && state.filtered.length > 0) {
            listbox.setAttribute('data-visible', 'true');
            listbox.setAttribute('aria-hidden', 'false');
        }
        else {
            listbox.removeAttribute('data-visible');
            listbox.setAttribute('aria-hidden', 'true');
        }
    };
    const openList = () => {
        if (input.value.trim().length === 0)
            return;
        state.isOpen = true;
        input.setAttribute('aria-expanded', 'true');
        buildList();
        updateVisibility();
    };
    const closeList = (revert = false) => {
        state.isOpen = false;
        input.setAttribute('aria-expanded', 'false');
        if (revert) {
            input.value = currentInputValue;
        }
        clearActive();
        updateVisibility();
    };
    const filterItems = (query) => {
        const lower = query.toLowerCase();
        state.filtered = COUNTRIES.filter(c => c.toLowerCase().includes(lower));
        state.activeIndex = -1;
    };
    const selectItem = (idx) => {
        if (idx < 0 || idx >= allOptions.length)
            return;
        const selected = allOptions[idx].dataset.country;
        if (selected) {
            currentInputValue = selected;
            input.value = selected;
            onSelect(selected);
            resultDisplay.textContent = `Selected: ${selected}`;
        }
        closeList();
    };
    const handleInput = () => {
        currentInputValue = input.value;
        if (currentInputValue.length > 0) {
            filterItems(currentInputValue);
            openList();
        }
        else {
            state.filtered = [...COUNTRIES];
            clearActive();
            closeList(true);
        }
    };
    const handleKeyDown = (e) => {
        if (e.key === 'ArrowDown') {
            e.preventDefault();
            if (!state.isOpen) {
                if (currentInputValue.length > 0)
                    openList();
                else
                    return;
            }
            if (allOptions.length > 0) {
                setActiveAndHighlight(state.activeIndex + 1);
            }
        }
        else if (e.key === 'ArrowUp') {
            e.preventDefault();
            if (!state.isOpen)
                return;
            if (allOptions.length > 0) {
                setActiveAndHighlight(state.activeIndex - 1);
            }
        }
        else if (e.key === 'Enter') {
            e.preventDefault();
            if (state.isOpen && allOptions.length > 0 && state.activeIndex >= 0) {
                selectItem(state.activeIndex);
            }
        }
        else if (e.key === 'Escape') {
            e.preventDefault();
            closeList(true);
        }
    };
    const handleClickOutside = (e) => {
        if (state.isOpen && !input.contains(e.target) && !listbox.contains(e.target)) {
            closeList(true);
        }
    };
    // Bind events
    input.addEventListener('input', handleInput);
    input.addEventListener('keydown', handleKeyDown);
    outsideClickListener = handleClickOutside;
    document.addEventListener('click', outsideClickListener);
    // Close on focusout (with slight delay to allow click propagation)
    input.addEventListener('blur', () => {
        setTimeout(() => {
            const relatedTarget = document.activeElement;
            if (relatedTarget?.closest(`#${listbox.id}`)) {
                return;
            }
            if (state.isOpen) {
                closeList(true);
            }
        }, 50);
    });
    return {
        cleanup: () => {
            input.removeEventListener('input', handleInput);
            input.removeEventListener('keydown', handleKeyDown);
            input.removeEventListener('blur', () => { });
            if (outsideClickListener) {
                document.removeEventListener('click', outsideClickListener);
                outsideClickListener = null;
            }
        },
    };
}
};

// ── module: src/accordion.ts ──
__mods["src/accordion.ts"] = function (exports, require, module) {
"use strict";
/**
 * Accordion / Disclosure Group Component
 * Each trigger toggles its associated region. Multiple sections may be open.
 * Animates height transition, then toggles hidden attribute for correctness.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.initAccordion = initAccordion;
function initAccordion(config) {
    const { container } = config;
    // Respect prefers-reduced-motion
    const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
    const handleTriggerClick = (trigger) => {
        const isExpanded = trigger.getAttribute('aria-expanded') === 'true';
        toggleRegion(trigger, isExpanded);
    };
    const toggleRegion = (trigger, forceClose) => {
        const panelId = trigger.getAttribute('aria-controls');
        if (!panelId)
            return;
        const panel = document.getElementById(panelId);
        if (!panel)
            return;
        const isCurrentlyOpen = trigger.getAttribute('aria-expanded') === 'true';
        const willBeOpen = forceClose !== undefined ? !forceClose : !isCurrentlyOpen;
        trigger.setAttribute('aria-expanded', String(willBeOpen));
        if (willBeOpen) {
            // Open: measure full height, set it explicitly, animate
            panel.style.height = 'auto';
            const scrollHeight = panel.scrollHeight;
            panel.style.height = '0px';
            panel.removeAttribute('hidden');
            requestAnimationFrame(() => {
                panel.style.height = `${scrollHeight}px`;
            });
            // When animation ends, set auto height and remove transition
            const onTransitionEnd = () => {
                panel.removeEventListener('transitionend', onTransitionEnd);
                if (trigger.getAttribute('aria-expanded') === 'true') {
                    panel.style.height = 'auto';
                    // Remove transition for closed state to be instant when closing
                    setTimeout(() => {
                        panel.style.transition = '';
                    }, 50);
                }
            };
            panel.addEventListener('transitionend', onTransitionEnd);
        }
        else {
            // Close: measure current height, then collapse to 0 with transition
            panel.style.height = `${panel.scrollHeight}px`;
            // Force reflow
            panel.offsetHeight;
            requestAnimationFrame(() => {
                panel.style.height = '0px';
            });
            const onTransitionEnd = () => {
                panel.removeEventListener('transitionend', onTransitionEnd);
                if (trigger.getAttribute('aria-expanded') === 'false') {
                    panel.setAttribute('hidden', '');
                    panel.style.height = '';
                    // Reset transition for next open
                    setTimeout(() => {
                        panel.style.transition = '';
                    }, 50);
                }
            };
            panel.addEventListener('transitionend', onTransitionEnd);
        }
    };
    // Keyboard navigation between accordion triggers using arrow keys
    const getTriggers = () => {
        return Array.from(container.querySelectorAll('button.accordion-trigger'));
    };
    const handleKeyDown = (e) => {
        const triggers = getTriggers();
        const currentTrigger = e.target;
        if (!currentTrigger || !currentTrigger.classList.contains('accordion-trigger'))
            return;
        const currentIndex = triggers.indexOf(currentTrigger);
        switch (e.key) {
            case 'ArrowDown':
                e.preventDefault();
                triggers[(currentIndex + 1) % triggers.length].focus();
                break;
            case 'ArrowUp':
                e.preventDefault();
                triggers[(currentIndex - 1 + triggers.length) % triggers.length].focus();
                break;
            case 'Home':
                e.preventDefault();
                triggers[0].focus();
                break;
            case 'End':
                e.preventDefault();
                triggers[triggers.length - 1].focus();
                break;
        }
    };
    // Bind click handlers to all triggers
    const triggers = getTriggers();
    const clickHandlers = new Map();
    triggers.forEach(trigger => {
        // Initialize first item's region based on aria-expanded
        const panelId = trigger.getAttribute('aria-controls');
        if (panelId) {
            const panel = document.getElementById(panelId);
            if (panel) {
                const isExpanded = trigger.getAttribute('aria-expanded') === 'true';
                if (!isExpanded) {
                    panel.setAttribute('hidden', '');
                    panel.style.height = '0px';
                }
                else {
                    panel.style.height = 'auto';
                }
            }
        }
        const handler = () => handleTriggerClick(trigger);
        trigger.addEventListener('click', handler);
        trigger.addEventListener('keydown', handleKeyDown);
        clickHandlers.set(trigger, handler);
    });
    // Expose toggle for external use
    container.__toggleAccordion = toggleRegion;
    container.__getTriggers = getTriggers;
    container.__cleanupTriggers = () => {
        triggers.forEach((t, i) => {
            const h = clickHandlers.get(t);
            if (h)
                t.removeEventListener('click', h);
            t.removeEventListener('keydown', handleKeyDown);
        });
        clickHandlers.clear();
    };
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>