diff --git a/accordion.js b/accordion.js
new file mode 100644
index 0000000..c31d782
--- /dev/null
+++ b/accordion.js
@@ -0,0 +1,70 @@
+// Accessible, animated accordion component with an event API.
+(function () {
+ const DEFAULTS = { animate: false, duration: 200, allowMultiple: true };
+
+ class Accordion {
+ constructor(root, options = {}) {
+ this.root = root;
+ this.options = { ...DEFAULTS, ...options };
+ this.handlers = {};
+ this.items = [...root.querySelectorAll('.accordion__item')].map((item, i) => {
+ const trigger = item.querySelector('.accordion__trigger');
+ const panel = item.querySelector('.accordion__panel');
+ const id = `accordion-panel-${Accordion.nextId++}`;
+ panel.id = id;
+ trigger.setAttribute('aria-controls', id);
+ trigger.setAttribute('aria-expanded', 'false');
+ panel.hidden = true;
+ trigger.addEventListener('click', () => this.toggle(i));
+ trigger.addEventListener('keydown', (e) => this.onKey(e, i));
+ return { trigger, panel };
+ });
+ }
+
+ on(event, fn) {
+ (this.handlers[event] ||= []).push(fn);
+ return this;
+ }
+
+ emit(event, detail) {
+ (this.handlers[event] || []).forEach((fn) => fn(detail));
+ }
+
+ toggle(index) {
+ const { trigger, panel } = this.items[index];
+ const open = trigger.getAttribute('aria-expanded') !== 'true';
+ if (open && !this.options.allowMultiple) {
+ this.items.forEach((_, j) => j !== index && this.close(j));
+ }
+ trigger.setAttribute('aria-expanded', String(open));
+ if (this.options.animate) {
+ panel.style.transition = `max-height ${this.options.duration}ms ease`;
+ }
+ panel.hidden = !open;
+ this.emit(open ? 'open' : 'close', { index });
+ }
+
+ close(index) {
+ const { trigger, panel } = this.items[index];
+ trigger.setAttribute('aria-expanded', 'false');
+ panel.hidden = true;
+ }
+
+ onKey(event, index) {
+ const last = this.items.length - 1;
+ const target = { ArrowDown: index === last ? 0 : index + 1, ArrowUp: index === 0 ? last : index - 1, Home: 0, End: last }[event.key];
+ if (target === undefined) return;
+ event.preventDefault();
+ this.items[target].trigger.focus();
+ }
+ }
+ Accordion.nextId = 1;
+
+ const registry = new WeakMap();
+ document.querySelectorAll('[data-accordion]').forEach((root) => {
+ registry.set(root, new Accordion(root, { animate: root.dataset.accordionAnimate === 'true' }));
+ });
+
+ window.Accordion = Accordion;
+ window.getAccordion = (root) => registry.get(root);
+})();
diff --git a/faq.html b/faq.html
index 4b35b03..7a9e706 100644
--- a/faq.html
+++ b/faq.html
@@ -8,12 +8,21 @@
Frequently asked questions
- What should I bring?
- Water, sturdy shoes, a rain layer and snacks. We provide poles.
- Can I cancel?
- Free cancellation up to 48 hours before the hike start time.
- Are dogs allowed?
- Leashed dogs are welcome on the Lakeside and Meadow routes.
+
+
+
+
Water, sturdy shoes, a rain layer and snacks. We provide poles.
+
+
+
+
Free cancellation up to 48 hours before the hike start time.
+
+
+
+
Leashed dogs are welcome on the Lakeside and Meadow routes.
+
+
+