← Accessible Components results

Accessible Components

Qwen 3.6 27b · 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 62,241 bytes · SHA-256 ecef25c5db67
<!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: base ── */
/* ─── Reset & Base ─────────────────────────────────────────────────────────── */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

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

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  font-size: 15px;
  line-height: 1.6;
  color: #1a1d23;
  background: #ffffff;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: 'SF Mono', 'Fira Code', 'JetBrains Mono', Consolas, monospace;
  font-size: 0.875em;
  background: #f1f3f7;
  padding: 0.15em 0.35em;
  border-radius: 3px;
}

kbd {
  font-family: 'SF Mono', 'Fira Code', monospace;
  font-size: 0.8em;
  background: #f1f3f7;
  border: 1px solid #d8dbe4;
  border-bottom-width: 2px;
  border-radius: 4px;
  padding: 0.1em 0.4em;
  white-space: nowrap;
}

/* ─── Skip Link ────────────────────────────────────────────────────────────── */
.a11y-skip-link {
  position: absolute;
  top: -100%;
  left: 16px;
  z-index: 2000;
  background: #3b5bdb;
  color: #ffffff;
  padding: 10px 20px;
  border-radius: 0 0 8px 8px;
  font-weight: 600;
  text-decoration: none;
  font-size: 14px;
  transition: top 0.15s ease;
}

.a11y-skip-link:focus {
  top: 0;
  outline: 3px solid #3b5bdb;
  outline-offset: 2px;
}

/* ─── Screen Reader Only ──────────────────────────────────────────────────── */
.a11y-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ─── Focus Indicators ────────────────────────────────────────────────────── */
:focus-visible {
  outline: 3px solid #3b5bdb;
  outline-offset: 2px;
}

/* Remove outline from native focus for elements that use :focus-visible */
:focus:not(:focus-visible) {
  outline: none;
}

/* ─── Layout ──────────────────────────────────────────────────────────────── */
#app {
  display: flex;
  min-height: 100vh;
}

.a11y-sidebar {
  position: sticky;
  top: 0;
  width: 240px;
  height: 100vh;
  flex-shrink: 0;
  background: #f8f9fb;
  border-right: 1px solid #e2e5ec;
  padding: 32px 0;
  overflow-y: auto;
  z-index: 100;
}

.a11y-sidebar nav ul {
  list-style: none;
  padding: 0 16px;
}

.a11y-sidebar nav li {
  margin-bottom: 2px;
}

.a11y-sidebar nav a {
  display: block;
  padding: 8px 16px;
  color: #5f6577;
  text-decoration: none;
  font-size: 14px;
  font-weight: 500;
  border-radius: 6px;
  border-left: 3px solid transparent;
  transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}

.a11y-sidebar nav a:hover {
  color: #1a1d23;
  background: #eef0f5;
}

.a11y-sidebar nav a:focus-visible {
  outline: 3px solid #3b5bdb;
  outline-offset: -3px;
  background: #e7ebf9;
  border-left-color: #3b5bdb;
}

.a11y-sidebar nav a.active {
  color: #3b5bdb;
  background: #e7ebf9;
  border-left-color: #3b5bdb;
}

.a11y-main {
  flex: 1;
  max-width: 860px;
  padding: 48px 56px 80px;
  min-width: 0;
}

/* ─── Header ──────────────────────────────────────────────────────────────── */
.a11y-header {
  margin-bottom: 56px;
  padding-bottom: 32px;
  border-bottom: 1px solid #e2e5ec;
}

.a11y-header h1 {
  font-size: 32px;
  font-weight: 700;
  color: #1a1d23;
  letter-spacing: -0.02em;
  margin-bottom: 12px;
  line-height: 1.2;
}

.a11y-subtitle {
  font-size: 16px;
  color: #5f6577;
  max-width: 600px;
  line-height: 1.7;
}

/* ─── Sections ────────────────────────────────────────────────────────────── */
.a11y-section {
  margin-bottom: 64px;
  padding-top: 16px;
  scroll-margin-top: 24px;
}

.a11y-section h2 {
  font-size: 22px;
  font-weight: 700;
  color: #1a1d23;
  margin-bottom: 8px;
  letter-spacing: -0.01em;
}

.a11y-section-desc {
  color: #5f6577;
  margin-bottom: 8px;
  font-size: 14px;
  line-height: 1.6;
}

.a11y-section-contract {
  font-size: 12px;
  color: #8b90a0;
  margin-bottom: 20px;
  font-family: 'SF Mono', 'Fira Code', monospace;
  background: #f8f9fb;
  display: inline-block;
  padding: 4px 10px;
  border-radius: 4px;
  border: 1px solid #e2e5ec;
}

.a11y-widget-container {
  border: 1px solid #e2e5ec;
  border-radius: 8px;
  padding: 24px;
  background: #ffffff;
  min-height: 60px;
  position: relative;
}

/* ─── Buttons ─────────────────────────────────────────────────────────────── */
.a11y-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 10px 20px;
  font-size: 14px;
  font-weight: 600;
  font-family: inherit;
  border: 1px solid transparent;
  border-radius: 6px;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease;
  line-height: 1.4;
}

.a11y-btn:active {
  transform: scale(0.98);
}

.a11y-btn-primary {
  background: #3b5bdb;
  color: #ffffff;
  border-color: #3b5bdb;
}

.a11y-btn-primary:hover {
  background: #3047c5;
  border-color: #3047c5;
}

.a11y-btn-primary:focus-visible {
  outline: 3px solid #3b5bdb;
  outline-offset: 2px;
}

/* ─── Modal ───────────────────────────────────────────────────────────────── */
.a11y-modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: rgba(0, 0, 0, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.a11y-modal-dialog {
  position: relative;
  background: #ffffff;
  border-radius: 12px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2), 0 4px 16px rgba(0, 0, 0, 0.1);
  max-width: 520px;
  width: 100%;
  padding: 32px;
  max-height: 85vh;
  overflow-y: auto;
}

.a11y-modal-title {
  font-size: 20px;
  font-weight: 700;
  color: #1a1d23;
  margin-bottom: 12px;
  line-height: 1.3;
}

.a11y-modal-close {
  position: absolute;
  top: 16px;
  right: 16px;
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #f1f3f7;
  border: 1px solid #e2e5ec;
  border-radius: 6px;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  color: #5f6577;
  transition: background 0.15s ease, color 0.15s ease;
  font-family: inherit;
}

.a11y-modal-close:hover {
  background: #e2e5ec;
  color: #1a1d23;
}

.a11y-modal-close:focus-visible {
  outline: 3px solid #3b5bdb;
  outline-offset: 2px;
}

.a11y-modal-content {
  color: #5f6577;
  font-size: 14px;
  line-height: 1.7;
  margin-top: 8px;
}

/* ─── Dropdown Menu ───────────────────────────────────────────────────────── */
.a11y-menu-trigger {
  position: relative;
}

.a11y-menu-list {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 200;
  min-width: 180px;
  background: #ffffff;
  border: 1px solid #e2e5ec;
  border-radius: 8px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1), 0 2px 6px rgba(0, 0, 0, 0.06);
  list-style: none;
  padding: 6px;
}

.a11y-menu-item {
  padding: 8px 12px;
  font-size: 14px;
  color: #1a1d23;
  border-radius: 5px;
  cursor: pointer;
  transition: background 0.1s ease, color 0.1s ease;
  white-space: nowrap;
}

.a11y-menu-item:hover,
.a11y-menu-item:focus-visible {
  background: #e7ebf9;
  color: #3b5bdb;
}

.a11y-menu-item:focus-visible {
  outline: 3px solid #3b5bdb;
  outline-offset: -3px;
}

.a11y-feedback {
  margin-top: 12px;
  padding: 8px 14px;
  background: #edfae6;
  color: #2b8a3e;
  border-radius: 6px;
  font-size: 13px;
  font-weight: 500;
  display: none;
  border: 1px solid #b4e6a3;
}

/* ─── Tabs ────────────────────────────────────────────────────────────────── */
.a11y-tablist {
  display: flex;
  gap: 0;
  border-bottom: 2px solid #e2e5ec;
  margin-bottom: 0;
}

.a11y-tab {
  padding: 10px 20px;
  font-size: 14px;
  font-weight: 500;
  font-family: inherit;
  background: none;
  border: none;
  color: #5f6577;
  cursor: pointer;
  position: relative;
  transition: color 0.15s ease;
  border-bottom: 2px solid transparent;
  margin-bottom: -2px;
}

.a11y-tab:hover {
  color: #1a1d23;
}

.a11y-tab[aria-selected="true"] {
  color: #3b5bdb;
  border-bottom-color: #3b5bdb;
  font-weight: 600;
}

.a11y-tab:focus-visible {
  outline: 3px solid #3b5bdb;
  outline-offset: -3px;
}

.a11y-tab-panel {
  padding: 20px 0 0;
  color: #5f6577;
  font-size: 14px;
  line-height: 1.7;
}

.a11y-tab-panel p {
  margin-bottom: 12px;
}

.a11y-tab-panel p:last-child {
  margin-bottom: 0;
}

.a11y-tab-panel ul {
  margin: 0 0 0 20px;
}

.a11y-tab-panel li {
  margin-bottom: 6px;
}

.a11y-code-block {
  background: #1a1d23;
  color: #e2e5ec;
  padding: 16px;
  border-radius: 6px;
  overflow-x: auto;
  font-size: 13px;
  line-height: 1.6;
}

.a11y-code-block pre {
  margin: 0;
  font-family: 'SF Mono', 'Fira Code', monospace;
}

/* ─── Combobox ────────────────────────────────────────────────────────────── */
.a11y-combobox-input {
  width: 100%;
  max-width: 360px;
  padding: 10px 14px;
  font-size: 14px;
  font-family: inherit;
  border: 1px solid #d8dbe4;
  border-radius: 6px;
  background: #ffffff;
  color: #1a1d23;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
  line-height: 1.5;
}

.a11y-combobox-input::placeholder {
  color: #8b90a0;
}

.a11y-combobox-input:focus {
  outline: none;
  border-color: #3b5bdb;
  box-shadow: 0 0 0 3px rgba(59, 91, 219, 0.15);
}

.a11y-combobox-listbox {
  position: absolute;
  z-index: 200;
  width: 100%;
  max-width: 360px;
  max-height: 200px;
  overflow-y: auto;
  background: #ffffff;
  border: 1px solid #e2e5ec;
  border-radius: 8px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1), 0 2px 6px rgba(0, 0, 0, 0.06);
  list-style: none;
  padding: 6px;
  margin-top: 4px;
}

.a11y-combobox-option {
  padding: 8px 12px;
  font-size: 14px;
  color: #1a1d23;
  border-radius: 5px;
  cursor: pointer;
  transition: background 0.1s ease;
}

.a11y-combobox-option:hover {
  background: #f1f3f7;
}

.a11y-combobox-option[aria-selected="true"] {
  background: #e7ebf9;
  color: #3b5bdb;
  font-weight: 500;
}

/* ─── Accordion ───────────────────────────────────────────────────────────── */
.a11y-accordion-button {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 16px 0;
  font-size: 15px;
  font-weight: 600;
  font-family: inherit;
  background: none;
  border: none;
  border-bottom: 1px solid #e2e5ec;
  color: #1a1d23;
  cursor: pointer;
  text-align: left;
  transition: color 0.15s ease;
}

.a11y-accordion-button:hover {
  color: #3b5bdb;
}

.a11y-accordion-button:focus-visible {
  outline: 3px solid #3b5bdb;
  outline-offset: 2px;
  border-radius: 4px;
}

.a11y-accordion-icon {
  font-size: 12px;
  transition: transform 0.2s ease;
  flex-shrink: 0;
  margin-left: 12px;
  color: #8b90a0;
}

.a11y-accordion-button[aria-expanded="true"] .a11y-accordion-icon {
  transform: rotate(90deg);
  color: #3b5bdb;
}

.a11y-accordion-title {
  flex: 1;
}

.a11y-accordion-region {
  overflow: hidden;
  padding: 0 0 16px;
}

.a11y-accordion-region p {
  color: #5f6577;
  font-size: 14px;
  line-height: 1.7;
  margin-bottom: 8px;
}

.a11y-accordion-region p:last-child {
  margin-bottom: 0;
}

.a11y-accordion-region ul {
  margin: 0 0 0 20px;
}

.a11y-accordion-region li {
  color: #5f6577;
  font-size: 14px;
  line-height: 1.7;
  margin-bottom: 6px;
}

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

  html {
    scroll-behavior: auto;
  }
}

/* ─── Responsive ──────────────────────────────────────────────────────────── */
@media (max-width: 768px) {
  .a11y-sidebar {
    display: none;
  }

  .a11y-main {
    padding: 32px 24px 64px;
  }

  .a11y-header h1 {
    font-size: 26px;
  }
}
</style>
</head>
<body>
<a class="a11y-skip-link" id="skip-link" href="#main-content">Skip to main content</a>
<aside class="a11y-sidebar" id="sidebar" aria-label="Component navigation">
  <nav>
    <ul>
      <li><a href="#section-modal">Modal Dialog</a></li>
      <li><a href="#section-menu">Dropdown Menu</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>
  </nav>
</aside>
<main class="a11y-main" id="main-content">
  <header class="a11y-header">
    <h1>Accessible Component Showcase</h1>
    <p class="a11y-subtitle">A fully keyboard- and screen-reader-accessible component library built with vanilla JavaScript, following WAI-ARIA Authoring Practices.</p>
  </header>
  <section class="a11y-section" id="section-modal" aria-labelledby="heading-modal">
    <h2 id="heading-modal">Modal Dialog</h2>
    <p class="a11y-section-desc">Opens a focus-trapped overlay. Press Escape or click the backdrop to close. Focus returns to the trigger on close.</p>
    <div class="a11y-section-contract"><kbd>Enter</kbd> / <kbd>Space</kbd> open · <kbd>Esc</kbd> close · Tab trapped inside</div>
    <div class="a11y-widget-container" id="widget-modal"></div>
  </section>
  <section class="a11y-section" id="section-menu" aria-labelledby="heading-menu">
    <h2 id="heading-menu">Dropdown Menu</h2>
    <p class="a11y-section-desc">A button-activated menu with full keyboard navigation. Arrow keys move between items with wrapping.</p>
    <div class="a11y-section-contract"><kbd>Enter</kbd> / <kbd>Space</kbd> / <kbd>↓</kbd> open · <kbd>↑</kbd><kbd>↓</kbd> navigate · <kbd>Home</kbd> / <kbd>End</kbd> jump · <kbd>Esc</kbd> close</div>
    <div class="a11y-widget-container" id="widget-menu"></div>
  </section>
  <section class="a11y-section" id="section-tabs" aria-labelledby="heading-tabs">
    <h2 id="heading-tabs">Tabs</h2>
    <p class="a11y-section-desc">Only the active tab is in the tab order. Arrow keys move between tabs. Hidden panels use the <code>hidden</code> attribute.</p>
    <div class="a11y-section-contract"><kbd>←</kbd> / <kbd>→</kbd> switch tabs · <kbd>Home</kbd> / <kbd>End</kbd> jump · <kbd>Tab</kbd> enters panel</div>
    <div class="a11y-widget-container" id="widget-tabs"></div>
  </section>
  <section class="a11y-section" id="section-combobox" aria-labelledby="heading-combobox">
    <h2 id="heading-combobox">Combobox</h2>
    <p class="a11y-section-desc">Type to filter a list of options. Arrow keys highlight items; Enter selects. Uses the aria-activedescendant pattern.</p>
    <div class="a11y-section-contract">Type to filter · <kbd>↓</kbd> / <kbd>↑</kbd> highlight · <kbd>Enter</kbd> select · <kbd>Esc</kbd> close &amp; revert</div>
    <div class="a11y-widget-container" id="widget-combobox"></div>
  </section>
  <section class="a11y-section" id="section-accordion" aria-labelledby="heading-accordion">
    <h2 id="heading-accordion">Accordion</h2>
    <p class="a11y-section-desc">Expandable sections with animated open/close. Multiple sections may be open simultaneously.</p>
    <div class="a11y-section-contract"><kbd>Enter</kbd> / <kbd>Space</kbd> toggle · multiple open allowed</div>
    <div class="a11y-widget-container" id="widget-accordion"></div>
  </section>
</main>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./modal":"src/modal.ts","./menu":"src/menu.ts","./tabs":"src/tabs.ts","./combobox":"src/combobox.ts","./accordion":"src/accordion.ts"},"src/modal.ts":{"./constants":"src/constants.ts","./helpers":"src/helpers.ts"},"src/menu.ts":{"./constants":"src/constants.ts","./helpers":"src/helpers.ts"},"src/tabs.ts":{"./helpers":"src/helpers.ts"},"src/combobox.ts":{"./constants":"src/constants.ts","./helpers":"src/helpers.ts"},"src/accordion.ts":{"./constants":"src/constants.ts","./helpers":"src/helpers.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 ──────────────────────────────────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.start = start;
const modal_1 = require("./modal");
const menu_1 = require("./menu");
const tabs_1 = require("./tabs");
const combobox_1 = require("./combobox");
const accordion_1 = require("./accordion");
function start() {
    // ─── Modal Dialog ──────────────────────────────────────────────────────
    const modalSection = document.getElementById('section-modal');
    const modalWidget = document.getElementById('widget-modal');
    if (modalSection && modalWidget) {
        const modal = new modal_1.ModalDialog(modalWidget, {
            title: 'Confirmation Dialog',
            content: 'This modal traps focus. Tab and Shift+Tab cycle within the dialog. Press Escape or click the backdrop to close. Focus returns to the trigger button on close.',
        });
        modalWidget.insertBefore(modal.getTrigger(), modalWidget.firstChild);
        modal.mount();
    }
    // ─── Dropdown Menu ─────────────────────────────────────────────────────
    const menuSection = document.getElementById('section-menu');
    const menuWidget = document.getElementById('widget-menu');
    if (menuSection && menuWidget) {
        const actions = [];
        const menuItems = [
            { label: 'Copy', action: () => actions.push('Copied to clipboard') },
            { label: 'Edit', action: () => actions.push('Editing selected item') },
            { label: 'Share', action: () => actions.push('Share dialog opened') },
            { label: 'Delete', action: () => actions.push('Item deleted') },
        ];
        const menu = new menu_1.DropdownMenu(menuWidget, 'Actions ▾', menuItems);
        menu.mount();
        // Feedback display
        const feedback = document.createElement('div');
        feedback.className = 'a11y-feedback';
        feedback.id = 'menu-feedback';
        menuWidget.appendChild(feedback);
        // Intercept actions to show feedback
        const originalItems = [...menuItems];
        originalItems.forEach((item, i) => {
            const origAction = item.action;
            item.action = () => {
                origAction();
                feedback.textContent = actions[actions.length - 1];
                feedback.style.display = 'block';
                setTimeout(() => { feedback.style.display = 'none'; }, 2000);
            };
        });
    }
    // ─── Tabs ──────────────────────────────────────────────────────────────
    const tabsWidget = document.getElementById('widget-tabs');
    if (tabsWidget) {
        const tabItems = [
            {
                label: 'Overview',
                content: `<p>This component demonstrates proper ARIA tab semantics. Only the active tab is in the tab order — arrow keys move between tabs. Tab panels use the <code>hidden</code> attribute, not just CSS hiding.</p>
          <p>Try pressing <kbd>←</kbd> and <kbd>→</kbd> to navigate between tabs, or <kbd>Home</kbd> / <kbd>End</kbd> to jump to the first or last tab.</p>`,
            },
            {
                label: 'Features',
                content: `<ul>
            <li>Single tab in tab order (roving tabindex pattern)</li>
            <li>Arrow keys move between tabs with wrapping</li>
            <li>Home/End jump to first/last tab</li>
            <li>Hidden panels use the <code>hidden</code> attribute</li>
            <li>Full keyboard and screen reader support</li>
          </ul>`,
            },
            {
                label: 'Code Example',
                content: `<div class="a11y-code-block"><pre><code>&lt;div role="tablist"&gt;
  &lt;button role="tab"
          aria-selected="true"
          aria-controls="panel-1"&gt;Tab 1&lt;/button&gt;
  &lt;button role="tab"
          aria-selected="false"
          aria-controls="panel-2"&gt;Tab 2&lt;/button&gt;
&lt;/div&gt;
&lt;div role="tabpanel" id="panel-1"
     aria-labelledby="tab-1"&gt;...&lt;/div&gt;</code></pre></div>`,
            },
        ];
        const tabs = new tabs_1.Tabs(tabsWidget, tabItems);
        tabs.mount();
    }
    // ─── Combobox ──────────────────────────────────────────────────────────
    const comboboxWidget = document.getElementById('widget-combobox');
    if (comboboxWidget) {
        const countries = [
            'Argentina', 'Australia', 'Austria', 'Belgium', 'Brazil',
            'Canada', 'China', 'Colombia', 'Denmark', 'Egypt',
            'Finland', 'France', 'Germany', 'Greece', 'India',
            'Indonesia', 'Ireland', 'Italy', 'Japan', 'Kenya',
            'Mexico', 'Netherlands', 'New Zealand', 'Norway', 'Pakistan',
            'Poland', 'Portugal', 'Romania', 'Russia', 'Singapore',
            'South Africa', 'South Korea', 'Spain', 'Sweden', 'Switzerland',
            'Thailand', 'Turkey', 'United Kingdom', 'United States', 'Vietnam',
        ];
        const combobox = new combobox_1.Combobox(comboboxWidget, 'Search countries...', countries);
        combobox.mount();
    }
    // ─── Accordion ─────────────────────────────────────────────────────────
    const accordionWidget = document.getElementById('widget-accordion');
    if (accordionWidget) {
        const accordionItems = [
            {
                title: 'What is this component?',
                content: `<p>The accordion (also called a disclosure group) allows users to expand and collapse sections of content. Multiple sections can be open simultaneously.</p>`,
            },
            {
                title: 'How does it work?',
                content: `<p>Each section has a button with <code>aria-expanded</code> and <code>aria-controls</code> pointing to the associated region. The region uses <code>role="region"</code> and <code>aria-labelledby</code> referencing the button.</p>
          <p>Open/close animations use CSS transitions on the <code>height</code> property. The <code>hidden</code> attribute is toggled only after the animation completes.</p>`,
            },
            {
                title: 'Keyboard interaction',
                content: `<p>Press <kbd>Enter</kbd> or <kbd>Space</kbd> on any accordion button to toggle its section. There are no special arrow-key behaviors since multiple sections may be open — standard tab order applies.</p>`,
            },
            {
                title: 'Accessibility features',
                content: `<ul>
            <li>Proper <code>aria-expanded</code> state on each button</li>
            <li><code>aria-controls</code> links each button to its region</li>
            <li>Regions use <code>role="region"</code> with <code>aria-labelledby</code></li>
            <li>Animations respect <code>prefers-reduced-motion</code></li>
            <li>Content is never hidden during animation — <code>hidden</code> is set after transition ends</li>
          </ul>`,
            },
        ];
        const accordion = new accordion_1.Accordion(accordionWidget, accordionItems);
        accordion.mount();
    }
}
// Auto-run
start();
};

// ── module: src/constants.ts ──
__mods["src/constants.ts"] = function (exports, require, module) {
"use strict";
// ─── Tunable Constants ────────────────────────────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.SIDEBAR_WIDTH = exports.Z_INDEX = exports.BORDER_RADIUS = exports.FONT = exports.ANIMATION = exports.SPACING = exports.COLORS = void 0;
exports.COLORS = {
    bg: '#ffffff',
    bgSecondary: '#f8f9fb',
    bgTertiary: '#f1f3f7',
    text: '#1a1d23',
    textSecondary: '#5f6577',
    textTertiary: '#8b90a0',
    border: '#e2e5ec',
    borderFocus: '#3b5bdb',
    accent: '#3b5bdb',
    accentHover: '#3047c5',
    accentLight: '#e7ebf9',
    accentText: '#ffffff',
    danger: '#e03131',
    success: '#2b8a3e',
    successLight: '#edfae6',
    shadow: 'rgba(0, 0, 0, 0.12)',
    shadowHeavy: 'rgba(0, 0, 0, 0.25)',
    overlay: 'rgba(0, 0, 0, 0.45)',
    focusRing: '#3b5bdb',
    focusRingOffset: '2px',
};
exports.SPACING = {
    xs: '4px',
    sm: '8px',
    md: '16px',
    lg: '24px',
    xl: '32px',
    xxl: '48px',
    xxxl: '64px',
};
exports.ANIMATION = {
    duration: '200ms',
    durationSlow: '300ms',
    easing: 'ease-out',
    easingIn: 'ease-in',
};
exports.FONT = {
    family: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
    mono: "'JetBrains Mono', 'Fira Code', monospace",
    size: {
        xs: '12px',
        sm: '13px',
        md: '14px',
        base: '15px',
        lg: '18px',
        xl: '22px',
        xxl: '28px',
        xxxl: '36px',
    },
    weight: {
        normal: '400',
        medium: '500',
        semibold: '600',
        bold: '700',
    },
};
exports.BORDER_RADIUS = {
    sm: '4px',
    md: '6px',
    lg: '8px',
    xl: '12px',
};
exports.Z_INDEX = {
    sidebar: 100,
    dropdown: 200,
    modal: 1000,
    modalOverlay: 999,
    skipLink: 2000,
};
exports.SIDEBAR_WIDTH = '240px';
};

// ── module: src/helpers.ts ──
__mods["src/helpers.ts"] = function (exports, require, module) {
"use strict";
// ─── Shared Accessibility Helpers ─────────────────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.ActiveDescendantManager = void 0;
exports.createFocusTrap = createFocusTrap;
exports.debounce = debounce;
exports.prefersReducedMotion = prefersReducedMotion;
exports.setAria = setAria;
exports.genId = genId;
/**
 * Focus-trap utility: keeps Tab/Shift+Tab cycling within a container.
 * Returns a dispose function to remove the trap.
 */
function createFocusTrap(container) {
    const focusableSelector = [
        'a[href]:not([disabled]):not([tabindex="-1"])',
        'button:not([disabled]):not([tabindex="-1"])',
        'input:not([disabled]):not([tabindex="-1"])',
        'select:not([disabled]):not([tabindex="-1"])',
        'textarea:not([disabled]):not([tabindex="-1"])',
        '[tabindex]:not([tabindex="-1"])',
        '[contenteditable="true"]',
    ].join(', ');
    function getFocusableElements() {
        return Array.from(container.querySelectorAll(focusableSelector)).filter((el) => !el.hasAttribute('disabled') && !isHidden(el));
    }
    function isHidden(el) {
        return el.hidden || getComputedStyle(el).display === 'none' || getComputedStyle(el).visibility === 'hidden';
    }
    function handleKeydown(e) {
        if (e.key !== 'Tab')
            return;
        const focusable = getFocusableElements();
        if (focusable.length === 0) {
            e.preventDefault();
            return;
        }
        const first = focusable[0];
        const last = focusable[focusable.length - 1];
        if (e.shiftKey) {
            // Shift+Tab: if on first element, wrap to last
            if (document.activeElement === first || !container.contains(document.activeElement)) {
                e.preventDefault();
                last.focus();
            }
        }
        else {
            // Tab: if on last element, wrap to first
            if (document.activeElement === last || !container.contains(document.activeElement)) {
                e.preventDefault();
                first.focus();
            }
        }
    }
    container.addEventListener('keydown', handleKeydown);
    return () => container.removeEventListener('keydown', handleKeydown);
}
/**
 * Manager for aria-activedescendant pattern.
 * Tracks which descendant element is "active" (highlighted) without moving DOM focus.
 */
class ActiveDescendantManager {
    constructor(host, container) {
        this._activeId = null;
        this.host = host;
        this.container = container;
    }
    setActive(id) {
        this._activeId = id;
        this.host.setAttribute('aria-activedescendant', id ?? '');
        if (id) {
            const el = this.container.querySelector(`#${CSS.escape(id)}`);
            if (el) {
                el.setAttribute('aria-selected', 'true');
                el.scrollIntoView({ block: 'nearest' });
            }
        }
        // Clear previous selections
        this.container.querySelectorAll('[aria-selected]').forEach((el) => {
            if (el.id !== this._activeId) {
                el.setAttribute('aria-selected', 'false');
            }
        });
    }
    get activeId() {
        return this._activeId;
    }
    getItems() {
        return Array.from(this.container.querySelectorAll('[role="option"]'));
    }
    moveHighlight(direction) {
        const items = this.getItems();
        if (items.length === 0)
            return null;
        const currentIndex = this._activeId
            ? items.findIndex((el) => el.id === this._activeId)
            : direction === 'next'
                ? -1
                : items.length;
        let newIndex;
        if (direction === 'next') {
            newIndex = currentIndex + 1;
            if (newIndex >= items.length)
                newIndex = 0; // wrap
        }
        else {
            newIndex = currentIndex - 1;
            if (newIndex < 0)
                newIndex = items.length - 1; // wrap
        }
        return items[newIndex]?.id ?? null;
    }
    reset() {
        this.setActive(null);
    }
}
exports.ActiveDescendantManager = ActiveDescendantManager;
/**
 * Debounce function for filtering inputs.
 */
function debounce(fn, delay) {
    let timer;
    return ((...args) => {
        clearTimeout(timer);
        timer = setTimeout(() => fn(...args), delay);
    });
}
/**
 * Check if prefers-reduced-motion is set.
 */
function prefersReducedMotion() {
    return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
}
/**
 * Safely set an attribute; omit value removes it.
 */
function setAria(el, attr, value) {
    if (value === null || value === undefined) {
        el.removeAttribute(attr);
    }
    else {
        el.setAttribute(attr, typeof value === 'boolean' ? (value ? 'true' : 'false') : value);
    }
}
/**
 * Generate a unique ID prefix for this session.
 */
let idCounter = 0;
function genId(prefix = 'a11y') {
    return `${prefix}-${++idCounter}`;
}
};

// ── module: src/modal.ts ──
__mods["src/modal.ts"] = function (exports, require, module) {
"use strict";
// ─── Modal Dialog Component ───────────────────────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.ModalDialog = void 0;
const constants_1 = require("./constants");
const helpers_1 = require("./helpers");
class ModalDialog {
    constructor(container, options) {
        this.disposeFocusTrap = null;
        this.isOpen = false;
        this._widgetContainer = null;
        this.trigger = document.createElement('button');
        this.trigger.className = 'a11y-btn a11y-btn-primary';
        this.trigger.textContent = 'Open Modal Dialog';
        this.trigger.setAttribute('aria-haspopup', 'dialog');
        // Build overlay
        this.overlay = document.createElement('div');
        this.overlay.className = 'a11y-modal-overlay';
        this.overlay.setAttribute('aria-hidden', 'true');
        this.overlay.style.display = 'none';
        // Build dialog
        this.dialog = document.createElement('div');
        this.dialog.className = 'a11y-modal-dialog';
        this.dialog.setAttribute('role', 'dialog');
        this.dialog.setAttribute('aria-modal', 'true');
        const dialogId = (0, helpers_1.genId)('modal');
        const titleId = (0, helpers_1.genId)('modal-title');
        const contentId = (0, helpers_1.genId)('modal-content');
        this.dialog.id = dialogId;
        this.trigger.setAttribute('aria-controls', dialogId);
        // Title
        this.titleEl = document.createElement('h2');
        this.titleEl.id = titleId;
        this.titleEl.className = 'a11y-modal-title';
        this.titleEl.textContent = options.title;
        this.dialog.setAttribute('aria-labelledby', titleId);
        // Close button
        this.closeBtn = document.createElement('button');
        this.closeBtn.className = 'a11y-modal-close';
        this.closeBtn.setAttribute('aria-label', 'Close dialog');
        this.closeBtn.textContent = '×';
        // Content
        this.contentEl = document.createElement('div');
        this.contentEl.id = contentId;
        this.contentEl.className = 'a11y-modal-content';
        this.contentEl.textContent = options.content;
        // Assemble
        this.dialog.appendChild(this.titleEl);
        this.dialog.appendChild(this.closeBtn);
        this.dialog.appendChild(this.contentEl);
        this.overlay.appendChild(this.dialog);
        // Overlay is appended to body so it sits outside the inert content area
        document.body.appendChild(this.overlay);
        // Store reference to the widget container for inert management
        this._widgetContainer = container;
    }
    mount() {
        this.trigger.addEventListener('click', () => this.open());
        this.closeBtn.addEventListener('click', () => this.close());
        this.overlay.addEventListener('click', (e) => {
            if (e.target === this.overlay)
                this.close();
        });
        document.addEventListener('keydown', (e) => {
            if (e.key === 'Escape' && this.isOpen) {
                e.preventDefault();
                this.close();
            }
        });
    }
    open() {
        if (this.isOpen)
            return;
        this.isOpen = true;
        this.overlay.style.display = 'block';
        this.overlay.setAttribute('aria-hidden', 'false');
        this.dialog.setAttribute('aria-hidden', 'false');
        (0, helpers_1.setAria)(this.trigger, 'aria-expanded', true);
        // Inert the main content area (not body, so the overlay/dialog can receive focus)
        const mainContent = document.getElementById('main-content');
        if (mainContent)
            mainContent.setAttribute('inert', '');
        // Animate in
        if (!(0, helpers_1.prefersReducedMotion)()) {
            this.overlay.style.opacity = '0';
            this.overlay.style.transition = `opacity ${constants_1.ANIMATION.duration} ${constants_1.ANIMATION.easing}`;
            this.dialog.style.transform = 'scale(0.95) translateY(10px)';
            this.dialog.style.transition = `transform ${constants_1.ANIMATION.duration} ${constants_1.ANIMATION.easing}`;
            requestAnimationFrame(() => {
                this.overlay.style.opacity = '1';
                this.dialog.style.transform = 'scale(1) translateY(0)';
            });
        }
        // Focus trap
        this.disposeFocusTrap = (0, helpers_1.createFocusTrap)(this.dialog);
        // Move focus into dialog
        requestAnimationFrame(() => {
            this.closeBtn.focus();
        });
    }
    close() {
        if (!this.isOpen)
            return;
        this.isOpen = false;
        // Animate out
        if (!(0, helpers_1.prefersReducedMotion)()) {
            this.overlay.style.opacity = '0';
            this.dialog.style.transform = 'scale(0.95) translateY(10px)';
        }
        else {
            this.overlay.style.display = 'none';
        }
        const onTransitionEnd = () => {
            this.overlay.style.display = 'none';
            this.overlay.style.opacity = '';
            this.dialog.style.transform = '';
            this.overlay.removeEventListener('transitionend', onTransitionEnd);
        };
        if (!(0, helpers_1.prefersReducedMotion)()) {
            this.overlay.addEventListener('transitionend', onTransitionEnd, { once: true });
            // Fallback timeout
            setTimeout(onTransitionEnd, 300);
        }
        // Remove focus trap
        if (this.disposeFocusTrap) {
            this.disposeFocusTrap();
            this.disposeFocusTrap = null;
        }
        // Remove inert
        const mainContent = document.getElementById('main-content');
        if (mainContent)
            mainContent.removeAttribute('inert');
        (0, helpers_1.setAria)(this.trigger, 'aria-expanded', false);
        // Return focus to trigger
        this.trigger.focus();
    }
    getTrigger() {
        return this.trigger;
    }
}
exports.ModalDialog = ModalDialog;
};

// ── module: src/menu.ts ──
__mods["src/menu.ts"] = function (exports, require, module) {
"use strict";
// ─── Dropdown Menu Component ──────────────────────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.DropdownMenu = void 0;
const constants_1 = require("./constants");
const helpers_1 = require("./helpers");
class DropdownMenu {
    constructor(container, buttonLabel, items) {
        this.isOpen = false;
        this.items = items;
        // Button
        this.button = document.createElement('button');
        this.button.className = 'a11y-btn a11y-btn-primary a11y-menu-trigger';
        this.button.textContent = buttonLabel;
        this.button.setAttribute('aria-haspopup', 'menu');
        this.button.setAttribute('aria-expanded', 'false');
        // Menu
        const menuId = (0, helpers_1.genId)('menu');
        const statusId = (0, helpers_1.genId)('menu-status');
        this.button.setAttribute('aria-controls', menuId);
        this.menu = document.createElement('ul');
        this.menu.className = 'a11y-menu-list';
        this.menu.id = menuId;
        this.menu.setAttribute('role', 'menu');
        this.menu.style.display = 'none';
        // Status region for screen readers
        this.statusRegion = document.createElement('span');
        this.statusRegion.id = statusId;
        this.statusRegion.className = 'a11y-sr-only';
        this.statusRegion.setAttribute('aria-live', 'polite');
        this.statusRegion.setAttribute('aria-atomic', 'true');
        // Menu items
        items.forEach((item, index) => {
            const li = document.createElement('li');
            li.setAttribute('role', 'menuitem');
            li.className = 'a11y-menu-item';
            li.textContent = item.label;
            li.tabIndex = 0;
            li.id = `${menuId}-item-${index}`;
            li.addEventListener('click', () => {
                item.action();
                this.close();
            });
            li.addEventListener('keydown', (e) => {
                if (e.key === 'Enter' || e.key === ' ') {
                    e.preventDefault();
                    item.action();
                    this.close();
                }
            });
            this.menu.appendChild(li);
        });
        container.appendChild(this.button);
        container.appendChild(this.menu);
        container.appendChild(this.statusRegion);
    }
    mount() {
        this.button.addEventListener('click', () => this.toggle());
        this.button.addEventListener('keydown', (e) => {
            switch (e.key) {
                case 'Enter':
                case ' ':
                    e.preventDefault();
                    this.toggle();
                    break;
                case 'ArrowDown':
                    e.preventDefault();
                    this.open();
                    this.focusFirstItem();
                    break;
                case 'Escape':
                    if (this.isOpen) {
                        e.preventDefault();
                        this.close();
                    }
                    break;
            }
        });
        // Menu key handling
        this.menu.addEventListener('keydown', (e) => {
            const menuItems = Array.from(this.menu.querySelectorAll('[role="menuitem"]'));
            const currentIndex = menuItems.indexOf(document.activeElement);
            switch (e.key) {
                case 'ArrowDown':
                    e.preventDefault();
                    if (currentIndex < menuItems.length - 1) {
                        menuItems[currentIndex + 1].focus();
                    }
                    else {
                        menuItems[0].focus(); // wrap
                    }
                    break;
                case 'ArrowUp':
                    e.preventDefault();
                    if (currentIndex > 0) {
                        menuItems[currentIndex - 1].focus();
                    }
                    else {
                        menuItems[menuItems.length - 1].focus(); // wrap
                    }
                    break;
                case 'Home':
                    e.preventDefault();
                    menuItems[0]?.focus();
                    break;
                case 'End':
                    e.preventDefault();
                    menuItems[menuItems.length - 1]?.focus();
                    break;
                case 'Escape':
                    e.preventDefault();
                    this.close();
                    break;
                case 'Tab':
                    this.close();
                    // Allow natural tab behavior
                    break;
            }
        });
        // Close on outside click
        document.addEventListener('click', (e) => {
            if (this.isOpen && !this.button.contains(e.target) && !this.menu.contains(e.target)) {
                this.close();
            }
        });
    }
    open() {
        if (this.isOpen)
            return;
        this.isOpen = true;
        this.menu.style.display = 'block';
        (0, helpers_1.setAria)(this.button, 'aria-expanded', true);
        if (!(0, helpers_1.prefersReducedMotion)()) {
            this.menu.style.opacity = '0';
            this.menu.style.transform = 'translateY(-4px)';
            this.menu.style.transition = `opacity ${constants_1.ANIMATION.duration} ${constants_1.ANIMATION.easing}, transform ${constants_1.ANIMATION.duration} ${constants_1.ANIMATION.easing}`;
            requestAnimationFrame(() => {
                this.menu.style.opacity = '1';
                this.menu.style.transform = 'translateY(0)';
            });
        }
        this.statusRegion.textContent = `${this.items.length} items in menu`;
    }
    close() {
        if (!this.isOpen)
            return;
        this.isOpen = false;
        this.menu.style.display = 'none';
        this.menu.style.opacity = '';
        this.menu.style.transform = '';
        (0, helpers_1.setAria)(this.button, 'aria-expanded', false);
        this.button.focus();
    }
    toggle() {
        if (this.isOpen) {
            this.close();
        }
        else {
            this.open();
        }
    }
    focusFirstItem() {
        const firstItem = this.menu.querySelector('[role="menuitem"]');
        if (firstItem)
            firstItem.focus();
    }
}
exports.DropdownMenu = DropdownMenu;
};

// ── module: src/tabs.ts ──
__mods["src/tabs.ts"] = function (exports, require, module) {
"use strict";
// ─── Tabs Component ───────────────────────────────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.Tabs = void 0;
const helpers_1 = require("./helpers");
class Tabs {
    constructor(container, tabItems) {
        this.tabs = [];
        this.panels = [];
        this.activeIndex = 0;
        const tablistId = (0, helpers_1.genId)('tablist');
        // Tablist
        this.tablist = document.createElement('div');
        this.tablist.className = 'a11y-tablist';
        this.tablist.setAttribute('role', 'tablist');
        this.tablist.id = tablistId;
        tabItems.forEach((item, index) => {
            const tabId = (0, helpers_1.genId)('tab');
            const panelId = (0, helpers_1.genId)('panel');
            // Tab button
            const tab = document.createElement('button');
            tab.className = 'a11y-tab';
            tab.setAttribute('role', 'tab');
            tab.id = tabId;
            tab.textContent = item.label;
            tab.setAttribute('aria-controls', panelId);
            tab.setAttribute('aria-selected', index === 0 ? 'true' : 'false');
            tab.tabIndex = index === 0 ? 0 : -1;
            tab.addEventListener('click', () => this.activateTab(index));
            this.tabs.push(tab);
            this.tablist.appendChild(tab);
            // Panel
            const panel = document.createElement('div');
            panel.className = 'a11y-tab-panel';
            panel.setAttribute('role', 'tabpanel');
            panel.id = panelId;
            panel.setAttribute('aria-labelledby', tabId);
            if (index !== 0) {
                panel.hidden = true;
            }
            if (typeof item.content === 'string') {
                panel.innerHTML = item.content;
            }
            else {
                panel.appendChild(item.content);
            }
            this.panels.push(panel);
        });
        container.appendChild(this.tablist);
        this.panels.forEach((panel) => container.appendChild(panel));
    }
    mount() {
        this.tablist.addEventListener('keydown', (e) => {
            const target = e.target;
            if (!target.hasAttribute('role') || target.getAttribute('role') !== 'tab')
                return;
            const currentIndex = this.tabs.indexOf(target);
            if (currentIndex === -1)
                return;
            let newIndex = currentIndex;
            switch (e.key) {
                case 'ArrowRight':
                    e.preventDefault();
                    newIndex = (currentIndex + 1) % this.tabs.length;
                    break;
                case 'ArrowLeft':
                    e.preventDefault();
                    newIndex = (currentIndex - 1 + this.tabs.length) % this.tabs.length;
                    break;
                case 'Home':
                    e.preventDefault();
                    newIndex = 0;
                    break;
                case 'End':
                    e.preventDefault();
                    newIndex = this.tabs.length - 1;
                    break;
                default:
                    return;
            }
            this.activateTab(newIndex);
        });
    }
    activateTab(index) {
        if (index === this.activeIndex)
            return;
        // Deactivate current
        this.tabs[this.activeIndex].setAttribute('aria-selected', 'false');
        this.tabs[this.activeIndex].tabIndex = -1;
        this.panels[this.activeIndex].hidden = true;
        // Activate new
        this.activeIndex = index;
        this.tabs[index].setAttribute('aria-selected', 'true');
        this.tabs[index].tabIndex = 0;
        this.tabs[index].focus();
        this.panels[index].hidden = false;
    }
}
exports.Tabs = Tabs;
};

// ── module: src/combobox.ts ──
__mods["src/combobox.ts"] = function (exports, require, module) {
"use strict";
// ─── Combobox (Autocomplete) Component ────────────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.Combobox = void 0;
const constants_1 = require("./constants");
const helpers_1 = require("./helpers");
class Combobox {
    constructor(container, placeholder, options) {
        this.isOpen = false;
        this.lastValue = '';
        this.options = options;
        this.filteredOptions = [...options];
        const inputId = (0, helpers_1.genId)('combobox-input');
        const listboxId = (0, helpers_1.genId)('combobox-listbox');
        // Input
        this.input = document.createElement('input');
        this.input.type = 'text';
        this.input.className = 'a11y-combobox-input';
        this.input.id = inputId;
        this.input.setAttribute('role', 'combobox');
        this.input.setAttribute('aria-autocomplete', 'list');
        this.input.setAttribute('aria-controls', listboxId);
        this.input.setAttribute('aria-expanded', 'false');
        this.input.setAttribute('aria-haspopup', 'listbox');
        this.input.setAttribute('aria-activedescendant', '');
        this.input.setAttribute('autocomplete', 'off');
        this.input.placeholder = placeholder;
        // Listbox
        this.listbox = document.createElement('ul');
        this.listbox.className = 'a11y-combobox-listbox';
        this.listbox.id = listboxId;
        this.listbox.setAttribute('role', 'listbox');
        this.listbox.setAttribute('aria-label', 'Suggestions');
        this.listbox.style.display = 'none';
        // Manager for aria-activedescendant
        this.manager = new helpers_1.ActiveDescendantManager(this.input, this.listbox);
        container.appendChild(this.input);
        container.appendChild(this.listbox);
    }
    mount() {
        // Filter on input
        this.input.addEventListener('input', () => {
            const value = this.input.value;
            this.lastValue = value;
            this.filterAndRender(value);
        });
        // Keyboard handling
        this.input.addEventListener('keydown', (e) => {
            switch (e.key) {
                case 'ArrowDown':
                    e.preventDefault();
                    if (!this.isOpen) {
                        this.open();
                    }
                    this.manager.setActive(this.manager.moveHighlight('next'));
                    break;
                case 'ArrowUp':
                    e.preventDefault();
                    this.manager.setActive(this.manager.moveHighlight('prev'));
                    break;
                case 'Enter':
                    e.preventDefault();
                    if (this.isOpen && this.manager.activeId) {
                        this.selectActive();
                    }
                    break;
                case 'Escape':
                    e.preventDefault();
                    this.close();
                    this.input.value = this.lastValue;
                    this.filterAndRender(this.lastValue);
                    break;
                case 'Tab':
                    this.close();
                    break;
            }
        });
        // Close on focus out
        this.input.addEventListener('blur', () => {
            // Small delay to allow click on option to register
            setTimeout(() => {
                if (this.isOpen) {
                    this.close();
                }
            }, 100);
        });
        // Click on options
        this.listbox.addEventListener('click', (e) => {
            const option = e.target.closest('[role="option"]');
            if (option) {
                this.selectOption(option.textContent ?? '');
            }
        });
    }
    filterAndRender(query) {
        const lower = query.toLowerCase();
        this.filteredOptions = this.options.filter((opt) => opt.toLowerCase().includes(lower));
        this.renderOptions();
        if (this.filteredOptions.length > 0 && query.length > 0) {
            if (!this.isOpen)
                this.open();
        }
        else if (this.filteredOptions.length === 0) {
            this.close();
        }
    }
    renderOptions() {
        this.listbox.innerHTML = '';
        this.manager.reset();
        this.filteredOptions.forEach((opt) => {
            const li = document.createElement('li');
            li.setAttribute('role', 'option');
            li.className = 'a11y-combobox-option';
            li.id = (0, helpers_1.genId)('combobox-option');
            li.textContent = opt;
            li.setAttribute('aria-selected', 'false');
            li.addEventListener('mouseenter', () => {
                this.manager.setActive(li.id);
            });
            this.listbox.appendChild(li);
        });
    }
    open() {
        if (this.isOpen)
            return;
        this.isOpen = true;
        this.listbox.style.display = 'block';
        (0, helpers_1.setAria)(this.input, 'aria-expanded', true);
        if (!(0, helpers_1.prefersReducedMotion)()) {
            this.listbox.style.opacity = '0';
            this.listbox.style.transform = 'translateY(-4px)';
            this.listbox.style.transition = `opacity ${constants_1.ANIMATION.duration} ${constants_1.ANIMATION.easing}, transform ${constants_1.ANIMATION.duration} ${constants_1.ANIMATION.easing}`;
            requestAnimationFrame(() => {
                this.listbox.style.opacity = '1';
                this.listbox.style.transform = 'translateY(0)';
            });
        }
    }
    close() {
        if (!this.isOpen)
            return;
        this.isOpen = false;
        this.listbox.style.display = 'none';
        this.listbox.style.opacity = '';
        this.listbox.style.transform = '';
        (0, helpers_1.setAria)(this.input, 'aria-expanded', false);
        this.manager.reset();
    }
    selectActive() {
        const activeId = this.manager.activeId;
        if (!activeId)
            return;
        const option = this.listbox.querySelector(`#${CSS.escape(activeId)}`);
        if (option) {
            this.selectOption(option.textContent ?? '');
        }
    }
    selectOption(value) {
        this.input.value = value;
        this.lastValue = value;
        this.close();
        this.input.focus();
    }
}
exports.Combobox = Combobox;
};

// ── module: src/accordion.ts ──
__mods["src/accordion.ts"] = function (exports, require, module) {
"use strict";
// ─── Accordion / Disclosure Group Component ───────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.Accordion = void 0;
const constants_1 = require("./constants");
const helpers_1 = require("./helpers");
class Accordion {
    constructor(container, items) {
        this.items = [];
        items.forEach((item, index) => {
            const buttonId = (0, helpers_1.genId)('accordion-btn');
            const regionId = (0, helpers_1.genId)('accordion-region');
            // Button
            const button = document.createElement('button');
            button.className = 'a11y-accordion-button';
            button.id = buttonId;
            button.setAttribute('aria-expanded', 'false');
            button.setAttribute('aria-controls', regionId);
            button.setAttribute('aria-label', `${item.title}${index === 0 ? '' : ' (collapsed)'}`);
            const icon = document.createElement('span');
            icon.className = 'a11y-accordion-icon';
            icon.setAttribute('aria-hidden', 'true');
            icon.textContent = '▸';
            const titleSpan = document.createElement('span');
            titleSpan.className = 'a11y-accordion-title';
            titleSpan.textContent = item.title;
            button.appendChild(titleSpan);
            button.appendChild(icon);
            // Region
            const region = document.createElement('div');
            region.className = 'a11y-accordion-region';
            region.id = regionId;
            region.setAttribute('role', 'region');
            region.setAttribute('aria-labelledby', buttonId);
            region.hidden = true;
            if (typeof item.content === 'string') {
                region.innerHTML = item.content;
            }
            else {
                region.appendChild(item.content);
            }
            // Click handler
            button.addEventListener('click', () => this.toggle(index));
            // Keyboard handler
            button.addEventListener('keydown', (e) => {
                if (e.key === 'Enter' || e.key === ' ') {
                    e.preventDefault();
                    this.toggle(index);
                }
            });
            this.items.push({ button, region, isOpen: false });
            container.appendChild(button);
            container.appendChild(region);
        });
    }
    mount() {
        // Nothing extra needed for accordion — click and keyboard are handled in constructor
    }
    toggle(index) {
        const { button, region, isOpen } = this.items[index];
        const newState = !isOpen;
        this.items[index].isOpen = newState;
        (0, helpers_1.setAria)(button, 'aria-expanded', newState);
        if (newState) {
            this.openRegion(region);
        }
        else {
            this.closeRegion(region);
        }
    }
    openRegion(region) {
        region.hidden = false;
        if ((0, helpers_1.prefersReducedMotion)()) {
            region.style.height = 'auto';
            return;
        }
        // Animate height
        region.style.height = '0';
        region.style.overflow = 'hidden';
        region.style.transition = `height ${constants_1.ANIMATION.durationSlow} ${constants_1.ANIMATION.easing}`;
        // Force reflow
        region.offsetHeight;
        const targetHeight = region.scrollHeight;
        region.style.height = `${targetHeight}px`;
        // After animation, set to auto
        region.addEventListener('transitionend', () => {
            region.style.height = 'auto';
            region.style.overflow = '';
            region.style.transition = '';
        }, { once: true });
        // Fallback
        setTimeout(() => {
            region.style.height = 'auto';
            region.style.overflow = '';
            region.style.transition = '';
        }, 350);
    }
    closeRegion(region) {
        if ((0, helpers_1.prefersReducedMotion)()) {
            region.hidden = true;
            return;
        }
        // Animate height to 0
        region.style.height = `${region.scrollHeight}px`;
        region.style.overflow = 'hidden';
        region.style.transition = `height ${constants_1.ANIMATION.durationSlow} ${constants_1.ANIMATION.easingIn}`;
        // Force reflow
        region.offsetHeight;
        region.style.height = '0';
        // After animation, hide
        region.addEventListener('transitionend', () => {
            region.hidden = true;
            region.style.height = '';
            region.style.overflow = '';
            region.style.transition = '';
        }, { once: true });
        // Fallback
        setTimeout(() => {
            region.hidden = true;
            region.style.height = '';
            region.style.overflow = '';
            region.style.transition = '';
        }, 350);
    }
}
exports.Accordion = Accordion;
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen/qwen3.6-27b","provider":"lmstudio","persona":"typescript","sessionId":"d5fea801-c8b9-4d9e-a270-3a1bc7bc6ea2","tokensIn":2556500,"tokensOut":40412,"tokensTotal":2596912,"cost":0,"turns":54,"toolCalls":53,"failedToolCalls":0,"timestamp":"2026-08-01T16:11:03.778Z"} -->