← Accessible Components results

Accessible Components

thinkingcap qwen3.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 40,914 bytes · SHA-256 6a33c8009e6e
<!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;
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
  color: #1a1a2e;
  background: #ffffff;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

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

/* ===== Skip Link ===== */
.skip-link {
  position: absolute;
  top: -100%;
  left: 50%;
  transform: translateX(-50%);
  background: #4361ee;
  color: #fff;
  padding: 12px 24px;
  border-radius: 0 0 8px 8px;
  font-weight: 600;
  z-index: 10000;
  text-decoration: none;
  transition: top 0.2s ease;
}

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

/* ===== Focus Ring (global) ===== */
*:focus-visible {
  outline: 3px solid #4361ee;
  outline-offset: 2px;
}

button:focus-visible,
input:focus-visible,
a:focus-visible,
[tabindex]:focus-visible {
  outline: 3px solid #4361ee;
  outline-offset: 2px;
}

/* ===== Layout ===== */
#layout {
  display: flex;
  min-height: 100vh;
}

/* ===== Sidebar ===== */
#sidebar {
  position: sticky;
  top: 0;
  width: 240px;
  height: 100vh;
  overflow-y: auto;
  flex-shrink: 0;
  background: #f8f9fa;
  border-right: 1px solid #dde1e7;
  padding: 32px 24px;
}

.sidebar-title {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: #555b6e;
  margin-bottom: 16px;
  font-weight: 600;
}

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

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

.sidebar-nav a {
  display: block;
  padding: 8px 12px;
  color: #555b6e;
  text-decoration: none;
  font-size: 0.9rem;
  border-radius: 6px;
  transition: background 0.15s ease, color 0.15s ease;
}

.sidebar-nav a:hover {
  background: #eef1f5;
  color: #1a1a2e;
}

.sidebar-nav a:focus-visible {
  outline: 3px solid #4361ee;
  outline-offset: -1px;
}

/* ===== Main Content ===== */
.main-content {
  flex: 1;
  max-width: 800px;
  padding: 48px 56px;
}

.page-header {
  margin-bottom: 48px;
  padding-bottom: 32px;
  border-bottom: 1px solid #dde1e7;
}

.page-header h1 {
  font-size: 2rem;
  font-weight: 700;
  color: #1a1a2e;
  margin-bottom: 8px;
  letter-spacing: -0.02em;
}

.subtitle {
  font-size: 1.05rem;
  color: #555b6e;
  max-width: 560px;
}

/* ===== Component Sections ===== */
.component-section {
  margin-bottom: 56px;
  padding-top: 8px;
  scroll-margin-top: 24px;
}

.component-section h2 {
  font-size: 1.35rem;
  font-weight: 700;
  color: #1a1a2e;
  margin-bottom: 20px;
  letter-spacing: -0.01em;
}

.demo-area {
  background: #f8f9fa;
  border: 1px solid #dde1e7;
  border-radius: 8px;
  padding: 32px;
  margin-bottom: 12px;
}

.keyboard-hint {
  font-size: 0.8rem;
  color: #555b6e;
  font-family: 'SF Mono', 'Fira Code', monospace;
  background: #eef1f5;
  display: inline-block;
  padding: 4px 12px;
  border-radius: 4px;
}

/* ===== Buttons ===== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 10px 20px;
  font-size: 0.9rem;
  font-weight: 600;
  border-radius: 6px;
  cursor: pointer;
  border: 1px solid transparent;
  transition: background 0.15s ease, box-shadow 0.15s ease;
  line-height: 1.4;
}

.btn-primary {
  background: #4361ee;
  color: #fff;
  border-color: #4361ee;
}

.btn-primary:hover {
  background: #3451d1;
  border-color: #3451d1;
}

.btn-secondary {
  background: #ffffff;
  color: #1a1a2e;
  border-color: #dde1e7;
}

.btn-secondary:hover {
  background: #f8f9fa;
}

/* ===== Modal ===== */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  z-index: 1000;
  animation: fadeIn 0.2s ease forwards;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

.modal-dialog {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: #ffffff;
  border-radius: 12px;
  padding: 32px;
  max-width: 480px;
  width: calc(100% - 32px);
  z-index: 1001;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2), 0 4px 16px rgba(0, 0, 0, 0.1);
  animation: modalSlideIn 0.2s ease forwards;
}

@keyframes modalSlideIn {
  from { opacity: 0; transform: translate(-50%, -48%); }
  to { opacity: 1; transform: translate(-50%, -50%); }
}

.modal-dialog h3 {
  font-size: 1.2rem;
  margin-bottom: 12px;
  color: #1a1a2e;
}

.modal-dialog p {
  color: #555b6e;
  margin-bottom: 24px;
  line-height: 1.6;
}

.modal-actions {
  display: flex;
  gap: 12px;
  justify-content: flex-end;
}

/* ===== Dropdown Menu ===== */
.dropdown-wrapper {
  position: relative;
  display: inline-block;
}

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

.dropdown-menu button[role="menuitem"] {
  display: block;
  width: 100%;
  text-align: left;
  padding: 8px 12px;
  font-size: 0.9rem;
  color: #1a1a2e;
  background: none;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.1s ease;
}

.dropdown-menu button[role="menuitem"]:hover,
.dropdown-menu button[role="menuitem"]:focus-visible {
  background: #e8ecff;
  color: #4361ee;
}

.menu-divider {
  height: 1px;
  background: #dde1e7;
  margin: 4px 0;
}

/* ===== Tabs ===== */
.tabs-container {
  width: 100%;
}

[role="tablist"] {
  display: flex;
  border-bottom: 2px solid #dde1e7;
  gap: 0;
  margin-bottom: 0;
}

[role="tab"] {
  padding: 12px 24px;
  font-size: 0.9rem;
  font-weight: 600;
  color: #555b6e;
  background: none;
  border: none;
  cursor: pointer;
  position: relative;
  transition: color 0.15s ease;
}

[role="tab"]:hover {
  color: #1a1a2e;
}

[role="tab"].is-active,
[role="tab"][aria-selected="true"] {
  color: #4361ee;
}

[role="tab"].is-active::after,
[role="tab"][aria-selected="true"]::after {
  content: '';
  position: absolute;
  bottom: -2px;
  left: 0;
  right: 0;
  height: 2px;
  background: #4361ee;
  border-radius: 2px 2px 0 0;
}

.tab-panel {
  padding: 24px 0 8px;
}

.tab-panel h3 {
  font-size: 1.1rem;
  margin-bottom: 12px;
  color: #1a1a2e;
}

.tab-panel p, .tab-panel ul {
  color: #555b6e;
  line-height: 1.7;
}

.tab-panel ul {
  padding-left: 20px;
  margin-top: 8px;
}

.spec-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}

.spec-table td {
  padding: 8px 12px;
  border-bottom: 1px solid #dde1e7;
  color: #555b6e;
}

.spec-table td:first-child {
  font-weight: 600;
  color: #1a1a2e;
  width: 40%;
}

.review {
  padding: 12px 0;
  border-bottom: 1px solid #dde1e7;
}

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

.review strong {
  color: #1a1a2e;
  font-size: 0.95rem;
}

.review p {
  margin-top: 4px;
  font-size: 0.9rem;
}

/* ===== Combobox ===== */
.combobox-label {
  display: block;
  font-weight: 600;
  font-size: 0.9rem;
  color: #1a1a2e;
  margin-bottom: 8px;
}

.combobox-wrapper {
  position: relative;
  max-width: 360px;
}

#combobox-input {
  width: 100%;
  padding: 10px 14px;
  font-size: 0.9rem;
  border: 1px solid #dde1e7;
  border-radius: 6px;
  background: #ffffff;
  color: #1a1a2e;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

#combobox-input:focus {
  border-color: #4361ee;
  box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.15);
}

.combobox-listbox {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  max-height: 220px;
  overflow-y: auto;
  background: #ffffff;
  border: 1px solid #dde1e7;
  border-radius: 8px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
  list-style: none;
  padding: 6px;
  z-index: 100;
}

.combobox-listbox [role="option"] {
  padding: 8px 12px;
  font-size: 0.9rem;
  color: #1a1a2e;
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.1s ease;
}

.combobox-listbox [role="option"]:hover {
  background: #f8f9fa;
}

.combobox-listbox [role="option"][aria-selected="true"] {
  background: #e8ecff;
  color: #4361ee;
  font-weight: 500;
}

/* ===== Accordion ===== */
.accordion-group {
  border: 1px solid #dde1e7;
  border-radius: 8px;
  overflow: hidden;
}

.accordion-trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 16px 20px;
  font-size: 0.95rem;
  font-weight: 600;
  color: #1a1a2e;
  background: none;
  border: none;
  cursor: pointer;
  text-align: left;
  transition: background 0.15s ease;
  position: relative;
}

.accordion-trigger:not(:last-of-type) {
  border-bottom: 1px solid #dde1e7;
}

.accordion-trigger:hover {
  background: #f8f9fa;
}

/* Chevron indicator */
.accordion-trigger::after {
  content: '';
  flex-shrink: 0;
  width: 8px;
  height: 8px;
  border-right: 2px solid #555b6e;
  border-bottom: 2px solid #555b6e;
  transform: rotate(45deg);
  transition: transform 0.2s ease;
  margin-left: 16px;
}

.accordion-trigger[aria-expanded="true"]::after {
  transform: rotate(-135deg);
}

.accordion-panel {
  padding: 0 20px;
  overflow: hidden;
}

.accordion-panel p {
  padding: 16px 0;
  color: #555b6e;
  line-height: 1.7;
  font-size: 0.9rem;
}

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

  .modal-dialog {
    animation: none;
  }

  .modal-backdrop {
    animation: none;
  }
}

/* ===== Responsive ===== */
@media (max-width: 768px) {
  #sidebar {
    display: none;
  }

  .main-content {
    padding: 32px 24px;
  }

  .page-header h1 {
    font-size: 1.5rem;
  }
}

/* ===== High contrast / zoom support ===== */
@media (min-resolution: 192dpi) {
  /* Ensure borders remain visible at high DPI */
  .component-section,
  .demo-area,
  .dropdown-menu,
  .combobox-listbox,
  .accordion-group {
    border-width: 1.5px;
  }
}
</style>
</head>
<body>
<a class="skip-link" href="#main-content">Skip to main content</a>
<div id="layout">
  <nav id="sidebar" aria-label="Component navigation">
    <h2 class="sidebar-title">Components</h2>
    <ul class="sidebar-nav">
      <li><a href="#section-modal">Modal Dialog</a></li>
      <li><a href="#section-dropdown">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>
  <main id="main-content" class="main-content">
    <header class="page-header">
      <h1>Accessible Component Showcase</h1>
      <p class="subtitle">A fully keyboard- and screen-reader-accessible component library. Every widget follows WAI-ARIA Authoring Practices.</p>
    </header>

    <!-- Modal Dialog Section -->
    <section id="section-modal" class="component-section">
      <h2>Modal Dialog</h2>
      <div class="demo-area">
        <button id="modal-trigger" class="btn btn-primary" aria-haspopup="dialog" aria-expanded="false">Open Modal</button>
        <div id="modal-backdrop" class="modal-backdrop" hidden=""></div>
        <div id="modal-dialog" class="modal-dialog" role="dialog" aria-modal="true" aria-labelledby="modal-title" hidden="">
          <h3 id="modal-title">Dialog Title</h3>
          <p>This is a modal dialog. Focus is trapped inside while open. Press Escape or click the backdrop to close.</p>
          <div class="modal-actions">
            <button class="btn btn-secondary" tabindex="0">Cancel</button>
            <button id="modal-close" class="btn btn-primary" tabindex="0">Close Dialog</button>
          </div>
        </div>
      </div>
      <p class="keyboard-hint">↑↓ navigate · Enter/Space activate · Esc close · Tab cycles within dialog</p>
    </section>

    <!-- Dropdown Menu Section -->
    <section id="section-dropdown" class="component-section">
      <h2>Dropdown Menu</h2>
      <div class="demo-area">
        <div class="dropdown-wrapper">
          <button id="dropdown-trigger" class="btn btn-primary" aria-haspopup="menu" aria-expanded="false" aria-controls="dropdown-menu">Actions ▾</button>
          <ul id="dropdown-menu" class="dropdown-menu" role="menu" hidden="">
            <li><button role="menuitem" data-action="Copy selected item">Copy</button></li>
            <li><button role="menuitem" data-action="Edit selected item">Edit</button></li>
            <li><button role="menuitem" data-action="Delete selected item">Delete</button></li>
            <li class="menu-divider"></li>
            <li><button role="menuitem" data-action="Share with team">Share…</button></li>
          </ul>
        </div>
      </div>
      <p class="keyboard-hint">↑↓ navigate · Home/End jump · Enter/Space select · Esc close</p>
    </section>

    <!-- Tabs Section -->
    <section id="section-tabs" class="component-section">
      <h2>Tabs</h2>
      <div class="demo-area">
        <div class="tabs-container">
          <div id="tablist" role="tablist" aria-label="Sample tabs">
  <button id="tab-overview" role="tab" aria-selected="true" aria-controls="panel-overview" tabindex="0" class="is-active">Overview</button>
  <button id="tab-specs" role="tab" aria-selected="false" aria-controls="panel-specs" tabindex="-1">Specifications</button>
  <button id="tab-reviews" role="tab" aria-selected="false" aria-controls="panel-reviews" tabindex="-1">Reviews</button>
</div>
          <div id="panel-overview" role="tabpanel" aria-labelledby="tab-overview" class="tab-panel">
            <h3>Product Overview</h3>
            <p>This is a high-quality product designed with accessibility in mind. Every interaction has been carefully crafted to work with keyboard navigation and screen readers.</p>
            <ul>
              <li>Fully accessible out of the box</li>
              <li>No external dependencies required</li>
              <li>Built with semantic HTML and ARIA</li>
            </ul>
          </div>
          <div id="panel-specs" role="tabpanel" aria-labelledby="tab-specs" class="tab-panel" hidden="">
            <h3>Technical Specifications</h3>
            <table class="spec-table">
              <tbody>
                <tr><td>Framework</td><td>Vanilla JavaScript</td></tr>
                <tr><td>CSS</td><td>Hand-written, no framework</td></tr>
                <tr><td>A11y Standard</td><td>WAI-ARIA 1.2</td></tr>
                <tr><td>Keyboard Support</td><td>Full navigation</td></tr>
              </tbody>
            </table>
          </div>
          <div id="panel-reviews" role="tabpanel" aria-labelledby="tab-reviews" class="tab-panel" hidden="">
            <h3>User Reviews</h3>
            <div class="review">
              <strong>"Excellent accessibility"</strong>
              <p>I can navigate the entire page with just my keyboard. The focus management is flawless.</p>
            </div>
            <div class="review">
              <strong>"Clean and polished"</strong>
              <p>The design is refined and professional. Great attention to detail in every component.</p>
            </div>
          </div>
        </div>
      </div>
      <p class="keyboard-hint">←→ switch tabs · Home/End jump · Tab enters panel content</p>
    </section>

    <!-- Combobox Section -->
    <section id="section-combobox" class="component-section">
      <h2>Combobox (Autocomplete)</h2>
      <div class="demo-area">
        <label for="combobox-input" class="combobox-label">Select a country</label>
        <div class="combobox-wrapper">
          <input id="combobox-input" type="text" role="combobox" aria-expanded="false" aria-controls="combobox-listbox" aria-autocomplete="list" autocomplete="off" placeholder="Start typing…">
          <ul id="combobox-listbox" class="combobox-listbox" role="listbox" hidden=""></ul>
        </div>
      </div>
      <p class="keyboard-hint">↑↓ highlight · Enter select · Esc close &amp; revert · Tab moves on</p>
    </section>

    <!-- Accordion Section -->
    <section id="section-accordion" class="component-section">
      <h2>Accordion / Disclosure Group</h2>
      <div class="demo-area">
        <div id="accordion-group" class="accordion-group">
          <button class="accordion-trigger" aria-expanded="false" aria-controls="acc-panel-1">What is this component library?</button>
          <div id="acc-panel-1" class="accordion-panel" hidden="">
            <p>This is a fully accessible component showcase built with vanilla JavaScript. Every widget follows WAI-ARIA Authoring Practices and is operable entirely by keyboard.</p>
          </div>

          <button class="accordion-trigger" aria-expanded="false" aria-controls="acc-panel-2">How does focus management work?</button>
          <div id="acc-panel-2" class="accordion-panel" hidden="">
            <p>Focus is managed using a reusable FocusTrap utility for modals, roving tabindex for tabs, and aria-activedescendant for the combobox. Each pattern follows W3C recommendations.</p>
          </div>

          <button class="accordion-trigger" aria-expanded="false" aria-controls="acc-panel-3">Can multiple panels be open at once?</button>
          <div id="acc-panel-3" class="accordion-panel" hidden="">
            <p>Yes! This accordion allows multiple panels to remain open simultaneously. Each button independently toggles its associated panel with smooth animations that respect prefers-reduced-motion.</p>
          </div>

          <button class="accordion-trigger" aria-expanded="false" aria-controls="acc-panel-4">Is this production-ready?</button>
          <div id="acc-panel-4" class="accordion-panel" hidden="">
            <p>The components demonstrate correct ARIA patterns, keyboard navigation, and focus management. They serve as a reference implementation for building accessible UI in any project.</p>
          </div>
        </div>
      </div>
      <p class="keyboard-hint">Enter/Space toggle · Multiple panels may be open</p>
    </section>

    <!-- Live regions (visually hidden, used by screen readers) -->
    <div id="dropdown-announcer" role="status" aria-live="polite" class="sr-only"></div>
    <div id="combobox-announcer" role="status" aria-live="polite" class="sr-only"></div>

  </main>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./modal":"src/modal.ts","./dropdown":"src/dropdown.ts","./tabs":"src/tabs.ts","./combobox":"src/combobox.ts","./accordion":"src/accordion.ts"},"src/utils.ts":{"./constants":"src/constants.ts"},"src/modal.ts":{"./constants":"src/constants.ts","./utils":"src/utils.ts"},"src/accordion.ts":{"./constants":"src/constants.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";
Object.defineProperty(exports, "__esModule", { value: true });
const modal_1 = require("./modal");
const dropdown_1 = require("./dropdown");
const tabs_1 = require("./tabs");
const combobox_1 = require("./combobox");
const accordion_1 = require("./accordion");
// Initialize all components once the DOM is ready
(0, modal_1.initModal)();
(0, dropdown_1.initDropdown)();
(0, tabs_1.initTabs)();
(0, combobox_1.initCombobox)();
(0, accordion_1.initAccordion)();
};

// ── module: src/constants.ts ──
__mods["src/constants.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SIDEBAR_WIDTH = exports.BORDER_RADIUS = exports.FOCUS_RING_WIDTH = exports.ANIMATION = exports.SPACING = exports.COLORS = void 0;
// Design tokens and tunable constants
exports.COLORS = {
    bg: '#ffffff',
    bgSecondary: '#f8f9fa',
    bgTertiary: '#eef1f5',
    text: '#1a1a2e',
    textSecondary: '#555b6e',
    accent: '#4361ee',
    accentLight: '#e8ecff',
    accentDark: '#3451d1',
    border: '#dde1e7',
    focusRing: '#4361ee',
    success: '#2dc653',
    white: '#ffffff',
    overlay: 'rgba(0, 0, 0, 0.45)',
};
exports.SPACING = {
    xs: '4px',
    sm: '8px',
    md: '16px',
    lg: '24px',
    xl: '32px',
    xxl: '48px',
};
exports.ANIMATION = {
    duration: 200, // ms for most transitions
    modalFade: 250,
    accordionSlide: 300,
};
exports.FOCUS_RING_WIDTH = 3;
exports.BORDER_RADIUS = '8px';
exports.SIDEBAR_WIDTH = '240px';
};

// ── module: src/utils.ts ──
__mods["src/utils.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FocusTrap = void 0;
exports.getFocusableElements = getFocusableElements;
exports.prefersReducedMotion = prefersReducedMotion;
exports.debounce = debounce;
exports.uid = uid;
// Focusable element selector — covers all interactive elements that can receive focus
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(', ');
function getFocusableElements(container) {
    return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR));
}
// Focus trap utility — keeps focus cycling within a container
class FocusTrap {
    constructor(container) {
        this.previousActiveElement = null;
        this.handlerBound = false;
        this.handleKeydown = (e) => {
            if (e.key !== 'Tab' || !this.handlerBound)
                return;
            const focusables = getFocusableElements(this.container);
            if (focusables.length === 0)
                return;
            const first = focusables[0];
            const last = focusables[focusables.length - 1];
            if (e.shiftKey) {
                // Shift+Tab: wrap from first to last
                if (document.activeElement === first || !this.container.contains(document.activeElement)) {
                    e.preventDefault();
                    last.focus();
                }
            }
            else {
                // Tab: wrap from last to first
                if (document.activeElement === last || !this.container.contains(document.activeElement)) {
                    e.preventDefault();
                    first.focus();
                }
            }
        };
        this.container = container;
    }
    activate() {
        this.previousActiveElement = document.activeElement;
        const focusables = getFocusableElements(this.container);
        if (focusables.length > 0) {
            focusables[0].focus();
        }
        this.handlerBound = true;
        document.addEventListener('keydown', this.handleKeydown);
    }
    deactivate() {
        document.removeEventListener('keydown', this.handleKeydown);
        this.handlerBound = false;
        if (this.previousActiveElement && typeof this.previousActiveElement.focus === 'function') {
            this.previousActiveElement.focus();
        }
    }
}
exports.FocusTrap = FocusTrap;
// Reduced motion detection
function prefersReducedMotion() {
    return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
}
// Debounce helper
function debounce(fn, ms) {
    let timer;
    return (...args) => {
        clearTimeout(timer);
        timer = setTimeout(() => fn(...args), ms);
    };
}
// Generate a unique ID
let idCounter = 0;
function uid(prefix = 'id') {
    return `${prefix}-${++idCounter}`;
}
};

// ── module: src/modal.ts ──
__mods["src/modal.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initModal = initModal;
const utils_1 = require("./utils");
function initModal() {
    const trigger = document.getElementById('modal-trigger');
    const dialog = document.getElementById('modal-dialog');
    const closeBtn = document.getElementById('modal-close');
    const backdrop = document.getElementById('modal-backdrop');
    if (!trigger || !dialog)
        return;
    let trap = null;
    function openModal() {
        dialog.removeAttribute('hidden');
        backdrop.removeAttribute('hidden');
        trigger.setAttribute('aria-expanded', 'true');
        // Make the rest of the page inert
        document.body.setAttribute('inert', '');
        trap = new utils_1.FocusTrap(dialog);
        trap.activate();
    }
    function closeModal() {
        if (!trap)
            return;
        trap.deactivate();
        trap = null;
        dialog.setAttribute('hidden', '');
        backdrop.setAttribute('hidden', '');
        trigger.setAttribute('aria-expanded', 'false');
        // Remove inert from body so page is interactive again
        document.body.removeAttribute('inert');
    }
    trigger.addEventListener('click', openModal);
    closeBtn?.addEventListener('click', closeModal);
    backdrop?.addEventListener('click', closeModal);
    dialog.addEventListener('keydown', (e) => {
        if (e.key === 'Escape') {
            e.preventDefault();
            closeModal();
        }
    });
}
};

// ── module: src/dropdown.ts ──
__mods["src/dropdown.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initDropdown = initDropdown;
function initDropdown() {
    const button = document.getElementById('dropdown-trigger');
    const menu = document.getElementById('dropdown-menu');
    if (!button || !menu)
        return;
    const items = Array.from(menu.querySelectorAll('[role="menuitem"]'));
    let isOpen = false;
    function openMenu(focusFirst = true) {
        menu.removeAttribute('hidden');
        button.setAttribute('aria-expanded', 'true');
        if (focusFirst && items.length > 0) {
            items[0].focus();
        }
        isOpen = true;
    }
    function closeMenu(returnFocus = true) {
        menu.setAttribute('hidden', '');
        button.setAttribute('aria-expanded', 'false');
        isOpen = false;
        if (returnFocus) {
            button.focus();
        }
    }
    // Button triggers
    button.addEventListener('click', () => {
        if (isOpen)
            closeMenu();
        else
            openMenu();
    });
    button.addEventListener('keydown', (e) => {
        if ((e.key === 'Enter' || e.key === ' ') && !isOpen) {
            e.preventDefault();
            openMenu();
        }
        if (e.key === 'ArrowDown') {
            e.preventDefault();
            openMenu(true);
        }
    });
    // Menu item key navigation
    menu.addEventListener('keydown', (e) => {
        const currentIndex = items.indexOf(document.activeElement);
        switch (e.key) {
            case 'ArrowDown':
                e.preventDefault();
                if (currentIndex < items.length - 1) {
                    items[currentIndex + 1].focus();
                }
                else {
                    items[0].focus(); // wrap
                }
                break;
            case 'ArrowUp':
                e.preventDefault();
                if (currentIndex > 0) {
                    items[currentIndex - 1].focus();
                }
                else {
                    items[items.length - 1].focus(); // wrap
                }
                break;
            case 'Home':
                e.preventDefault();
                items[0]?.focus();
                break;
            case 'End':
                e.preventDefault();
                items[items.length - 1]?.focus();
                break;
            case 'Escape':
                e.preventDefault();
                closeMenu(true);
                break;
            case 'Tab':
                closeMenu(false); // let focus move naturally
                break;
            case 'Enter':
            case ' ':
                if (document.activeElement && items.includes(document.activeElement)) {
                    e.preventDefault();
                    document.activeElement.click();
                }
                break;
        }
    });
    // Menu item click handlers — perform action and close
    items.forEach((item) => {
        item.addEventListener('click', () => {
            const action = item.getAttribute('data-action');
            if (action) {
                announceAction(action);
            }
            closeMenu(true);
        });
    });
    // Close on outside click
    document.addEventListener('click', (e) => {
        if (!isOpen)
            return;
        const target = e.target;
        if (!menu.contains(target) && !button.contains(target)) {
            closeMenu(false);
        }
    });
}
// Live region for announcements
function announceAction(text) {
    let announcer = document.getElementById('dropdown-announcer');
    if (!announcer)
        return;
    announcer.textContent = '';
    requestAnimationFrame(() => {
        announcer.textContent = text;
    });
}
};

// ── module: src/tabs.ts ──
__mods["src/tabs.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initTabs = initTabs;
function initTabs() {
    const tablist = document.getElementById('tablist');
    if (!tablist)
        return;
    const tabs = Array.from(tablist.querySelectorAll('[role="tab"]'));
    if (tabs.length === 0)
        return;
    // Activate a specific tab by index
    function activateTab(index) {
        tabs.forEach((tab, i) => {
            const panelId = tab.getAttribute('aria-controls');
            const panel = document.getElementById(panelId);
            if (i === index) {
                tab.setAttribute('aria-selected', 'true');
                tab.setAttribute('tabindex', '0');
                tab.classList.add('is-active');
                panel?.removeAttribute('hidden');
            }
            else {
                tab.setAttribute('aria-selected', 'false');
                tab.setAttribute('tabindex', '-1');
                tab.classList.remove('is-active');
                panel?.setAttribute('hidden', '');
            }
        });
    }
    // Click handler for each tab
    tabs.forEach((tab, i) => {
        tab.addEventListener('click', () => activateTab(i));
    });
    // Keyboard navigation on the tablist
    tablist.addEventListener('keydown', (e) => {
        const currentIndex = tabs.indexOf(document.activeElement);
        if (currentIndex === -1)
            return;
        let newIndex = currentIndex;
        switch (e.key) {
            case 'ArrowRight':
                e.preventDefault();
                newIndex = (currentIndex + 1) % tabs.length;
                break;
            case 'ArrowLeft':
                e.preventDefault();
                newIndex = (currentIndex - 1 + tabs.length) % tabs.length;
                break;
            case 'Home':
                e.preventDefault();
                newIndex = 0;
                break;
            case 'End':
                e.preventDefault();
                newIndex = tabs.length - 1;
                break;
            default:
                return;
        }
        activateTab(newIndex);
        tabs[newIndex].focus();
    });
}
};

// ── module: src/combobox.ts ──
__mods["src/combobox.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initCombobox = initCombobox;
const OPTIONS = [
    'Afghanistan', 'Albania', 'Algeria', 'Andorra', 'Angola',
    'Argentina', 'Australia', 'Austria', 'Azerbaijan', 'Bahamas',
    'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium',
    'Benin', 'Bhutan', 'Bolivia', 'Bosnia and Herzegovina', 'Botswana',
];
function initCombobox() {
    const input = document.getElementById('combobox-input');
    const listbox = document.getElementById('combobox-listbox');
    if (!input || !listbox)
        return;
    let isOpen = false;
    let activeIndex = -1;
    let originalValue = '';
    function renderOptions(filter) {
        const filtered = filter === ''
            ? OPTIONS
            : OPTIONS.filter(o => o.toLowerCase().includes(filter.toLowerCase()));
        listbox.innerHTML = '';
        if (filtered.length === 0)
            return;
        filtered.forEach((option, i) => {
            const el = document.createElement('div');
            el.id = `combobox-option-${i}`;
            el.setAttribute('role', 'option');
            el.setAttribute('aria-selected', 'false');
            el.textContent = option;
            el.addEventListener('click', () => selectOption(option));
            listbox.appendChild(el);
        });
    }
    function openListbox() {
        const filter = input.value;
        renderOptions(filter);
        if (listbox.children.length === 0)
            return;
        listbox.removeAttribute('hidden');
        input.setAttribute('aria-expanded', 'true');
        isOpen = true;
    }
    function closeListbox() {
        listbox.setAttribute('hidden', '');
        input.setAttribute('aria-expanded', 'false');
        input.removeAttribute('aria-activedescendant');
        activeIndex = -1;
        isOpen = false;
    }
    function selectOption(value) {
        input.value = value;
        originalValue = ''; // reset revert state
        closeListbox();
        announceSelection(value);
    }
    function setActive(index) {
        const options = listbox.querySelectorAll('[role="option"]');
        if (index < 0 || index >= options.length)
            return;
        activeIndex = index;
        options.forEach((opt, i) => {
            opt.setAttribute('aria-selected', i === index ? 'true' : 'false');
        });
        input.setAttribute('aria-activedescendant', options[index].id);
        // Scroll into view if needed
        const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
        options[index].scrollIntoView({ block: 'nearest', behavior: reduced ? 'auto' : 'smooth' });
    }
    function announceSelection(value) {
        let announcer = document.getElementById('combobox-announcer');
        if (!announcer)
            return;
        announcer.textContent = '';
        requestAnimationFrame(() => {
            announcer.textContent = `${value} selected`;
        });
    }
    // Input events
    input.addEventListener('focus', () => {
        originalValue = input.value;
        openListbox();
    });
    input.addEventListener('input', () => {
        if (!isOpen)
            openListbox();
        else
            renderOptions(input.value);
        activeIndex = -1;
        input.removeAttribute('aria-activedescendant');
    });
    input.addEventListener('blur', () => {
        // Delay to allow click handlers on options to fire first
        requestAnimationFrame(() => {
            if (!listbox.contains(document.activeElement)) {
                closeListbox();
            }
        });
    });
    input.addEventListener('keydown', (e) => {
        const options = listbox.querySelectorAll('[role="option"]');
        switch (e.key) {
            case 'ArrowDown':
                e.preventDefault();
                if (!isOpen) {
                    openListbox();
                    return;
                }
                setActive(activeIndex < options.length - 1 ? activeIndex + 1 : 0);
                break;
            case 'ArrowUp':
                e.preventDefault();
                if (activeIndex > 0) {
                    setActive(activeIndex - 1);
                }
                else {
                    setActive(options.length - 1);
                }
                break;
            case 'Enter':
                e.preventDefault();
                if (isOpen && activeIndex >= 0 && options[activeIndex]) {
                    selectOption(options[activeIndex].textContent);
                }
                break;
            case 'Escape':
                e.preventDefault();
                // Revert to original value
                input.value = originalValue;
                closeListbox();
                break;
            case 'Tab':
                closeListbox();
                break;
        }
    });
    // Initial render (hidden)
    listbox.setAttribute('hidden', '');
}
};

// ── module: src/accordion.ts ──
__mods["src/accordion.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initAccordion = initAccordion;
const constants_1 = require("./constants");
function initAccordion() {
    const group = document.getElementById('accordion-group');
    if (!group)
        return;
    const buttons = Array.from(group.querySelectorAll('.accordion-trigger'));
    buttons.forEach((button) => {
        button.addEventListener('click', () => togglePanel(button));
        button.addEventListener('keydown', (e) => {
            if (e.key === 'Enter' || e.key === ' ') {
                e.preventDefault();
                togglePanel(button);
            }
        });
    });
}
function togglePanel(button) {
    const panelId = button.getAttribute('aria-controls');
    const panel = document.getElementById(panelId);
    if (!panel)
        return;
    const isExpanded = button.getAttribute('aria-expanded') === 'true';
    if (isExpanded) {
        // Collapse: animate height then set hidden
        collapsePanel(button, panel);
    }
    else {
        // Expand: remove hidden first, then animate
        expandPanel(button, panel);
    }
}
function expandPanel(button, panel) {
    const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    button.setAttribute('aria-expanded', 'true');
    panel.removeAttribute('hidden');
    // Measure natural height
    const targetHeight = panel.scrollHeight;
    panel.style.height = '0px';
    panel.style.overflow = 'hidden';
    if (reduced) {
        panel.style.height = '';
        panel.style.overflow = '';
        return;
    }
    // Force reflow so the browser registers height: 0 before animating
    panel.offsetHeight; // eslint-disable-line @typescript-eslint/no-unused-expressions
    panel.style.transition = `height ${constants_1.ANIMATION.accordionSlide}ms ease`;
    panel.style.height = `${targetHeight}px`;
    // After animation, remove constraints
    setTimeout(() => {
        if (button.getAttribute('aria-expanded') === 'true') {
            panel.style.height = '';
            panel.style.overflow = '';
            panel.style.transition = '';
        }
    }, constants_1.ANIMATION.accordionSlide);
}
function collapsePanel(button, panel) {
    const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    if (reduced) {
        button.setAttribute('aria-expanded', 'false');
        panel.setAttribute('hidden', '');
        return;
    }
    // Measure current height
    const startHeight = panel.scrollHeight;
    panel.style.height = `${startHeight}px`;
    panel.style.overflow = 'hidden';
    // Force reflow
    panel.offsetHeight; // eslint-disable-line @typescript-eslint/no-unused-expressions
    button.setAttribute('aria-expanded', 'false');
    panel.style.transition = `height ${constants_1.ANIMATION.accordionSlide}ms ease`;
    panel.style.height = '0px';
    // After animation, set hidden and clean up styles
    setTimeout(() => {
        if (button.getAttribute('aria-expanded') === 'false') {
            panel.setAttribute('hidden', '');
            panel.style.height = '';
            panel.style.overflow = '';
            panel.style.transition = '';
        }
    }, constants_1.ANIMATION.accordionSlide);
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"thinkingcap-qwen3.6-27b","provider":"lmstudio","persona":"typescript","sessionId":"65449ae3-ccaf-440d-9e01-f3943c95cacd","tokensIn":855378,"tokensOut":20272,"tokensTotal":875650,"turns":36,"toolCalls":35,"failedToolCalls":0,"timestamp":"2026-07-31T06:20:48.759Z"} -->