← Accessible Components results

Accessible Components

GPT 5.6 terra · 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 26,759 bytes · SHA-256 b309fd6542a2
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>A11y Patterns — Accessible Component Showcase</title>
<style>
/* ── css: design-system ── */
:root{--ink:#18233a;--muted:#657089;--line:#dce2eb;--paper:#f8fafc;--white:#fff;--navy:#233b6c;--mint:#b9e9d0;--mint-dark:#166b52;--lav:#e7e4ff;--coral:#ffded6;--radius:14px;--shadow:0 18px 45px rgba(25,40,70,.12);--fast:160ms;--normal:260ms}*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:var(--paper);color:var(--ink);font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;line-height:1.5}.skip-link{position:fixed;z-index:100;top:12px;left:12px;padding:10px 14px;background:var(--ink);color:#fff;border-radius:7px;transform:translateY(-150%);transition:transform var(--fast)}.skip-link:focus{transform:translateY(0)}.shell{max-width:1440px;margin:auto;display:grid;grid-template-columns:255px minmax(0,1fr)}.sidebar{position:sticky;top:0;height:100vh;padding:40px 28px;border-right:1px solid var(--line);background:#f4f7fa;display:flex;flex-direction:column}.brand{display:flex;align-items:center;gap:10px;color:var(--ink);text-decoration:none;font-weight:760;letter-spacing:-.03em}.brand small{display:block;font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--muted)}.brand-mark{display:grid;place-items:center;width:32px;height:32px;border-radius:9px;background:var(--navy);color:var(--mint);font-family:Georgia,serif;font-size:21px}.sidebar nav{margin-top:84px;display:grid;gap:5px}.nav-label,.eyebrow{margin:0 0 10px;font-size:10px;font-weight:800;letter-spacing:.14em;color:#6b7590}.sidebar nav a{padding:8px 10px;border-radius:7px;color:#516078;text-decoration:none;font-size:14px;font-weight:650}.sidebar nav a:hover{background:#e8edf4;color:var(--navy)}.sidebar-note{margin-top:auto;padding:14px;border-top:1px solid var(--line);font-size:12px;color:var(--muted);line-height:1.65}.sidebar-note span{color:var(--mint-dark);font-weight:900}main{padding:0 7.5vw 70px;min-width:0}.hero{padding:88px 0 78px;border-bottom:1px solid var(--line)}h1,h2,h3,p{margin-top:0}h1{max-width:800px;margin-bottom:22px;font-size:clamp(42px,5.3vw,76px);letter-spacing:-.065em;line-height:1.02;font-weight:780}h1 em{font-family:Georgia,serif;color:var(--navy);font-weight:500}.intro{max-width:600px;font-size:18px;color:var(--muted)}.legend{display:flex;gap:22px;margin-top:30px;color:#536078;font-size:13px;font-weight:650}.dot,.status i{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--mint-dark);margin-right:6px}.component-section{display:grid;grid-template-columns:minmax(170px,250px) minmax(0,1fr);gap:38px;padding:78px 0;border-bottom:1px solid var(--line);scroll-margin-top:20px}.section-meta>p:first-child{font-size:10px;letter-spacing:.13em;font-weight:800;color:#7d87a0}.section-meta h2{font-size:27px;letter-spacing:-.04em;margin-bottom:9px}.section-meta>p:last-child{font-size:14px;color:var(--muted)}.demo-card{min-width:0;background:var(--white);border:1px solid var(--line);border-radius:var(--radius);overflow:visible;box-shadow:0 4px 12px rgba(28,43,70,.025)}.demo-top{min-height:51px;border-bottom:1px solid var(--line);padding:14px 19px;display:flex;justify-content:space-between;gap:12px;align-items:center;font-size:12px;color:var(--muted)}.status{font-weight:750;color:var(--ink)}.contract{font-family:ui-monospace,SFMono-Regular,monospace;font-size:11px;color:#727d96}.modal-demo,.menu-demo,.tabs-demo,.combo-demo,.accordion-demo{padding:32px}.modal-demo p,.menu-demo p{color:var(--muted);margin-bottom:20px}.btn{min-height:42px;padding:9px 15px;border-radius:8px;border:1px solid transparent;font:inherit;font-size:14px;font-weight:730;cursor:pointer}.primary{background:var(--navy);color:#fff}.primary:hover{background:#172e5e}.secondary{background:#fff;border-color:#cbd4e1;color:var(--ink)}.secondary:hover{background:#f2f5f8}.text-btn{background:transparent;border:0;padding:0;color:var(--navy);font:inherit;font-weight:700;text-decoration:underline;cursor:pointer}.menu-demo{display:flex;align-items:center;gap:24px;min-height:129px}.menu-wrap,.combo-wrap{position:relative}.menu{position:absolute;z-index:5;left:0;top:calc(100% + 7px);width:214px;padding:6px;background:#fff;border:1px solid var(--line);border-radius:10px;box-shadow:var(--shadow)}.menu button{width:100%;display:flex;justify-content:space-between;padding:10px;border:0;border-radius:6px;background:#fff;color:var(--ink);font:inherit;font-size:14px;text-align:left;cursor:pointer}.menu button:hover,.menu button:focus{background:#edf2ff}.menu kbd{color:var(--muted);font-size:11px}.result{margin:0!important;font-size:14px}.tablist{display:flex;gap:4px;border-bottom:1px solid var(--line);margin-bottom:22px}.tablist button{padding:10px 13px;border:0;border-bottom:2px solid transparent;margin-bottom:-1px;background:transparent;color:var(--muted);font:inherit;font-size:14px;font-weight:700;cursor:pointer}.tablist button[aria-selected="true"]{border-color:var(--navy);color:var(--navy)}.tabpanel{padding:16px;border-radius:9px;background:#f3f6fa;font-size:14px}.tabpanel p{margin:3px 0 12px;color:var(--muted)}.tabpanel a{color:var(--navy);font-weight:700}.progress{height:7px;border-radius:5px;background:#dfe6ef}.progress span{display:block;width:82%;height:100%;border-radius:inherit;background:var(--mint-dark)}label{display:block;margin-bottom:7px;font-size:13px;font-weight:750}.combo-wrap{max-width:440px}.combo-wrap input{width:100%;min-height:43px;padding:10px 12px;border:1px solid #bfcada;border-radius:8px;color:var(--ink);font:inherit}.listbox{position:absolute;z-index:6;top:calc(100% + 5px);left:0;width:100%;max-height:204px;overflow:auto;margin:0;padding:5px;list-style:none;background:#fff;border:1px solid var(--line);border-radius:9px;box-shadow:var(--shadow)}.listbox li{padding:8px 10px;border-radius:6px;font-size:14px;cursor:pointer}.listbox li[aria-selected="true"],.listbox li:hover{background:#e7edff;color:#193468}.field-hint{margin:10px 0 0;color:var(--muted);font-size:12px}.accordion-demo{padding-top:14px;padding-bottom:14px}.accordion-item{border-bottom:1px solid var(--line)}.accordion-item:last-child{border-bottom:0}.accordion-item h3{margin:0}.accordion-item button{width:100%;display:flex;justify-content:space-between;align-items:center;padding:18px 0;border:0;background:transparent;color:var(--ink);font:inherit;font-size:14px;font-weight:720;text-align:left;cursor:pointer}.accordion-item button span{font-size:22px;font-weight:400;color:var(--navy);transition:transform var(--normal)}.accordion-item button[aria-expanded="true"] span{transform:rotate(45deg)}.accordion-panel{overflow:hidden;height:0;transition:height var(--normal) ease}.accordion-panel>div{padding:0 28px 18px 0}.accordion-panel p{margin:0;color:var(--muted);font-size:14px}footer{padding:36px 0;color:var(--muted);font-size:13px}footer strong{color:var(--ink)}.modal-layer{position:fixed;z-index:50;inset:0;display:grid;place-items:center;padding:20px}.backdrop{position:absolute;inset:0;background:rgba(17,28,50,.56);backdrop-filter:blur(2px)}.dialog{position:relative;width:min(100%,480px);padding:27px;border-radius:15px;background:#fff;box-shadow:0 25px 80px rgba(6,15,32,.35)}.dialog-header{display:flex;justify-content:space-between;gap:15px}.dialog h2{margin:0;font-size:27px;letter-spacing:-.04em}.dialog>p{margin:13px 0 22px;color:var(--muted)}.dialog input{width:100%;min-height:43px;padding:10px 12px;border:1px solid #bfcada;border-radius:8px;font:inherit}.dialog-actions{display:flex;justify-content:flex-end;gap:9px;margin-top:23px}.icon-btn{width:34px;height:34px;border:1px solid var(--line);border-radius:7px;background:#fff;color:var(--ink);font-size:25px;line-height:1;cursor:pointer}button:focus-visible,a:focus-visible,input:focus-visible,[role="tab"]:focus-visible{outline:3px solid #ed8c71;outline-offset:3px}button:focus:not(:focus-visible),a:focus:not(:focus-visible),input:focus:not(:focus-visible){outline:none}[hidden]{display:none!important}@media(max-width:800px){.shell{display:block}.sidebar{position:static;height:auto;padding:22px;border-right:0;border-bottom:1px solid var(--line)}.sidebar nav{margin-top:23px;display:flex;overflow:auto}.sidebar nav a{white-space:nowrap}.sidebar-note{display:none}main{padding:0 24px 50px}.hero{padding:56px 0}.component-section{grid-template-columns:1fr;gap:20px;padding:55px 0}}@media(max-width:500px){.component-section{padding:44px 0}.demo-top{align-items:flex-start;flex-direction:column}.modal-demo,.menu-demo,.tabs-demo,.combo-demo{padding:22px}.menu-demo{display:block}.result{margin-top:18px!important}.legend{flex-direction:column;gap:6px}.contract{white-space:normal}}@media(prefers-reduced-motion:reduce){*,*:before,*:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important}}
</style>
</head>
<body>
<a class="skip-link" href="#main">Skip to main content</a><div class="shell"><aside class="sidebar" aria-label="Component navigation"><a class="brand" href="#top"><span class="brand-mark">A</span><span>A11y Patterns<small>Component library</small></span></a><nav aria-label="On this page"><p class="nav-label">PATTERNS</p><a href="#dialog">Modal dialog</a><a href="#menu">Dropdown menu</a><a href="#tabs">Tabs</a><a href="#combobox">Combobox</a><a href="#accordion">Accordion</a></nav><div class="sidebar-note"><span aria-hidden="true">✓</span> Keyboard-first<br>WAI-ARIA patterns</div></aside><main id="main" tabindex="-1"><header id="top" class="hero"><p class="eyebrow">FOUNDATIONS / INTERACTION</p><h1>Accessible components,<br><em>made tangible.</em></h1><p class="intro">Five carefully engineered interaction patterns with the semantics and keyboard behavior your interface deserves.</p><div class="legend"><span><i class="dot"></i> Live examples</span><span>Built with vanilla JavaScript</span></div></header><section id="dialog" class="component-section" aria-labelledby="dialog-heading"><div class="section-meta"><p>01 / OVERLAY</p><h2 id="dialog-heading">Modal dialog</h2><p>A contained decision space that holds focus until the task is complete.</p></div><div class="demo-card"><div class="demo-top"><span class="status"><i></i> Live component</span><span class="contract">Tab cycle · Esc close</span></div><div class="modal-demo"><p>Invite a teammate to your workspace.</p><button id="modal-trigger" class="btn primary">Open invite dialog <span aria-hidden="true">→</span></button></div></div></section><section id="menu" class="component-section" aria-labelledby="menu-heading"><div class="section-meta"><p>02 / ACTIONS</p><h2 id="menu-heading">Dropdown menu</h2><p>Contextual actions with predictable directional navigation.</p></div><div class="demo-card"><div class="demo-top"><span class="status"><i></i> Live component</span><span class="contract">↑↓ navigate · Enter select · Esc close</span></div><div class="menu-demo"><div class="menu-wrap"><button id="menu-button" class="btn secondary" aria-haspopup="menu" aria-expanded="false" aria-controls="action-menu">Project actions <span aria-hidden="true">⌄</span></button><div id="action-menu" class="menu" role="menu" aria-labelledby="menu-button" hidden=""><button role="menuitem">Rename project <kbd>R</kbd></button><button role="menuitem">Duplicate project <kbd>⌘D</kbd></button><button role="menuitem">Archive project <kbd>⌫</kbd></button></div></div><p id="menu-result" class="result" aria-live="polite">Choose an action to see it here.</p></div></div></section><section id="tabs" class="component-section" aria-labelledby="tabs-heading"><div class="section-meta"><p>03 / NAVIGATION</p><h2 id="tabs-heading">Tabs</h2><p>One destination in the tab order, with rapid arrow-key switching.</p></div><div class="demo-card"><div class="demo-top"><span class="status"><i></i> Live component</span><span class="contract">← → switch · Tab enters panel</span></div><div class="tabs-demo"><div class="tablist" role="tablist" aria-label="Workspace details"><button id="tab-overview" role="tab" aria-selected="true" aria-controls="panel-overview" tabindex="0">Overview</button><button id="tab-activity" role="tab" aria-selected="false" aria-controls="panel-activity" tabindex="-1">Activity</button><button id="tab-members" role="tab" aria-selected="false" aria-controls="panel-members" tabindex="-1">Members</button></div><div id="panel-overview" class="tabpanel" role="tabpanel" aria-labelledby="tab-overview" tabindex="0"><strong>Design system</strong><p>24 components · 98% coverage</p><div class="progress"><span></span></div></div><div id="panel-activity" class="tabpanel" role="tabpanel" aria-labelledby="tab-activity" tabindex="0" hidden=""><strong>Latest activity</strong><p>Token audit completed just now.</p><a href="#tabs">View activity log →</a></div><div id="panel-members" class="tabpanel" role="tabpanel" aria-labelledby="tab-members" tabindex="0" hidden=""><strong>Team members</strong><p>12 designers and engineers collaborating.</p><button class="text-btn">Manage members →</button></div></div></div></section><section id="combobox" class="component-section" aria-labelledby="combo-heading"><div class="section-meta"><p>04 / INPUT</p><h2 id="combo-heading">Combobox</h2><p>Autocomplete that keeps focus in the input and speaks its current option.</p></div><div class="demo-card"><div class="demo-top"><span class="status"><i></i> Live component</span><span class="contract">Type filter · ↑↓ browse · Enter select</span></div><div class="combo-demo"><label for="country-input">Choose a workspace region</label><div class="combo-wrap"><input id="country-input" role="combobox" aria-autocomplete="list" aria-expanded="false" aria-controls="region-listbox" aria-activedescendant="" autocomplete="off" placeholder="Start typing a region…"><ul id="region-listbox" class="listbox" role="listbox" aria-label="Workspace regions" hidden=""></ul></div><p id="combo-hint" class="field-hint" aria-live="polite">20 regions available. Use arrow keys to browse.</p></div></div></section><section id="accordion" class="component-section" aria-labelledby="accordion-heading"><div class="section-meta"><p>05 / DISCLOSURE</p><h2 id="accordion-heading">Accordion</h2><p>Independent disclosures for information that benefits from progressive reveal.</p></div><div class="demo-card"><div class="demo-top"><span class="status"><i></i> Live component</span><span class="contract">Enter / Space toggle · Multiple open</span></div><div class="accordion-demo"><div class="accordion-item"><h3><button id="acc-1" aria-expanded="false" aria-controls="acc-panel-1">Why is keyboard support important?<span aria-hidden="true">+</span></button></h3><div id="acc-panel-1" class="accordion-panel" role="region" aria-labelledby="acc-1" hidden=""><div><p>It gives every person an efficient way to move through an interface, including people who use switch devices, voice control, and screen readers.</p></div></div></div><div class="accordion-item"><h3><button id="acc-2" aria-expanded="false" aria-controls="acc-panel-2">What does a visible focus state do?<span aria-hidden="true">+</span></button></h3><div id="acc-panel-2" class="accordion-panel" role="region" aria-labelledby="acc-2" hidden=""><div><p>It preserves a clear sense of location, so keyboard users never have to guess which control will receive their next action.</p></div></div></div><div class="accordion-item"><h3><button id="acc-3" aria-expanded="false" aria-controls="acc-panel-3">Can more than one section stay open?<span aria-hidden="true">+</span></button></h3><div id="acc-panel-3" class="accordion-panel" role="region" aria-labelledby="acc-3" hidden=""><div><p>Yes. These disclosures are independent, so people can compare information without losing their place.</p></div></div></div></div></div></section><footer>Patterns built to be <strong>perceivable, operable, understandable, and robust.</strong></footer></main></div><div id="modal-layer" class="modal-layer" hidden=""><div class="backdrop" data-close-modal=""></div><div id="invite-dialog" class="dialog" role="dialog" aria-modal="true" aria-labelledby="dialog-title" aria-describedby="dialog-description" tabindex="-1"><div class="dialog-header"><div><p class="eyebrow">NEW INVITATION</p><h2 id="dialog-title">Invite teammate</h2></div><button id="modal-close" class="icon-btn" aria-label="Close dialog">×</button></div><p id="dialog-description">Send a secure invitation to collaborate in your workspace.</p><label for="invite-email">Email address</label><input id="invite-email" type="email" placeholder="name@company.com"><div class="dialog-actions"><button id="modal-cancel" class="btn secondary">Cancel</button><button id="modal-send" class="btn primary">Send invitation</button></div></div></div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {};
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 });
// Tunable interaction constants
const MOTION_MS = 260;
const FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), [tabindex]:not([tabindex="-1"])';
// Shared helpers
function el(id) { return document.getElementById(id); }
function focusables(root) { return [...root.querySelectorAll(FOCUSABLE)].filter(x => !x.hasAttribute('hidden')); }
function keyIs(event, ...keys) { return keys.includes(event.key); }
// Modal dialog: inert page, return focus, and contained tab loop
const modalLayer = el('modal-layer');
const dialog = el('invite-dialog');
const modalTrigger = el('modal-trigger');
const closeModalButton = el('modal-close');
let modalReturnFocus = modalTrigger;
function openModal() {
    modalReturnFocus = modalTrigger;
    modalLayer.hidden = false;
    document.querySelector('main')?.setAttribute('inert', '');
    document.querySelector('.sidebar')?.setAttribute('inert', '');
    requestAnimationFrame(() => el('invite-email').focus());
}
function closeModal() {
    if (modalLayer.hidden)
        return;
    modalLayer.hidden = true;
    document.querySelector('main')?.removeAttribute('inert');
    document.querySelector('.sidebar')?.removeAttribute('inert');
    modalReturnFocus.focus();
}
modalTrigger.addEventListener('click', openModal);
closeModalButton.addEventListener('click', closeModal);
el('modal-cancel').addEventListener('click', closeModal);
el('modal-send').addEventListener('click', () => { closeModal(); });
modalLayer.addEventListener('click', event => { if (event.target.matches('[data-close-modal]'))
    closeModal(); });
dialog.addEventListener('keydown', event => {
    if (event.key === 'Escape') {
        event.preventDefault();
        closeModal();
        return;
    }
    if (event.key !== 'Tab')
        return;
    const items = focusables(dialog);
    const first = items[0];
    const last = items[items.length - 1];
    if (event.shiftKey && document.activeElement === first) {
        event.preventDefault();
        last.focus();
    }
    else if (!event.shiftKey && document.activeElement === last) {
        event.preventDefault();
        first.focus();
    }
});
// Menu button with roving directional focus
const menuButton = el('menu-button');
const menu = el('action-menu');
const menuItems = [...menu.querySelectorAll('[role="menuitem"]')];
function setMenu(open, focusIndex) {
    menu.hidden = !open;
    menuButton.setAttribute('aria-expanded', String(open));
    if (open && focusIndex !== undefined)
        menuItems[focusIndex].focus();
}
menuButton.addEventListener('click', () => setMenu(menu.hidden));
menuButton.addEventListener('keydown', event => {
    if (keyIs(event, 'ArrowDown', 'Enter', ' ')) {
        event.preventDefault();
        setMenu(true, 0);
    }
    if (event.key === 'ArrowUp') {
        event.preventDefault();
        setMenu(true, menuItems.length - 1);
    }
});
menu.addEventListener('keydown', event => {
    const index = menuItems.indexOf(document.activeElement);
    if (event.key === 'Escape') {
        event.preventDefault();
        setMenu(false);
        menuButton.focus();
    }
    else if (event.key === 'Tab')
        setMenu(false);
    else if (event.key === 'ArrowDown') {
        event.preventDefault();
        menuItems[(index + 1 + menuItems.length) % menuItems.length].focus();
    }
    else if (event.key === 'ArrowUp') {
        event.preventDefault();
        menuItems[(index - 1 + menuItems.length) % menuItems.length].focus();
    }
    else if (event.key === 'Home') {
        event.preventDefault();
        menuItems[0].focus();
    }
    else if (event.key === 'End') {
        event.preventDefault();
        menuItems[menuItems.length - 1].focus();
    }
});
menuItems.forEach(item => item.addEventListener('click', () => { el('menu-result').textContent = `“${item.childNodes[0].textContent?.trim()}” selected.`; setMenu(false); menuButton.focus(); }));
// Tabs: one active tab in the tab sequence, automatic activation
const tabs = [...document.querySelectorAll('[role="tab"]')];
function activateTab(tab, moveFocus = true) {
    tabs.forEach(candidate => { const active = candidate === tab; candidate.setAttribute('aria-selected', String(active)); candidate.tabIndex = active ? 0 : -1; el(candidate.getAttribute('aria-controls')).hidden = !active; });
    if (moveFocus)
        tab.focus();
}
tabs.forEach((tab, index) => tab.addEventListener('keydown', event => {
    let next = -1;
    if (event.key === 'ArrowRight')
        next = (index + 1) % tabs.length;
    if (event.key === 'ArrowLeft')
        next = (index - 1 + tabs.length) % tabs.length;
    if (event.key === 'Home')
        next = 0;
    if (event.key === 'End')
        next = tabs.length - 1;
    if (next >= 0) {
        event.preventDefault();
        activateTab(tabs[next]);
    }
}));
tabs.forEach(tab => tab.addEventListener('click', () => activateTab(tab, false)));
// Combobox: aria-activedescendant pattern (focus always stays in input)
const regions = ['Amsterdam', 'Atlanta', 'Auckland', 'Berlin', 'Boston', 'Brussels', 'Copenhagen', 'Dallas', 'Dublin', 'Frankfurt', 'Helsinki', 'Lisbon', 'London', 'Madrid', 'Melbourne', 'Montreal', 'New York', 'Paris', 'Singapore', 'Tokyo'];
const combo = el('country-input');
const listbox = el('region-listbox');
const hint = el('combo-hint');
let filtered = regions.slice();
let activeIndex = -1;
let committed = '';
function renderOptions() {
    listbox.replaceChildren(...filtered.map((name, i) => { const option = document.createElement('li'); option.id = `region-option-${i}`; option.role = 'option'; option.textContent = name; option.setAttribute('aria-selected', String(i === activeIndex)); option.addEventListener('mousedown', e => e.preventDefault()); option.addEventListener('click', () => choose(i)); return option; }));
    const open = filtered.length > 0 && document.activeElement === combo;
    listbox.hidden = !open;
    combo.setAttribute('aria-expanded', String(open));
    combo.setAttribute('aria-activedescendant', activeIndex >= 0 ? `region-option-${activeIndex}` : '');
    if (activeIndex >= 0)
        el(`region-option-${activeIndex}`).scrollIntoView({ block: 'nearest' });
}
function choose(index) { const value = filtered[index]; if (!value)
    return; combo.value = value; committed = value; activeIndex = index; hint.textContent = `${value} selected.`; combo.setAttribute('aria-activedescendant', `region-option-${index}`); listbox.hidden = true; combo.setAttribute('aria-expanded', 'false'); }
combo.addEventListener('focus', () => { filtered = regions.filter(x => x.toLowerCase().includes(combo.value.toLowerCase())); activeIndex = -1; renderOptions(); });
combo.addEventListener('input', () => { filtered = regions.filter(x => x.toLowerCase().includes(combo.value.toLowerCase())); activeIndex = -1; hint.textContent = filtered.length ? `${filtered.length} regions available. Use arrow keys to browse.` : 'No matching regions.'; renderOptions(); });
combo.addEventListener('keydown', event => { if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
    event.preventDefault();
    if (!filtered.length)
        return;
    activeIndex = event.key === 'ArrowDown' ? (activeIndex + 1) % filtered.length : (activeIndex - 1 + filtered.length) % filtered.length;
    renderOptions();
}
else if (event.key === 'Enter' && activeIndex >= 0) {
    event.preventDefault();
    choose(activeIndex);
}
else if (event.key === 'Escape') {
    combo.value = committed;
    activeIndex = -1;
    listbox.hidden = true;
    combo.setAttribute('aria-expanded', 'false');
    combo.setAttribute('aria-activedescendant', '');
} });
combo.addEventListener('blur', () => setTimeout(() => { listbox.hidden = true; combo.setAttribute('aria-expanded', 'false'); }, 120));
// Accordion: animate height, then hide only after closing transition
[...document.querySelectorAll('.accordion-item button')].forEach(button => button.addEventListener('click', () => {
    const panel = el(button.getAttribute('aria-controls'));
    const opening = button.getAttribute('aria-expanded') !== 'true';
    button.setAttribute('aria-expanded', String(opening));
    if (opening) {
        panel.hidden = false;
        panel.style.height = '0px';
        requestAnimationFrame(() => { panel.style.height = `${panel.scrollHeight}px`; });
        const done = () => { panel.style.height = 'auto'; panel.removeEventListener('transitionend', done); };
        panel.addEventListener('transitionend', done);
    }
    else {
        panel.style.height = `${panel.scrollHeight}px`;
        requestAnimationFrame(() => { panel.style.height = '0px'; });
        const done = () => { panel.hidden = true; panel.removeEventListener('transitionend', done); };
        panel.addEventListener('transitionend', done);
    }
}));
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"openai/gpt-5.6-terra","provider":"openrouter","persona":"typescript","sessionId":"c899736e-ab87-49f4-8677-5d66ad69a0d5","tokensIn":264817,"tokensOut":10753,"tokensTotal":275570,"turns":22,"toolCalls":23,"failedToolCalls":1,"timestamp":"2026-07-31T16:05:13.976Z"} -->