Accessible Components
nex agi Nex N2.5 mini GGUF · typescript

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 56,718 bytes · SHA-256 02e142bf1a5f
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ARIA Components — Accessible Interaction Showcase</title>
<style>
/* ── css: tokens ── */
:root {
--ink: #17202b;
--muted: #65707f;
--faint: #8a95a2;
--line: #e6e9ec;
--line-strong: #d6dce1;
--surface: #ffffff;
--canvas: #f7f8f7;
--wash: #eef2f1;
--accent: #236a62;
--accent-deep: #195049;
--accent-soft: #e6f1ee;
--accent-warm: #e4a35e;
--danger: #a24b4b;
--shadow: 0 18px 50px rgba(30, 51, 48, .13);
--radius: 14px;
--radius-sm: 9px;
--focus: 0 0 0 3px rgba(35, 106, 98, .26);
--font: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--mono: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
--container: 1180px;
--side: 250px;
--section-gap: clamp(56px, 7vw, 100px);
--ease: cubic-bezier(.22, .61, .21, 1);
--duration: 260ms;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; scroll-padding-top: 32px; }
body { margin: 0; color: var(--ink); background: var(--canvas); font-family: var(--font); font-size: 15px; line-height: 1.55; -webkit-font-smoothing: antialiased; }
button, input { font: inherit; }
button, a { -webkit-tap-highlight-color: transparent; }
a { color: inherit; }
::selection { background: var(--accent-soft); color: var(--accent-deep); }
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; box-shadow: var(--focus); }
.skip-link { position: fixed; z-index: 100; top: 14px; left: 14px; padding: 10px 14px; border-radius: 8px; color: #fff; background: var(--accent-deep); transform: translateY(-160%); transition: transform 160ms var(--ease); }
.skip-link:focus-visible { transform: translateY(0); outline-color: #fff; box-shadow: 0 8px 24px rgba(25,80,73,.25); }
.shell { min-height: 100vh; display: grid; grid-template-columns: var(--side) minmax(0, 1fr); }
.sidebar { position: sticky; top: 0; align-self: start; height: 100vh; display: flex; flex-direction: column; padding: 38px 26px 26px; border-right: 1px solid var(--line); background: rgba(255,255,255,.72); backdrop-filter: blur(12px); }
.brand { display: flex; align-items: center; gap: 10px; font-size: 15px; font-weight: 750; letter-spacing: .01em; }
.brand-mark { position: relative; display: grid; place-items: center; width: 24px; height: 24px; border-radius: 7px; background: var(--accent-deep); box-shadow: inset 0 0 0 1px rgba(255,255,255,.16); }
.brand-mark span:first-child, .brand-mark span:last-child { position: absolute; width: 6px; height: 6px; border-radius: 50%; background: #fff; }
.brand-mark span:first-child { transform: translate(-4px, -4px); }
.brand-mark span:last-child { transform: translate(4px, 4px); }
.sidebar-meta { display: flex; flex-direction: column; gap: 5px; margin: 50px 0 34px; padding-left: 2px; }
.eyebrow { color: var(--accent); font-size: 10px; font-weight: 800; letter-spacing: .15em; text-transform: uppercase; }
.version { color: var(--faint); font-family: var(--mono); font-size: 10px; letter-spacing: .04em; }
.side-nav { display: flex; flex-direction: column; gap: 3px; }
.nav-label { margin: 0 0 9px 11px; color: var(--faint); font-size: 10px; font-weight: 750; letter-spacing: .14em; text-transform: uppercase; }
.nav-link { display: flex; justify-content: space-between; align-items: center; padding: 11px 11px; border-radius: 8px; color: var(--muted); text-decoration: none; font-size: 13px; transition: color 160ms var(--ease), background 160ms var(--ease); }
.nav-link span { color: var(--faint); font-size: 13px; transition: transform 160ms var(--ease), color 160ms var(--ease); }
.nav-link:hover, .nav-link:focus-visible { color: var(--accent-deep); background: var(--accent-soft); }
.nav-link:hover span, .nav-link:focus-visible span { color: var(--accent); transform: translate(2px, -2px); }
.side-footer { margin-top: auto; padding: 18px 11px 0; border-top: 1px solid var(--line); }
.health { display: flex; align-items: center; gap: 7px; color: var(--accent-deep); font-size: 12px; font-weight: 700; }
.health-dot { width: 7px; height: 7px; border-radius: 50%; background: #59a878; box-shadow: 0 0 0 4px rgba(89,168,120,.14); }
.side-footer p { margin: 9px 0 0; color: var(--faint); font-size: 11px; line-height: 1.45; }
.workspace { min-width: 0; }
.hero { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(320px, .8fr); align-items: center; gap: 50px; min-height: 510px; padding: 90px max(48px, calc((100vw - var(--container)) / 2)); background: linear-gradient(110deg, #fff 0%, #fbfcfb 58%, #f1f5f3 100%); }
.hero-kicker, .hero-copy { max-width: 650px; }
.hero-kicker { display: flex; align-items: center; gap: 9px; color: var(--accent-deep); font-size: 11px; font-weight: 750; letter-spacing: .13em; text-transform: uppercase; }
.pulse { width: 8px; height: 8px; border-radius: 50%; background: var(--accent-warm); box-shadow: 0 0 0 5px rgba(228,163,94,.16); }
.hero h1 { max-width: 660px; margin: 20px 0 20px; font-size: clamp(42px, 5.1vw, 74px); font-weight: 700; letter-spacing: -.055em; line-height: .98; }
.hero h1 em { color: var(--accent); font-style: normal; }
.hero-copy { max-width: 570px; margin: 0; color: var(--muted); font-size: clamp(15px, 1.4vw, 18px); line-height: 1.65; }
.hero-actions { display: flex; align-items: center; gap: 22px; margin-top: 32px; }
.text-link { display: inline-flex; align-items: center; gap: 9px; color: var(--accent-deep); font-weight: 700; text-decoration: none; }
.text-link span { color: var(--accent); transition: transform 160ms var(--ease); }
.text-link:hover span { transform: translateX(4px); }
.hero-note { display: flex; align-items: center; gap: 5px; color: var(--faint); font-family: var(--mono); font-size: 11px; }
.kbd { display: inline-flex; min-width: 22px; justify-content: center; align-items: center; height: 22px; padding: 0 6px; border: 1px solid var(--line-strong); border-bottom-width: 2px; border-radius: 5px; background: #fff; color: var(--muted); font-family: var(--mono); font-size: 10px; line-height: 1; box-shadow: 0 1px 1px rgba(20,30,33,.04); }
.hero-orbit { position: relative; display: grid; place-items: center; justify-self: center; width: min(100%, 410px); aspect-ratio: 1; }
.orbit-ring { position: absolute; border: 1px solid #d9e5e1; border-radius: 50%; opacity: .7; }
.ring-one { inset: 14%; }
.ring-two { inset: 29%; border-style: dashed; opacity: .55; }
.orbit-core { position: relative; z-index: 1; display: grid; place-items: center; width: 122px; height: 122px; border-radius: 50%; color: #fff; background: var(--accent-deep); box-shadow: 0 18px 45px rgba(25,80,73,.22); }
.orbit-core span { font-size: 38px; line-height: 1; transform: translateY(8px); }
.orbit-core small { position: absolute; bottom: 25px; color: rgba(255,255,255,.72); font-size: 9px; letter-spacing: .08em; line-height: 1.2; text-align: center; text-transform: uppercase; }
.main-heading { display: flex; align-items: end; justify-content: space-between; gap: 20px; padding: 48px max(48px, calc((100vw - var(--container)) / 2)) 38px; }
.main-heading .intro { margin: 8px 0 0; color: var(--muted); font-size: 14px; }
.pattern-badge { display: inline-flex; align-items: center; gap: 8px; padding: 8px 11px; border: 1px solid #cfe2dc; border-radius: 999px; color: var(--accent-deep); background: rgba(230,241,238,.55); font-size: 11px; font-weight: 700; }
.badge-check { display: grid; place-items: center; width: 17px; height: 17px; border-radius: 50%; color: #fff; background: var(--accent); font-size: 10px; }
.component-section { padding: 0 max(48px, calc((100vw - var(--container)) / 2)) var(--section-gap); scroll-margin-top: 24px; }
.section-topline { display: flex; justify-content: space-between; align-items: center; margin-bottom: 18px; }
.section-number { color: var(--accent); font-family: var(--mono); font-size: 11px; font-weight: 700; letter-spacing: .12em; }
.tag { padding: 5px 9px; border: 1px solid var(--line-strong); border-radius: 999px; color: var(--muted); font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; }
.tag-outline { color: var(--accent); background: var(--accent-soft); border-color: #cfe2dc; }
.section-intro { display: flex; align-items: end; justify-content: space-between; gap: 30px; margin-bottom: 21px; }
.section-intro h2 { margin: 0; font-size: 24px; font-weight: 700; letter-spacing: -.035em; }
.section-intro p { max-width: 500px; margin: 0 0 2px; color: var(--muted); font-size: 13px; line-height: 1.6; }
.demo-card { overflow: hidden; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); box-shadow: 0 1px 2px rgba(29,45,47,.025), 0 14px 34px rgba(29,45,47,.035); }
.demo-card-header { display: flex; align-items: center; gap: 8px; min-height: 44px; padding: 0 20px; border-bottom: 1px solid var(--line); background: #fcfdfc; color: var(--muted); font-size: 11px; }
.demo-dot { width: 7px; height: 7px; border-radius: 50%; background: #65a98f; box-shadow: 0 0 0 4px rgba(101,169,143,.11); }
.demo-label { font-weight: 700; }
.demo-hint { margin-left: auto; color: var(--faint); font-size: 11px; }
.contract-caption { display: flex; align-items: center; gap: 9px; margin: 14px 2px 0; color: var(--muted); font-size: 11px; }
.contract-caption strong { color: var(--ink); font-size: 11px; }
.caption-icon { display: grid; place-items: center; width: 19px; height: 19px; border: 1px solid var(--line-strong); border-radius: 6px; color: var(--accent); background: #fff; font-size: 11px; }
.button { display: inline-flex; align-items: center; justify-content: center; gap: 9px; min-height: 40px; padding: 0 15px; border: 1px solid transparent; border-radius: 8px; cursor: pointer; font-size: 12px; font-weight: 700; transition: transform 150ms var(--ease), background 150ms var(--ease), border-color 150ms var(--ease), color 150ms var(--ease); }
.button:active { transform: translateY(1px); }
.button-primary { color: #fff; background: var(--accent-deep); box-shadow: 0 7px 16px rgba(25,80,73,.16); }
.button-primary:hover { background: var(--accent); }
.button-primary span { font-size: 14px; }
.button-secondary { color: var(--accent-deep); border-color: #d5e1de; background: #fff; }
.button-secondary:hover { border-color: var(--accent); background: var(--accent-soft); }
.text-input { width: 100%; min-height: 43px; padding: 0 13px; border: 1px solid var(--line-strong); border-radius: 8px; color: var(--ink); background: #fff; font-size: 13px; transition: border-color 150ms var(--ease), box-shadow 150ms var(--ease); }
.text-input::placeholder { color: #a1aaB1; }
.text-input:hover, .text-input:focus { border-color: #a9c4bd; }
.text-input:focus { border-color: var(--accent); box-shadow: var(--focus); }
/* Modal */
.dialog-backdrop { position: fixed; z-index: 50; inset: 0; display: grid; place-items: center; padding: 24px; background: rgba(19, 32, 37, .43); animation: fade-in 180ms var(--ease) both; }
.dialog-backdrop[hidden] { display: none; }
.modal-dialog { width: min(100%, 470px); padding: 27px; border: 1px solid rgba(255,255,255,.5); border-radius: 15px; background: #fff; box-shadow: var(--shadow); animation: dialog-in 220ms var(--ease) both; }
.dialog-backdrop[hidden] .modal-dialog { animation: none; }
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes dialog-in { from { opacity: 0; transform: translateY(12px) scale(.98); } to { opacity: 1; transform: translateY(0) scale(1); } }
.dialog-topline { display: flex; align-items: center; gap: 9px; margin-bottom: 18px; }
.dialog-icon { display: grid; place-items: center; width: 28px; height: 28px; border-radius: 8px; color: var(--accent); background: var(--accent-soft); }
.modal-dialog h3 { margin: 0 0 9px; font-size: 22px; letter-spacing: -.035em; }
.modal-dialog > p { margin: 0 0 21px; color: var(--muted); font-size: 13px; }
.field-label { display: block; margin-bottom: 7px; color: var(--ink); font-size: 11px; font-weight: 750; }
.dialog-actions { display: flex; justify-content: flex-end; gap: 9px; margin-top: 20px; }
/* Menu */
.menu-demo { position: relative; min-height: 184px; padding: 30px; background: linear-gradient(135deg, #fcfdfc, #f5f8f7); }
.menu-popover { position: absolute; z-index: 10; top: 81px; left: 30px; min-width: 245px; margin: 0; padding: 6px; border: 1px solid var(--line); border-radius: 10px; background: #fff; box-shadow: 0 15px 38px rgba(25,48,45,.13); list-style: none; }
.menu-popover[hidden] { display: none; }
.menu-popover button { display: flex; align-items: center; justify-content: space-between; width: 100%; padding: 10px 11px; border: 0; border-radius: 6px; color: var(--ink); background: transparent; cursor: pointer; text-align: left; font-size: 12px; }
.menu-popover button:hover, .menu-popover button[aria-current="true"] { color: var(--accent-deep); background: var(--accent-soft); }
.menu-item-symbol { color: var(--faint); }
.menu-result { display: flex; align-items: center; justify-content: space-between; gap: 20px; max-width: 250px; padding: 13px 15px; border-left: 2px solid var(--accent); background: #fff; box-shadow: 0 5px 18px rgba(29,45,47,.07); }
.result-label, .stat-label { display: block; color: var(--faint); font-size: 10px; font-weight: 650; text-transform: uppercase; letter-spacing: .08em; }
.menu-result strong { font-size: 12px; }
/* Tabs */
.tabs-demo { padding: 0 30px 28px; background: #fbfcfb; }
.tab-list { display: flex; gap: 27px; border-bottom: 1px solid var(--line); }
.tab { position: relative; padding: 15px 1px 13px; border: 0; color: var(--faint); background: transparent; cursor: pointer; font-size: 12px; font-weight: 700; }
.tab:hover { color: var(--accent-deep); }
.tab[aria-selected="true"] { color: var(--accent-deep); }
.tab[aria-selected="true"]::after { position: absolute; right: 0; bottom: -1px; left: 0; height: 2px; border-radius: 2px 2px 0 0; background: var(--accent); content: ""; }
.tab-index { margin-left: 7px; font-family: var(--mono); font-size: 9px; font-weight: 500; }
.tab[aria-selected="true"] .tab-index { color: var(--accent); }
.panel-content { padding: 25px 0 4px; }
.stat-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.stat { padding: 14px 16px; border: 1px solid var(--line); border-radius: 9px; background: #fff; }
.stat strong { display: inline-block; margin: 10px 0 3px; font-size: 24px; letter-spacing: -.04em; }
.stat-trend { font-size: 10px; font-weight: 750; }
.stat-trend.up { color: #3f966d; }
.stat-trend.down { color: var(--accent-warm); }
.panel-prompt { display: flex; align-items: center; gap: 13px; margin: 20px 0 8px; padding: 15px 16px; border: 1px solid #d9e8e4; border-radius: 9px; background: var(--accent-soft); }
.panel-prompt strong { font-size: 12px; }
.panel-prompt p { margin: 2px 0 0; color: var(--muted); font-size: 11px; }
.prompt-icon { color: var(--accent); }
.panel-chart { height: 220px; padding: 18px 22px 12px; border: 1px solid var(--line); border-radius: 10px; background: #fff; }
.chart-axis { position: absolute; display: flex; justify-content: space-between; }
.chart-bars { display: flex; align-items: end; gap: clamp(8px, 2vw, 21px); height: 155px; margin: 30px 22px 0; border-bottom: 1px solid var(--line); background: repeating-linear-gradient(to top, transparent 0, transparent 37px, #f0f3f2 38px); }
.chart-bars span { position: relative; display: block; flex: 1; height: var(--bar-h); min-height: 18px; border-radius: 4px 4px 0 0; background: linear-gradient(to top, var(--accent) 0%, #78aba3 100%); }
.chart-bars span:nth-child(2n) { background: linear-gradient(to top, var(--accent-warm) 0%, #f1c188 100%); }
.chart-caption { margin-top: 10px; color: var(--muted); font-size: 10px; }
.mini-stats { display: flex; justify-content: space-between; padding: 15px 4px 0; color: var(--muted); font-size: 10px; }
.mini-stats strong { color: var(--ink); font-family: var(--mono); font-size: 11px; }
.activity-list { display: flex; flex-direction: column; gap: 2px; padding: 8px 16px; border-top: 1px solid var(--line); }
.activity-row { display: flex; align-items: center; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--line); }
.activity-avatar { display: grid; place-items: center; flex: 0 0 30px; height: 30px; border-radius: 8px; color: #fff; font-size: 9px; font-weight: 800; }
.activity-avatar.amber { background: #d89b58; } .activity-avatar.blue { background: #6692c7; } .activity-avatar.green { background: #6aa581; } .activity-avatar.coral { background: #c7796c; }
.activity-row strong { font-size: 11px; }
.activity-row p { margin: 2px 0 0; color: var(--muted); font-size: 10px; }
.activity-row time { margin-left: 4px; color: var(--faint); font-size: 9px; }
/* Combobox */
.combobox-demo { min-height: 198px; padding: 28px 30px; background: linear-gradient(135deg, #fcfdfc, #f5f8f7); }
.combobox-shell { position: relative; }
.combobox-shell .text-input { padding-right: 42px; }
.input-glyph { position: absolute; top: 12px; right: 14px; color: var(--faint); font-size: 22px; pointer-events: none; }
.combo-list { position: absolute; z-index: 12; top: 52px; right: 0; left: 0; max-height: 222px; margin: 5px 0 0; padding: 5px; overflow-y: auto; border: 1px solid var(--line); border-radius: 9px; background: #fff; box-shadow: 0 15px 38px rgba(25,48,45,.13); list-style: none; }
.combo-list li { padding: 9px 11px; border-radius: 6px; color: var(--ink); font-size: 12px; }
.combo-list li[aria-selected="true"] { color: var(--accent-deep); background: var(--accent-soft); }
.combo-list li[aria-selected="false"] { color: var(--muted); }
.combo-empty { color: var(--muted); font-size: 12px; }
.combo-status { display: flex; align-items: center; gap: 8px; margin: 12px 2px 0; color: var(--muted); font-size: 11px; }
.status-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--accent-warm); }
.combo-status strong { color: var(--accent-deep); font-weight: 700; }
/* Accordion */
.accordion { padding: 4px 20px 20px; background: #fbfcfb; }
.accordion-item + .accordion-item { border-top: 1px solid var(--line); }
.accordion-heading { margin: 0; }
.accordion-button { display: flex; align-items: center; justify-content: space-between; width: 100%; min-height: 59px; padding: 0 3px; border: 0; color: var(--ink); background: transparent; cursor: pointer; text-align: left; font-size: 13px; font-weight: 700; }
.accordion-button:hover { color: var(--accent-deep); }
.accordion-button:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
.accordion-icon { display: grid; place-items: center; width: 25px; height: 25px; border: 1px solid var(--line-strong); border-radius: 7px; color: var(--accent); background: #fff; font-size: 18px; line-height: 1; transition: transform 180ms var(--ease), background 180ms var(--ease); }
.accordion-button[aria-expanded="true"] .accordion-icon { color: #fff; background: var(--accent-deep); border-color: var(--accent-deep); }
.accordion-panel { overflow: hidden; }
.accordion-content { padding: 0 3px 21px 39px; color: var(--muted); font-size: 12px; line-height: 1.65; }
.accordion-content p { margin: 0; }
/* Closing */
.closing-strip { display: flex; align-items: end; justify-content: space-between; gap: 30px; margin: 0 max(48px, calc((100vw - var(--container)) / 2)) 70px; padding: 34px 38px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--accent-deep); color: #fff; }
.closing-strip h2 { margin: 12px 0 7px; font-size: 27px; letter-spacing: -.04em; }
.closing-strip p { max-width: 600px; margin: 0; color: rgba(255,255,255,.7); font-size: 13px; }
.closing-strip .button { flex: 0 0 auto; color: var(--accent-deep); background: #fff; }
@media (max-width: 900px) {
:root { --side: 210px; }
.hero { grid-template-columns: 1fr; min-height: auto; padding-top: 64px; padding-bottom: 64px; }
.hero-orbit { justify-self: start; width: min(100%, 300px); margin-top: 20px; }
.main-heading, .component-section, .closing-strip { padding-right: 28px; padding-left: 28px; }
.section-intro { display: block; }
.section-intro p { margin-top: 12px; }
}
@media (max-width: 680px) {
:root { --container: none; --side: 0; --section-gap: 56px; }
body { font-size: 14px; }
.shell { display: block; }
.sidebar { position: relative; height: auto; padding: 22px 18px; border-right: 0; border-bottom: 1px solid var(--line); }
.sidebar-meta { flex-direction: row; justify-content: space-between; margin: 22px 0 17px; }
.side-nav { display: grid; grid-template-columns: repeat(5, 1fr); gap: 5px; }
.nav-label { grid-column: 1 / -1; }
.nav-link { justify-content: flex-start; padding: 9px 8px; font-size: 11px; }
.nav-link span { display: none; }
.side-footer { display: none; }
.workspace { width: 100%; }
.hero { min-height: auto; gap: 25px; padding: 48px 20px 50px; }
.hero h1 { font-size: clamp(39px, 12vw, 56px); }
.hero-orbit { width: 220px; }
.orbit-core { width: 90px; height: 90px; }
.orbit-core span { font-size: 29px; }
.main-heading { display: block; padding: 40px 20px 28px; }
.pattern-badge { margin-top: 22px; }
.component-section { padding: 0 20px 56px; }
.section-topline { margin-bottom: 14px; }
.section-intro h2 { font-size: 22px; }
.section-intro p { font-size: 12px; }
.demo-card-header { padding: 0 14px; }
.demo-hint { font-size: 10px; }
.demo-card-header .demo-label { font-size: 10px; }
.menu-demo, .combobox-demo { min-height: 165px; padding: 22px 16px; }
.menu-popover { top: 65px; left: 16px; right: 16px; min-width: 0; }
.menu-result { max-width: 100%; }
.tabs-demo { padding: 0 12px 20px; }
.tab-list { gap: 19px; overflow-x: auto; }
.tab { flex: 0 0 auto; padding-right: 1px; }
.stat-row { grid-template-columns: 1fr; gap: 8px; }
.stat { display: flex; align-items: center; justify-content: space-between; padding: 12px; }
.stat strong { margin: 0 0 0 11px; }
.panel-prompt { padding: 12px; }
.panel-chart { height: 175px; padding: 14px 14px 8px; }
.chart-bars { margin-left: 14px; margin-right: 14px; }
.mini-stats { gap: 10px; flex-wrap: wrap; }
.combobox-list, .combo-list { position: fixed; top: auto; bottom: 10px; left: 10px; right: 10px; max-height: min(50vh, 260px); }
.combo-status { margin-left: 2px; }
.accordion { padding: 3px 14px 14px; }
.accordion-content { padding-left: 3px; }
.closing-strip { display: block; margin: 0 20px 48px; padding: 26px 20px; }
.closing-strip .button { margin-top: 20px; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { scroll-behavior: auto !important; animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; }
}
</style>
</head>
<body>
<a class="skip-link" href="#main-content">Skip to main content</a>
<div class="shell">
<aside class="sidebar" aria-label="Component navigation">
<div class="brand">
<span class="brand-mark" aria-hidden="true"><span></span><span></span></span>
<span>Element Atlas</span>
</div>
<div class="sidebar-meta">
<span class="eyebrow">Accessible UI kit</span>
<span class="version">v1.0 · vanilla</span>
</div>
<nav class="side-nav" aria-label="Components">
<p class="nav-label">Components</p>
<a class="nav-link" href="#component-modal">Modal dialog <span>↗</span></a>
<a class="nav-link" href="#dropdown-menu">Dropdown menu <span>↗</span></a>
<a class="nav-link" href="#tabs">Tabs <span>↗</span></a>
<a class="nav-link" href="#combobox">Combobox <span>↗</span></a>
<a class="nav-link" href="#accordion">Accordion <span>↗</span></a>
</nav>
<div class="side-footer">
<div class="health"><span class="health-dot" aria-hidden="true"></span> Keyboard ready</div>
<p>Every control follows the ARIA APG pattern.</p>
</div>
</aside>
<div class="workspace">
<header class="hero">
<div>
<div class="hero-kicker"><span class="pulse" aria-hidden="true"></span> Interaction patterns, responsibly built</div>
<h1>Interfaces that feel natural <em>to everyone.</em></h1>
<p class="hero-copy">A living design-system showcase for dialogs, menus, tabs, comboboxes, and accordions. Every example is built to be explored with a keyboard or assistive technology.</p>
<div class="hero-actions">
<a class="text-link" href="#component-modal">Start with the modal <span aria-hidden="true">→</span></a>
<span class="hero-note"><span class="kbd">1</span><span class="kbd">2</span><span class="kbd">3</span> patterns to explore</span>
</div>
</div>
<div class="hero-orbit" aria-hidden="true">
<div class="orbit-ring ring-one"></div><div class="orbit-ring ring-two"></div>
<div class="orbit-core"><span>◈</span><small>focus<br>first</small></div>
</div>
</header>
<main id="main-content" tabindex="-1">
<div class="main-heading"><div><span class="eyebrow">Component reference</span><p class="intro">Five core patterns. One shared standard for clear, dependable interaction.</p></div><div class="pattern-badge"><span class="badge-check" aria-hidden="true">✓</span> APG aligned</div></div>
<section id="component-modal" class="component-section" aria-labelledby="modal-dialog-heading">
<div class="section-topline"><span class="section-number">01</span><span class="tag tag-outline">Overlay</span></div>
<div class="section-intro"><h2 id="modal-dialog-heading">Modal dialog</h2><p>A contained decision with a clear next step. Focus is trapped, the background goes inert, and Escape returns you to where you began.</p></div>
<div class="demo-card">
<div class="demo-card-header"><span class="demo-dot" aria-hidden="true"></span><span class="demo-label">Live example</span><span class="demo-hint"><span class="kbd">Esc</span> to dismiss</span></div>
<div class="modal-demo"><div><span class="demo-label-small">Invite a collaborator</span><p class="demo-copy">Give your teammate access to the shared workspace.</p></div><button id="open-modal" class="button button-primary" type="button">Invite teammate <span aria-hidden="true">↗</span></button></div>
<div class="dialog-backdrop" id="modal-backdrop" hidden=""><div class="modal-dialog" id="modal-dialog" role="dialog" aria-modal="true" aria-labelledby="modal-title" aria-describedby="modal-description" data-dialog="">
<div class="dialog-topline"><span class="dialog-icon" aria-hidden="true">✦</span><span class="eyebrow">Workspace access</span></div>
<h3 id="modal-title">Invite a collaborator</h3>
<p id="modal-description">Choose an email to send an invitation. Your teammate can join with a single click.</p>
<label class="field-label" for="modal-email">Work email</label>
<input id="modal-email" class="text-input" type="email" placeholder="name@company.com" autocomplete="email">
<div class="dialog-actions"><button class="button button-secondary" type="button" data-modal-close="">Cancel</button><button class="button button-primary" type="button" data-modal-confirm="">Send invitation</button></div>
</div></div>
</div>
<p class="contract-caption"><span class="caption-icon" aria-hidden="true">⌘</span> <strong>Keyboard contract</strong> · Tab cycles inside · Esc closes · focus returns to trigger</p>
</section>
<section id="dropdown-menu" class="component-section" aria-labelledby="dropdown-menu-heading">
<div class="section-topline"><span class="section-number">02</span><span class="tag tag-outline">Navigation</span></div>
<div class="section-intro"><h2 id="dropdown-menu-heading">Dropdown menu</h2><p>A compact command list with full arrow-key support. The button reflects its expanded state, and selection performs a visible action.</p></div>
<div class="demo-card">
<div class="demo-card-header"><span class="demo-dot" aria-hidden="true"></span><span class="demo-label">Live example</span><span class="demo-hint"><span class="kbd">↓</span> opens · <span class="kbd">Esc</span> closes</span></div>
<div class="menu-demo">
<button id="menu-button" class="button button-secondary" type="button" aria-haspopup="menu" aria-expanded="false" aria-controls="menu-items"><span class="menu-glyph" aria-hidden="true">☰</span> Choose a view <span class="chevron" aria-hidden="true">⌄</span></button>
<ul id="menu-items" class="menu-popover" role="menu" aria-label="Choose a view" hidden="">
<li role="none"><button id="menu-item-dashboard" role="menuitem" type="button" data-menu-value="dashboard">Dashboard <span class="menu-item-symbol" aria-hidden="true">◫</span></button></li>
<li role="none"><button id="menu-item-reports" role="menuitem" type="button" data-menu-value="reports">Reports <span class="menu-item-symbol" aria-hidden="true">▤</span></button></li>
<li role="none"><button id="menu-item-activity" role="menuitem" type="button" data-menu-value="activity">Activity <span class="menu-item-symbol" aria-hidden="true">◌</span></button></li>
<li role="none"><button id="menu-item-settings" role="menuitem" type="button" data-menu-value="settings">Settings <span class="menu-item-symbol" aria-hidden="true">⚙</span></button></li>
</ul>
<div class="menu-result" id="menu-result" aria-live="polite"><span class="result-label">Selected view</span><strong id="menu-result-value">Dashboard</strong></div>
</div>
</div>
<p class="contract-caption"><span class="caption-icon" aria-hidden="true">⌘</span> <strong>Keyboard contract</strong> · ↓ opens · ↑ ↓ navigate · Home / End jump · Esc closes</p>
</section>
<section id="tabs" class="component-section" aria-labelledby="tabs-heading">
<div class="section-topline"><span class="section-number">03</span><span class="tag tag-outline">Navigation</span></div>
<div class="section-intro"><h2 id="tabs-heading">Tabs</h2><p>Roving tabindex keeps one tab in the tab order. Arrow keys move the selection while hidden panels stay truly hidden.</p></div>
<div class="demo-card">
<div class="demo-card-header"><span class="demo-dot" aria-hidden="true"></span><span class="demo-label">Live example</span><span class="demo-hint"><span class="kbd">←</span> <span class="kbd">→</span> switch panels</span></div>
<div class="tabs-demo">
<div class="tab-list" role="tablist" aria-label="Project views" id="project-tabs">
<button class="tab is-active" role="tab" id="tab-overview" aria-selected="true" aria-controls="panel-overview" tabindex="0" type="button">Overview <span class="tab-index" aria-hidden="true">01</span></button>
<button class="tab" role="tab" id="tab-analytics" aria-selected="false" aria-controls="panel-analytics" tabindex="-1" type="button">Analytics <span class="tab-index" aria-hidden="true">02</span></button>
<button class="tab" role="tab" id="tab-activity" aria-selected="false" aria-controls="panel-activity" tabindex="-1" type="button">Activity <span class="tab-index" aria-hidden="true">03</span></button>
</div>
<div id="panel-overview" role="tabpanel" aria-labelledby="tab-overview" tabindex="0"><div class="panel-content"><div class="stat-row"><div class="stat"><span class="stat-label">Active members</span><strong>1,284</strong><span class="stat-trend up">↗ 12%</span></div><div class="stat"><span class="stat-label">Tasks completed</span><strong>86</strong><span class="stat-trend up">↗ 8%</span></div><div class="stat"><span class="stat-label">On track</span><strong>94%</strong><span class="stat-trend down">↓ 2%</span></div></div><div class="panel-prompt"><span class="prompt-icon" aria-hidden="true">✦</span><div><strong>Good morning, Maya.</strong><p>Your sprint is moving in the right direction. Review the latest signals before your next standup.</p></div></div></div></div>
<div id="panel-analytics" role="tabpanel" aria-labelledby="tab-analytics" tabindex="0" hidden=""><div class="panel-content"><div class="panel-chart"><div class="chart-bars"><span style="--bar-h: 38%"></span><span style="--bar-h: 62%"></span><span style="--bar-h: 48%"></span><span style="--bar-h: 82%"></span><span style="--bar-h: 66%"></span><span style="--bar-h: 94%"></span><span style="--bar-h: 74%"></span></div><div class="chart-caption">Weekly signups <span>·</span> last 7 days</div></div><div class="mini-stats"><span>Conversion <strong>32.4%</strong></span><span>Avg. session <strong>4m 18s</strong></span><span>Bounce <strong>18.2%</strong></span></div></div></div>
<div id="panel-activity" role="tabpanel" aria-labelledby="tab-activity" tabindex="0" hidden=""><div class="panel-content"><div class="activity-list"><div class="activity-row"><span class="activity-avatar amber">JL</span><div><strong>Jules Lane</strong><p>Commented on “Onboarding flow” <time>2m ago</time></p></div></div><div class="activity-row"><span class="activity-avatar blue">AK</span><div><strong>Alex Kim</strong><p>Completed 3 tasks in the release checklist <time>18m ago</time></p></div></div><div class="activity-row"><span class="activity-avatar green">MT</span><div><strong>Marisol Torres</strong><p>Added 4 new comments to the Q3 plan <time>1h ago</time></p></div></div><div class="activity-row"><span class="activity-avatar coral">RS</span><div><strong>Ray Sun</strong><p>Moved “Empty state” to In review <time>3h ago</time></p></div></div></div></div>
</div>
</div>
<p class="contract-caption"><span class="caption-icon" aria-hidden="true">⌘</span> <strong>Keyboard contract</strong> · ← → switch tabs · one tab in tab order · panels reveal on selection</p>
</div></section>
<section id="combobox" class="component-section" aria-labelledby="combobox-heading">
<div class="section-topline"><span class="section-number">04</span><span class="tag tag-outline">Input</span></div>
<div class="section-intro"><h2 id="combobox-heading">Combobox</h2><p>Type to filter a large option set. The highlighted option is announced through <code>aria-activedescendant</code> and remains out of the tab order.</p></div>
<div class="demo-card">
<div class="demo-card-header"><span class="demo-dot" aria-hidden="true"></span><span class="demo-label">Live example</span><span class="demo-hint"><span class="kbd">↑</span> <span class="kbd">↓</span> filter · <span class="kbd">Enter</span> select</span></div>
<div class="combobox-demo"><label class="field-label" for="city-combobox">Select a city</label><div class="combobox-shell"><input id="city-combobox" class="text-input" type="text" role="combobox" aria-expanded="false" aria-controls="city-options" aria-activedescendant="" autocomplete="off" placeholder="Search 20 cities"><span class="input-glyph" aria-hidden="true">⌕</span></div><ul id="city-options" class="combo-list" role="listbox" aria-label="Cities" hidden=""></ul><p id="combo-status" class="combo-status" aria-live="polite"><span class="status-dot" aria-hidden="true"></span><span id="combo-status-text">Choose a destination</span></p></div>
</div>
<p class="contract-caption"><span class="caption-icon" aria-hidden="true">⌘</span> <strong>Keyboard contract</strong> · type to filter · ↑ ↓ highlight · Enter selects · Esc reverts</p>
</section>
<section id="accordion" class="component-section" aria-labelledby="accordion-heading">
<div class="section-topline"><span class="section-number">05</span><span class="tag tag-outline">Content</span></div>
<div class="section-intro"><h2 id="accordion-heading">Accordion</h2><p>Multiple regions can stay open at once. Height animates only while content is visible, then the region is hidden for assistive technology.</p></div>
<div class="demo-card accordion-demo-card">
<div class="demo-card-header"><span class="demo-dot" aria-hidden="true"></span><span class="demo-label">Live example</span><span class="demo-hint">Enter / Space toggles a region</span></div>
<div class="accordion" id="principles-accordion">
<div class="accordion-item"><h3 class="accordion-heading"><button id="accordion-button-1" class="accordion-button" type="button" aria-expanded="true" aria-controls="accordion-panel-1"><span class="accordion-title">What makes this accessible?</span><span class="accordion-icon" aria-hidden="true">−</span></button></h3><div id="accordion-panel-1" class="accordion-panel" role="region" aria-labelledby="accordion-button-1"><div class="accordion-content"><p>Every example uses native controls where possible, semantic structure where needed, and explicit relationships between labels, controls, and regions. State changes are reflected in the DOM and announced.</p></div></div></div>
<div class="accordion-item"><h3 class="accordion-heading"><button id="accordion-button-2" class="accordion-button" type="button" aria-expanded="false" aria-controls="accordion-panel-2"><span class="accordion-title">How is focus managed?</span><span class="accordion-icon" aria-hidden="true">+</span></button></h3><div id="accordion-panel-2" class="accordion-panel" role="region" aria-labelledby="accordion-button-2"><div class="accordion-content"><p>Focus is intentional. Dialogs trap focus and return it, menus close predictably, and only the active tab sits in the tab order. A visible focus ring appears on every interactive element.</p></div></div></div>
<div class="accordion-item"><h3 class="accordion-heading"><button id="accordion-button-3" class="accordion-button" type="button" aria-expanded="false" aria-controls="accordion-panel-3"><span class="accordion-title">What happens at 200% zoom?</span><span class="accordion-icon" aria-hidden="true">+</span></button></h3><div id="accordion-panel-3" class="accordion-panel" role="region" aria-labelledby="accordion-button-3"><div class="accordion-content"><p>The layout reflows into a single column, controls retain generous hit targets, and menus and panels never rely on visual placement alone. Content remains readable and operable at any supported browser zoom.</p></div></div></div>
<div class="accordion-item"><h3 class="accordion-heading"><button id="accordion-button-4" class="accordion-button" type="button" aria-expanded="false" aria-controls="accordion-panel-4"><span class="accordion-title">Can the motion be reduced?</span><span class="accordion-icon" aria-hidden="true">+</span></button></h3><div id="accordion-panel-4" class="accordion-panel" role="region" aria-labelledby="accordion-button-4"><div class="accordion-content"><p>Yes. The showcase respects prefers-reduced-motion, shortening transitions and keeping every state change immediate without sacrificing visibility or accessibility.</p></div></div></div>
</div>
</div>
<p class="contract-caption"><span class="caption-icon" aria-hidden="true">⌘</span> <strong>Keyboard contract</strong> · Enter / Space opens and closes · multiple regions may remain open</p>
</section>
<section class="closing-strip" aria-labelledby="closing-heading"><div><span class="eyebrow">Built for the long run</span><h2 id="closing-heading">Clear patterns make better products.</h2><p>Use these examples as a starting point for your own interface. The smallest details are where trust is built.</p></div><a class="button button-primary" href="#component-modal">Explore the pattern set <span aria-hidden="true">→</span></a></section>
</main>
</div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./accordion":"src/accordion.ts","./combobox":"src/combobox.ts","./dropdown":"src/dropdown.ts","./modal":"src/modal.ts","./tabs":"src/tabs.ts"},"src/modal.ts":{"./utils":"src/utils.ts"},"src/dropdown.ts":{"./utils":"src/utils.ts"},"src/tabs.ts":{"./utils":"src/utils.ts"},"src/combobox.ts":{"./utils":"src/utils.ts"},"src/accordion.ts":{"./utils":"src/utils.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 });
exports.start = start;
const accordion_1 = require("./accordion");
const combobox_1 = require("./combobox");
const dropdown_1 = require("./dropdown");
const modal_1 = require("./modal");
const tabs_1 = require("./tabs");
function start() {
(0, modal_1.createModal)();
(0, dropdown_1.createDropdown)();
(0, tabs_1.createTabs)();
(0, combobox_1.createCombobox)();
(0, accordion_1.createAccordion)();
}
start();
};
// ── module: src/utils.ts ──
__mods["src/utils.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.COMPONENTS = void 0;
exports.querySelectorOrThrow = querySelectorOrThrow;
exports.getFocusable = getFocusable;
exports.closeOverlay = closeOverlay;
exports.COMPONENTS = {
modal: { open: "open", close: "close" },
};
function querySelectorOrThrow(scope, selector) {
const found = scope.querySelector(selector);
if (!found)
throw new Error(`Missing ${selector}`);
return found;
}
function getFocusable(scope) {
return Array.from(scope.querySelectorAll('a[href], area[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]), [contenteditable="true"]')).filter((element) => !element.hasAttribute("disabled") && element.offsetParent !== null);
}
function closeOverlay(root, restoreFocus = () => undefined) {
root.hidden = true;
document.body.classList.remove("modal-open");
restoreFocus();
}
};
// ── module: src/modal.ts ──
__mods["src/modal.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createModal = createModal;
const utils_1 = require("./utils");
function createModal() {
const button = (0, utils_1.querySelectorOrThrow)(document, "#open-modal");
const backdrop = (0, utils_1.querySelectorOrThrow)(document, "#modal-backdrop");
const dialog = (0, utils_1.querySelectorOrThrow)(backdrop, "[data-dialog]");
const email = (0, utils_1.querySelectorOrThrow)(dialog, "#modal-email");
const closeButtons = Array.from(dialog.querySelectorAll("[data-modal-close]"));
const confirm = (0, utils_1.querySelectorOrThrow)(dialog, "[data-modal-confirm]");
function openDialog() {
const previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
backdrop.dataset.previousFocus = previousFocus?.id ?? "body";
backdrop.hidden = false;
document.body.classList.add("modal-open");
const sections = Array.from(document.querySelectorAll(".sidebar, .hero, main .component-section"));
sections.forEach((section) => section.setAttribute("inert", ""));
email.focus();
}
function closeDialog() {
backdrop.hidden = true;
document.body.classList.remove("modal-open");
Array.from(document.querySelectorAll("[inert]")).forEach((element) => element.removeAttribute("inert"));
const previousId = backdrop.dataset.previousFocus;
const previousFocus = previousId ? document.getElementById(previousId) : button;
(previousFocus instanceof HTMLElement ? previousFocus : button).focus();
backdrop.removeAttribute("data-previous-focus");
}
button.addEventListener("click", () => {
backdrop.hidden ? openDialog() : closeDialog();
});
backdrop.addEventListener("click", (event) => {
if (event.target === backdrop)
closeDialog();
});
backdrop.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
event.preventDefault();
closeDialog();
return;
}
if (event.key !== "Tab")
return;
const focusables = Array.from(dialog.querySelectorAll('button:not([disabled]), input:not([disabled]), [href], [tabindex]:not([tabindex="-1"])')).filter((element) => element.getClientRects().length > 0);
if (!focusables.length)
return;
const first = focusables[0];
const last = focusables[focusables.length - 1];
if (event.shiftKey && document.activeElement === first) {
event.preventDefault();
last.focus();
}
else if (!event.shiftKey && document.activeElement === last) {
event.preventDefault();
first.focus();
}
});
closeButtons.forEach((item) => item.addEventListener("click", () => closeDialog()));
confirm.addEventListener("click", () => closeDialog());
return closeDialog;
}
};
// ── module: src/dropdown.ts ──
__mods["src/dropdown.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createDropdown = createDropdown;
const utils_1 = require("./utils");
function createDropdown() {
const button = (0, utils_1.querySelectorOrThrow)(document, "#menu-button");
const menu = (0, utils_1.querySelectorOrThrow)(document, "#menu-items");
const resultValue = (0, utils_1.querySelectorOrThrow)(document, "#menu-result-value");
const items = Array.from(menu.querySelectorAll("[role='menuitem']"));
let current = 0;
function setOpen(open, initialIndex = -1) {
menu.hidden = !open;
button.setAttribute("aria-expanded", String(open));
if (open && initialIndex >= 0)
selectItem(initialIndex, false);
}
function selectItem(index, closeMenu = true) {
current = (index + items.length) % items.length;
items.forEach((item, itemIndex) => {
item.removeAttribute("aria-current");
if (itemIndex === current)
item.setAttribute("aria-current", "true");
});
const label = items[current].dataset.menuValue?.replace("-", " ");
if (label)
resultValue.textContent = label.replace(/\b\w/g, (character) => character.toUpperCase());
if (closeMenu) {
setOpen(false);
button.focus();
}
}
button.addEventListener("click", () => {
if (menu.hidden)
setOpen(true, 0);
else
setOpen(false);
});
button.addEventListener("keydown", (event) => {
if (event.key === "ArrowDown") {
event.preventDefault();
setOpen(true, 0);
}
else if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
if (menu.hidden)
setOpen(true, 0);
else
selectItem(current, false);
}
});
menu.addEventListener("click", (event) => {
const item = event.target.closest("[role='menuitem']");
if (item)
selectItem(items.indexOf(item), true);
});
menu.addEventListener("keydown", (event) => {
if (event.key === "ArrowDown") {
event.preventDefault();
selectItem(current + 1, false);
}
else if (event.key === "ArrowUp") {
event.preventDefault();
selectItem(current - 1, false);
}
else if (event.key === "Home") {
event.preventDefault();
selectItem(0, false);
}
else if (event.key === "End") {
event.preventDefault();
selectItem(items.length - 1, false);
}
else if (event.key === "Escape") {
event.preventDefault();
setOpen(false);
button.focus();
}
else if (event.key === "Tab") {
setOpen(false);
}
});
return () => setOpen(false);
}
};
// ── module: src/tabs.ts ──
__mods["src/tabs.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createTabs = createTabs;
const utils_1 = require("./utils");
function createTabs() {
const tablist = (0, utils_1.querySelectorOrThrow)(document, "#project-tabs");
const tabs = Array.from(tablist.querySelectorAll("[role='tab']"));
const panels = tabs.map((tab) => (0, utils_1.querySelectorOrThrow)(document, `#${tab.getAttribute("aria-controls")}`));
function activate(index, focus = true) {
tabs.forEach((tab, tabIndex) => {
const selected = tabIndex === index;
tab.setAttribute("aria-selected", String(selected));
tab.tabIndex = selected ? 0 : -1;
tab.classList.toggle("is-active", selected);
panels[tabIndex].hidden = !selected;
});
if (focus)
tabs[index].focus();
}
tabs.forEach((tab, index) => {
tab.addEventListener("click", () => activate(index));
tab.addEventListener("keydown", (event) => {
let nextIndex;
if (event.key === "ArrowRight")
nextIndex = (index + 1) % tabs.length;
else if (event.key === "ArrowLeft")
nextIndex = (index - 1 + tabs.length) % tabs.length;
else if (event.key === "Home")
nextIndex = 0;
else if (event.key === "End")
nextIndex = tabs.length - 1;
if (nextIndex !== undefined) {
event.preventDefault();
activate(nextIndex);
}
});
});
return () => activate(0, false);
}
};
// ── module: src/combobox.ts ──
__mods["src/combobox.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createCombobox = createCombobox;
const utils_1 = require("./utils");
const CITIES = [
"Amsterdam", "Auckland", "Bangkok", "Barcelona", "Berlin", "Boston", "Budapest",
"Copenhagen", "Dublin", "Florence", "Hong Kong", "Lisbon", "London", "Los Angeles",
"Melbourne", "Mexico City", "Montreal", "New York", "Osaka", "Porto", "San Francisco",
"Singapore", "Stockholm", "Sydney", "Tokyo", "Toronto", "Vancouver", "Vienna", "Zurich",
];
function createCombobox() {
const input = (0, utils_1.querySelectorOrThrow)(document, "#city-combobox");
const listbox = (0, utils_1.querySelectorOrThrow)(document, "#city-options");
const status = (0, utils_1.querySelectorOrThrow)(document, "#combo-status-text");
let filtered = CITIES;
let activeIndex = -1;
let lastText = "";
function render(query) {
filtered = CITIES.filter((city) => city.toLowerCase().includes(query.toLowerCase()));
listbox.replaceChildren();
if (filtered.length === 0) {
const empty = document.createElement("li");
empty.className = "combo-empty";
empty.setAttribute("role", "option");
empty.id = "city-option-empty";
empty.textContent = "No cities found";
listbox.append(empty);
activeIndex = -1;
}
else {
filtered.forEach((city, index) => {
const option = document.createElement("li");
option.id = `city-option-${index}`;
option.setAttribute("role", "option");
option.setAttribute("aria-selected", index === activeIndex ? "true" : "false");
option.textContent = city;
listbox.append(option);
});
if (activeIndex < 0)
activeIndex = 0;
}
const option = listbox.querySelector(`[id='city-option-${activeIndex}']`);
option?.scrollIntoView({ block: "nearest" });
}
function openListbox(query) {
input.value = query;
lastText = query;
render(query);
input.setAttribute("aria-expanded", "true");
input.setAttribute("aria-controls", "city-options");
if (activeIndex >= 0)
input.setAttribute("aria-activedescendant", `city-option-${activeIndex}`);
}
function closeListbox() {
input.value = lastText;
listbox.hidden = true;
listbox.replaceChildren();
activeIndex = -1;
input.setAttribute("aria-expanded", "false");
input.removeAttribute("aria-controls");
input.removeAttribute("aria-activedescendant");
}
input.addEventListener("focus", () => {
lastText = input.value;
if (input.value)
openListbox(input.value);
});
input.addEventListener("input", () => {
activeIndex = -1;
openListbox(input.value);
});
input.addEventListener("keydown", (event) => {
if (input.getAttribute("aria-expanded") !== "true")
return;
if (event.key === "ArrowDown") {
event.preventDefault();
activeIndex = activeIndex < filtered.length - 1 ? activeIndex + 1 : 0;
render(input.value);
input.setAttribute("aria-activedescendant", `city-option-${activeIndex}`);
}
else if (event.key === "ArrowUp") {
event.preventDefault();
activeIndex = activeIndex > 0 ? activeIndex - 1 : filtered.length - 1;
render(input.value);
input.setAttribute("aria-activedescendant", `city-option-${activeIndex}`);
}
else if (event.key === "Enter") {
event.preventDefault();
if (activeIndex >= 0) {
input.value = filtered[activeIndex];
lastText = input.value;
status.textContent = `Selected ${filtered[activeIndex]}`;
}
closeListbox();
input.focus();
}
else if (event.key === "Escape") {
event.preventDefault();
closeListbox();
}
else if (event.key === "Home" || event.key === "End") {
event.preventDefault();
activeIndex = event.key === "Home" ? 0 : filtered.length - 1;
render(input.value);
input.setAttribute("aria-activedescendant", `city-option-${activeIndex}`);
}
});
listbox.addEventListener("click", (event) => {
const option = event.target.closest("[role='option']");
if (!option)
return;
const index = Array.from(option.parentElement?.children ?? []).indexOf(option);
if (index < 0)
return;
input.value = filtered[index];
lastText = input.value;
status.textContent = `Selected ${filtered[index]}`;
closeListbox();
input.focus();
});
input.addEventListener("blur", () => {
window.setTimeout(() => closeListbox(), 0);
});
return () => closeListbox();
}
};
// ── module: src/accordion.ts ──
__mods["src/accordion.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createAccordion = createAccordion;
function createAccordion() {
const items = Array.from(document.querySelectorAll(".accordion-item"));
function setPanel(item, panel, open) {
const button = item.querySelector(".accordion-button");
if (!button)
return;
button.setAttribute("aria-expanded", String(open));
const icon = item.querySelector(".accordion-icon");
if (icon)
icon.textContent = open ? "−" : "+";
if (open) {
panel.hidden = false;
panel.style.height = "0px";
const targetHeight = panel.scrollHeight;
panel.style.height = `${targetHeight}px`;
window.requestAnimationFrame(() => {
panel.style.height = `${panel.scrollHeight}px`;
});
return;
}
panel.hidden = true;
panel.style.height = "";
panel.removeAttribute("style");
}
function toggle(item) {
const panel = item.querySelector(".accordion-panel");
if (!panel)
return;
setPanel(item, panel, panel.hidden !== true);
}
items.forEach((item) => item.querySelector(".accordion-button")?.addEventListener("click", () => toggle(item)));
return () => undefined;
}
};
// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>