# FAQ

> A polished FAQ accordion with smooth open/close motion and clear hierarchy — ideal for support and product pages.

**Definition:** FAQ is a text UI animation from collections.html. It is built with HTML, CSS, JavaScript, and GSAP, ships with a live preview, and is free to copy into personal and commercial projects.

## Facts
- Category: Text
- Stack: HTML, CSS, JavaScript, and GSAP
- Added: 2026-08-02
- Canonical page: https://html.bosconvertpro.com/collection/faq
- Full-page preview: https://html.bosconvertpro.com/collection/faq/full
- Live demo: https://html.bosconvertpro.com/demos/faq/index.html
- Markdown mirror: https://html.bosconvertpro.com/collection/faq/index.md
- License: https://html.bosconvertpro.com/terms

## How to use
1. Open https://html.bosconvertpro.com/collection/faq/full to preview the animation in full screen.
2. Copy index.html, styles.css, and script.js from the code tabs on https://html.bosconvertpro.com/collection/faq.
3. Paste the files into your project and adjust timing, easing, and content as needed.

## FAQ
### What is FAQ?

FAQ is a text UI animation from collections.html. A polished FAQ accordion with smooth open/close motion and clear hierarchy — ideal for support and product pages.

### What is FAQ built with?

FAQ is built with HTML, CSS, JavaScript, and GSAP. The source for every file is shown on the animation page and can be copied straight into your project.

### How do I use FAQ in my project?

Open the animation page (https://html.bosconvertpro.com/collection/faq), preview it live, then copy index.html, styles.css, and script.js from the code tabs. Paste the files into your project and adjust timing, easing, and content as needed. A standalone demo runs at https://html.bosconvertpro.com/demos/faq/index.html.

### Is FAQ free to use?

Yes. The FAQ demo code may be used in personal and commercial projects. You may not resell or redistribute the collection itself as a competing template pack. Full terms: https://html.bosconvertpro.com/terms.

## Source — index.html
````html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FAQ Accordion</title>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap"
      rel="stylesheet"
    />
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <main class="page">
      <section class="faq" aria-labelledby="faq-heading">
        <div class="faq__layout">
          <header class="faq__intro">
            <p class="faq__eyebrow">FAQ</p>
            <h1 id="faq-heading" class="faq__heading">
              Common questions
            </h1>
            <p class="faq__lede">
              Clear answers about timelines, pricing, handoff, and how we work
              together.
            </p>
            <a class="faq__contact" href="#faq-trigger-6">
              Still have a question?
              <span>Get in touch</span>
            </a>
          </header>

          <div class="faq__content">
            <div class="faq__card">
              <div class="faq__list" data-faq data-allow-multiple="false">
                <!-- Item 1 — starts open -->
                <div class="faq__item" data-faq-item data-open="true">
                  <button
                    class="faq__trigger"
                    data-faq-trigger
                    type="button"
                    id="faq-trigger-1"
                    aria-controls="faq-panel-1"
                    aria-expanded="true"
                  >
                    <span class="faq__question"
                      >How long does a typical project take?</span
                    >
                    <span class="faq__icon-wrap" aria-hidden="true">
                      <svg
                        class="faq__icon"
                        data-faq-icon
                        viewBox="0 0 16 16"
                        fill="none"
                        focusable="false"
                      >
                        <path
                          class="faq__icon-v"
                          data-faq-icon-v
                          d="M8 1V15"
                          stroke="currentColor"
                          stroke-width="1.75"
                          stroke-linecap="round"
                        />
                        <path
                          class="faq__icon-h"
                          data-faq-icon-h
                          d="M1 8H15"
                          stroke="currentColor"
                          stroke-width="1.75"
                          stroke-linecap="round"
                        />
                      </svg>
                    </span>
                  </button>
                  <div
                    class="faq__panel"
                    data-faq-panel
                    data-stagger-text
                    id="faq-panel-1"
                    role="region"
                    aria-labelledby="faq-trigger-1"
                    aria-hidden="false"
                  >
                    <p class="faq__answer">
                      Most product engagements run six to ten weeks from kickoff
                      to handoff. Discovery and wireframes land in week two;
                      visual systems and interactive prototypes follow. You get a
                      fixed timeline before any work begins—no open-ended
                      retainers.
                    </p>
                  </div>
                </div>

                <!-- Item 2 -->
                <div class="faq__item" data-faq-item>
                  <button
                    class="faq__trigger"
                    data-faq-trigger
                    type="button"
                    id="faq-trigger-2"
                    aria-controls="faq-panel-2"
                    aria-expanded="false"
                  >
                    <span class="faq__question"
                      >Do you work with early-stage startups?</span
                    >
                    <span class="faq__icon-wrap" aria-hidden="true">
                      <svg
                        class="faq__icon"
                        data-faq-icon
                        viewBox="0 0 16 16"
                        fill="none"
                        focusable="false"
                      >
                        <path
                          class="faq__icon-v"
                          data-faq-icon-v
                          d="M8 1V15"
                          stroke="currentColor"
                          stroke-width="1.75"
                          stroke-linecap="round"
                        />
                        <path
                          class="faq__icon-h"
                          data-faq-icon-h
                          d="M1 8H15"
                          stroke="currentColor"
                          stroke-width="1.75"
                          stroke-linecap="round"
                        />
                      </svg>
                    </span>
                  </button>
                  <div
                    class="faq__panel"
                    data-faq-panel
                    data-stagger-text
                    id="faq-panel-2"
                    role="region"
                    aria-labelledby="faq-trigger-2"
                    aria-hidden="true"
                  >
                    <p class="faq__answer">
                      Yes—roughly half of our clients are pre-Series A. We scope
                      lean sprints around a single high-impact surface: landing
                      page, onboarding, or core dashboard. You ship something
                      real without a bloated retainer or a six-month roadmap.
                    </p>
                  </div>
                </div>

                <!-- Item 3 -->
                <div class="faq__item" data-faq-item>
                  <button
                    class="faq__trigger"
                    data-faq-trigger
                    type="button"
                    id="faq-trigger-3"
                    aria-controls="faq-panel-3"
                    aria-expanded="false"
                  >
                    <span class="faq__question"
                      >What tools do you deliver files in?</span
                    >
                    <span class="faq__icon-wrap" aria-hidden="true">
                      <svg
                        class="faq__icon"
                        data-faq-icon
                        viewBox="0 0 16 16"
                        fill="none"
                        focusable="false"
                      >
                        <path
                          class="faq__icon-v"
                          data-faq-icon-v
                          d="M8 1V15"
                          stroke="currentColor"
                          stroke-width="1.75"
                          stroke-linecap="round"
                        />
                        <path
                          class="faq__icon-h"
                          data-faq-icon-h
                          d="M1 8H15"
                          stroke="currentColor"
                          stroke-width="1.75"
                          stroke-linecap="round"
                        />
                      </svg>
                    </span>
                  </button>
                  <div
                    class="faq__panel"
                    data-faq-panel
                    data-stagger-text
                    id="faq-panel-3"
                    role="region"
                    aria-labelledby="faq-trigger-3"
                    aria-hidden="true"
                  >
                    <p class="faq__answer">
                      Design systems live in Figma with documented components,
                      tokens, and variants. Engineering receives a clean handoff
                      package—specs, assets, and a short Loom walkthrough of key
                      flows. Code-ready tokens export to CSS or Tailwind on
                      request.
                    </p>
                  </div>
                </div>

                <!-- Item 4 -->
                <div class="faq__item" data-faq-item>
                  <button
                    class="faq__trigger"
                    data-faq-trigger
                    type="button"
                    id="faq-trigger-4"
                    aria-controls="faq-panel-4"
                    aria-expanded="false"
                  >
                    <span class="faq__question"
                      >How does pricing work?</span
                    >
                    <span class="faq__icon-wrap" aria-hidden="true">
                      <svg
                        class="faq__icon"
                        data-faq-icon
                        viewBox="0 0 16 16"
                        fill="none"
                        focusable="false"
                      >
                        <path
                          class="faq__icon-v"
                          data-faq-icon-v
                          d="M8 1V15"
                          stroke="currentColor"
                          stroke-width="1.75"
                          stroke-linecap="round"
                        />
                        <path
                          class="faq__icon-h"
                          data-faq-icon-h
                          d="M1 8H15"
                          stroke="currentColor"
                          stroke-width="1.75"
                          stroke-linecap="round"
                        />
                      </svg>
                    </span>
                  </button>
                  <div
                    class="faq__panel"
                    data-faq-panel
                    data-stagger-text
                    id="faq-panel-4"
                    role="region"
                    aria-labelledby="faq-trigger-4"
                    aria-hidden="true"
                  >
                    <p class="faq__answer">
                      Projects are scoped as fixed packages so you know the
                      investment upfront. Sprints start around $12k; full product
                      systems range higher depending on complexity. We share a
                      written proposal within three business days—no surprise
                      invoices mid-build.
                    </p>
                  </div>
                </div>

                <!-- Item 5 -->
                <div class="faq__item" data-faq-item>
                  <button
                    class="faq__trigger"
                    data-faq-trigger
                    type="button"
                    id="faq-trigger-5"
                    aria-controls="faq-panel-5"
                    aria-expanded="false"
                  >
                    <span class="faq__question"
                      >Can you support after launch?</span
                    >
                    <span class="faq__icon-wrap" aria-hidden="true">
                      <svg
                        class="faq__icon"
                        data-faq-icon
                        viewBox="0 0 16 16"
                        fill="none"
                        focusable="false"
                      >
                        <path
                          class="faq__icon-v"
                          data-faq-icon-v
                          d="M8 1V15"
                          stroke="currentColor"
                          stroke-width="1.75"
                          stroke-linecap="round"
                        />
                        <path
                          class="faq__icon-h"
                          data-faq-icon-h
                          d="M1 8H15"
                          stroke="currentColor"
                          stroke-width="1.75"
                          stroke-linecap="round"
                        />
                      </svg>
                    </span>
                  </button>
                  <div
                    class="faq__panel"
                    data-faq-panel
                    data-stagger-text
                    id="faq-panel-5"
                    role="region"
                    aria-labelledby="faq-trigger-5"
                    aria-hidden="true"
                  >
                    <p class="faq__answer">
                      Every project includes two weeks of post-launch polish.
                      After that you can move to a monthly care plan for
                      iterations, A/B variants, and design reviews—or book us per
                      sprint when you need a focused push.
                    </p>
                  </div>
                </div>

                <!-- Item 6 -->
                <div class="faq__item faq__item--last" data-faq-item>
                  <button
                    class="faq__trigger"
                    data-faq-trigger
                    type="button"
                    id="faq-trigger-6"
                    aria-controls="faq-panel-6"
                    aria-expanded="false"
                  >
                    <span class="faq__question">How do we get started?</span>
                    <span class="faq__icon-wrap" aria-hidden="true">
                      <svg
                        class="faq__icon"
                        data-faq-icon
                        viewBox="0 0 16 16"
                        fill="none"
                        focusable="false"
                      >
                        <path
                          class="faq__icon-v"
                          data-faq-icon-v
                          d="M8 1V15"
                          stroke="currentColor"
                          stroke-width="1.75"
                          stroke-linecap="round"
                        />
                        <path
                          class="faq__icon-h"
                          data-faq-icon-h
                          d="M1 8H15"
                          stroke="currentColor"
                          stroke-width="1.75"
                          stroke-linecap="round"
                        />
                      </svg>
                    </span>
                  </button>
                  <div
                    class="faq__panel"
                    data-faq-panel
                    data-stagger-text
                    id="faq-panel-6"
                    role="region"
                    aria-labelledby="faq-trigger-6"
                    aria-hidden="true"
                  >
                    <p class="faq__answer">
                      Book a thirty-minute intro call. If we are a fit, you
                      receive a written proposal within three business days—
                      scope, timeline, investment, and a suggested kickoff date.
                      No long forms, no pressure pitch.
                    </p>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </section>
    </main>

    <script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/gsap.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/SplitText.min.js"></script>
    <script src="script.js"></script>
  </body>
</html>
````

## Source — styles.css
````css
/* ============================================
   FAQ Accordion — Minimal modern UI
   ============================================ */

*,
*::before,
*::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

:root {
  --faq-bg: #f7f7f5;
  --faq-surface: #ffffff;
  --faq-border: #e8e8e4;
  --faq-border-hover: #d4d4d0;
  --faq-ink: #171717;
  --faq-ink-soft: #525252;
  --faq-ink-muted: #737373;
  --faq-icon-bg: #f0f0ec;
  --faq-icon-bg-active: #171717;
  --faq-icon-fg: #525252;
  --faq-icon-fg-active: #fafafa;
  --faq-radius: 16px;
  --faq-ease: cubic-bezier(0.16, 1, 0.3, 1);
}

html,
body {
  height: 100%;
}

body {
  font-family: "Plus Jakarta Sans", -apple-system, BlinkMacSystemFont, "Segoe UI",
    Roboto, "Helvetica Neue", sans-serif;
  background: var(--faq-bg);
  color: var(--faq-ink);
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* ----------------------------------------
   Page shell
   ---------------------------------------- */

.page {
  width: 100%;
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--faq-bg);
}

/* ----------------------------------------
   FAQ layout — top-aligned, no vertical center
   ---------------------------------------- */

.faq {
  width: 100%;
  max-width: 1080px;
  margin: 0 auto;
  padding: 4.5rem 1.75rem 6rem;
}

.faq__layout {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 2.5rem 3rem;
  width: 100%;
  align-items: start;
}

/* ----------------------------------------
   Intro column
   ---------------------------------------- */

.faq__intro {
  grid-column: 1 / 5;
  position: sticky;
  top: 2rem;
  align-self: start;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.faq__eyebrow {
  display: inline-flex;
  align-items: center;
  width: fit-content;
  margin: 0;
  padding: 0;
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  text-transform: none;
  color: var(--faq-ink-muted);
  background: none;
  border: none;
  border-radius: 0;
}

.faq__eyebrow::before {
  display: none;
}

.faq__heading {
  margin: 0;
  font-size: clamp(1.85rem, 3.5vw, 2.5rem);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.03em;
  color: var(--faq-ink);
}

.faq__lede {
  margin: 0;
  max-width: 28ch;
  font-size: 0.9375rem;
  font-weight: 400;
  line-height: 1.65;
  color: var(--faq-ink-muted);
}

.faq__contact {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.35rem;
  width: fit-content;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--faq-ink-muted);
  text-decoration: none;
  transition: color 0.3s var(--faq-ease);
}

.faq__contact span {
  color: var(--faq-ink);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: var(--faq-border-hover);
  transition:
    color 0.3s var(--faq-ease),
    text-decoration-color 0.3s var(--faq-ease);
}

.faq__contact:hover {
  color: var(--faq-ink-soft);
}

.faq__contact:hover span {
  color: var(--faq-ink);
  text-decoration-color: var(--faq-ink);
}

/* ----------------------------------------
   Content
   ---------------------------------------- */

.faq__content {
  grid-column: 5 / 13;
}

.faq__card {
  position: relative;
  background: var(--faq-surface);
  border: 1px solid var(--faq-border);
  border-radius: var(--faq-radius);
  padding: 0.25rem 1.35rem;
  box-shadow: 0 1px 2px rgba(23, 23, 23, 0.04);
  /* Stop browser scroll-anchoring from nudging the card while height animates */
  overflow-anchor: none;
}

.faq__card::before {
  display: none;
}

/* ----------------------------------------
   Accordion list
   ---------------------------------------- */

.faq__list {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
}

.faq__item {
  position: relative;
  border-bottom: 1px solid var(--faq-border);
  overflow-anchor: none;
}

.faq__item--last {
  border-bottom: none;
}

/* ----------------------------------------
   Trigger
   ---------------------------------------- */

.faq__trigger {
  display: flex;
  width: 100%;
  padding: 1.2rem 0.1rem;
  justify-content: space-between;
  align-items: center;
  gap: 1.15rem;
  background: transparent;
  border: none;
  cursor: pointer;
  text-align: left;
  color: var(--faq-ink-soft);
  font-family: inherit;
  transition: color 0.35s var(--faq-ease);
}

.faq__trigger:hover {
  color: var(--faq-ink);
}

.faq__trigger:hover .faq__icon-wrap {
  background: var(--faq-border);
  color: var(--faq-ink);
}

.faq__trigger[aria-expanded="true"] {
  color: var(--faq-ink);
}

.faq__trigger[aria-expanded="true"] .faq__icon-wrap {
  background: var(--faq-icon-bg-active);
  border-color: var(--faq-icon-bg-active);
  color: var(--faq-icon-fg-active);
}

.faq__trigger:focus {
  outline: none;
}

.faq__trigger:focus-visible {
  outline: 2px solid var(--faq-ink);
  outline-offset: 4px;
  border-radius: 6px;
}

.faq__question {
  margin: 0;
  font-size: 0.9875rem;
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: -0.012em;
  color: inherit;
}

/* ----------------------------------------
   Plus / minus icon
   ---------------------------------------- */

.faq__icon-wrap {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  flex-shrink: 0;
  border-radius: 50%;
  border: 1px solid transparent;
  background: var(--faq-icon-bg);
  color: var(--faq-icon-fg);
  transition:
    background 0.35s var(--faq-ease),
    border-color 0.35s var(--faq-ease),
    color 0.35s var(--faq-ease);
}

.faq__icon {
  width: 0.8rem;
  height: 0.8rem;
  flex-shrink: 0;
  color: inherit;
}

/* ----------------------------------------
   Panel & answer
   ---------------------------------------- */

.faq__panel {
  overflow: hidden;
  height: 0;
}

.faq__answer {
  margin: 0;
  padding: 0 2.25rem 1.25rem 0.1rem;
  max-width: 52ch;
  font-size: 0.875rem;
  font-weight: 400;
  line-height: 1.7;
  color: var(--faq-ink-muted);
}

/* SplitText line masks — transform only, no layout shift */
.faq__line {
  will-change: transform;
}

/* ----------------------------------------
   Accessibility
   ---------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .faq__trigger,
  .faq__icon,
  .faq__icon-wrap,
  .faq__panel,
  .faq__contact {
    transition: none !important;
    animation: none !important;
  }
}

@media (prefers-contrast: high) {
  .faq__item {
    border-width: 2px;
  }

  .faq__card {
    border-width: 2px;
  }

  .faq__trigger:focus-visible {
    outline-width: 3px;
  }
}

/* ----------------------------------------
   Responsive
   ---------------------------------------- */

@media (max-width: 991px) {
  .faq {
    padding: 3rem 1.5rem 4.5rem;
  }

  .faq__layout {
    grid-template-columns: 1fr;
    gap: 2rem;
  }

  .faq__intro {
    grid-column: 1 / -1;
    position: static;
  }

  .faq__heading {
    font-size: clamp(1.65rem, 5.5vw, 2.15rem);
  }

  .faq__lede {
    max-width: 42ch;
  }

  .faq__content {
    grid-column: 1 / -1;
  }
}

@media (max-width: 479px) {
  .faq {
    padding: 2.25rem 1rem 3.5rem;
  }

  .faq__card {
    padding: 0.1rem 1rem;
    border-radius: 14px;
  }

  .faq__trigger {
    padding: 1.05rem 0;
    gap: 0.85rem;
  }

  .faq__question {
    font-size: 0.9375rem;
  }

  .faq__answer {
    font-size: 0.8125rem;
    padding: 0 0.35rem 1.05rem 0;
  }

  .faq__icon-wrap {
    width: 1.6rem;
    height: 1.6rem;
  }
}
````

## Source — script.js
````javascript
/**
 * FAQ Accordion
 * Height + icon open/close (stable end handoff) + SplitText line stagger.
 * Lines are pre-split so text animation never reflows panel height mid-open.
 */

(() => {
  "use strict";

  const CONFIG = {
    duration: 0.5,
    ease: "expo.inOut",
    iconRotation: -180,
    allowMultiple: false,
    stagger: {
      duration: 0.4,
      delay: 0.08,
      ease: "expo.out",
      yPercent: 110,
      // Start text slightly after height begins (matches original feel)
      startAt: 0.12,
    },
  };

  const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)");

  function readAttr(el, name, fallback) {
    const raw = el.getAttribute(`data-${name}`);
    if (raw === null) return fallback;
    if (raw === "true") return true;
    if (raw === "false") return false;
    if (raw !== "" && !Number.isNaN(Number(raw))) return parseFloat(raw);
    return raw;
  }

  function isDisabled(el) {
    const raw = el.dataset.disable;
    if (!raw) return false;

    const map = {
      mobile: "(max-width: 479px)",
      tablet: "(max-width: 991px)",
      landscape: "(orientation: landscape) and (max-width: 767px)",
      desktop: "(min-width: 992px)",
    };

    return raw.split(",").some((token) => {
      const mq = map[token.trim()];
      return mq && window.matchMedia(mq).matches;
    });
  }

  function waitForGsap(callback, attempt = 0) {
    if (typeof gsap !== "undefined") {
      callback();
      return;
    }
    if (attempt < 50) {
      setTimeout(() => waitForGsap(callback, attempt + 1), 100);
    } else {
      console.warn("[FAQ Accordion] GSAP failed to load");
    }
  }

  function getAutoHeight(panel) {
    const current = panel.style.height;
    const overflow = panel.style.overflow;

    panel.style.overflow = "hidden";
    panel.style.height = "auto";
    const height = panel.scrollHeight;
    panel.style.height = current;
    panel.style.overflow = overflow;

    return height;
  }

  /**
   * Split once up front. Transform-only line motion never changes layout height,
   * so open/close stays jump-free.
   */
  function createLineSplit(panel) {
    if (
      !panel.hasAttribute("data-stagger-text") ||
      typeof SplitText === "undefined"
    ) {
      return null;
    }

    const answer = panel.querySelector(".faq__answer");
    if (!answer) return null;

    try {
      return SplitText.create(answer, {
        type: "lines",
        mask: "lines",
        linesClass: "faq__line",
      });
    } catch (err) {
      console.warn("[FAQ Accordion] SplitText failed", err);
      return null;
    }
  }

  function getLines(split) {
    if (!split) return null;
    const lines = split.lines;
    return lines && lines.length ? lines : null;
  }

  function createItem(itemEl, listCtx) {
    if (isDisabled(itemEl)) return null;

    const trigger = itemEl.querySelector("[data-faq-trigger]");
    const panel = itemEl.querySelector("[data-faq-panel]");
    const icon = itemEl.querySelector("[data-faq-icon]");
    const iconV = icon ? icon.querySelector("[data-faq-icon-v]") : null;

    if (!trigger || !panel) return null;

    const duration = readAttr(itemEl, "duration", CONFIG.duration);
    const ease = readAttr(itemEl, "ease", CONFIG.ease);
    const iconRotation = icon
      ? readAttr(icon, "icon-rotation", CONFIG.iconRotation)
      : CONFIG.iconRotation;

    const staggerDuration = readAttr(
      panel,
      "stagger-duration",
      CONFIG.stagger.duration
    );
    const staggerDelay = readAttr(panel, "stagger-delay", CONFIG.stagger.delay);
    const staggerEase = readAttr(panel, "stagger-ease", CONFIG.stagger.ease);
    const staggerY = readAttr(
      panel,
      "stagger-y-percent",
      CONFIG.stagger.yPercent
    );
    const staggerStart = readAttr(panel, "stagger-start", CONFIG.stagger.startAt);

    let isOpen = readAttr(itemEl, "open", false) === true;
    let tween = null;
    const split = createLineSplit(panel);
    const lines = getLines(split);

    gsap.set(panel, { height: 0, overflow: "hidden" });
    if (icon) gsap.set(icon, { rotation: 0, transformOrigin: "50% 50%" });
    if (iconV) gsap.set(iconV, { opacity: 1 });

    // Lines rest hidden; layout height is unchanged (transform only)
    if (lines) {
      gsap.set(lines, { yPercent: isOpen ? 0 : staggerY, force3D: true });
    }

    function setA11y(open) {
      trigger.setAttribute("aria-expanded", open ? "true" : "false");
      panel.setAttribute("aria-hidden", open ? "false" : "true");
    }

    function ensureAriaControls() {
      if (trigger.hasAttribute("aria-controls")) return;
      const id =
        panel.id || `faq-panel-${Math.random().toString(36).slice(2, 11)}`;
      panel.id = id;
      trigger.setAttribute("aria-controls", id);
    }

    function killTween() {
      if (tween) {
        tween.kill();
        tween = null;
      }
    }

    function open() {
      if (isOpen) return;

      if (!listCtx.allowMultiple) {
        listCtx.openItems.slice().forEach((other) => {
          if (other !== api) other.close();
        });
      }

      isOpen = true;
      if (!listCtx.openItems.includes(api)) listCtx.openItems.push(api);
      setA11y(true);

      killTween();

      const fromHeight = panel.getBoundingClientRect().height;
      // Measure with lines at rest position (yPercent 0) so height is final
      if (lines) gsap.set(lines, { yPercent: 0 });
      const toHeight = getAutoHeight(panel);
      if (lines) gsap.set(lines, { yPercent: staggerY });

      gsap.set(panel, { height: fromHeight, overflow: "hidden" });
      if (icon) gsap.set(icon, { transformOrigin: "50% 50%" });

      const targetHeight = Math.ceil(toHeight);

      tween = gsap.timeline({
        onComplete() {
          gsap.set(panel, { height: targetHeight, overflow: "hidden" });
          tween = null;
        },
      });

      tween.to(
        panel,
        {
          height: targetHeight,
          duration,
          ease,
        },
        0
      );

      if (icon) {
        tween.to(icon, { rotation: iconRotation, duration, ease }, 0);
      }
      if (iconV) {
        tween.to(
          iconV,
          { opacity: 0, duration: duration * 0.5, ease: "power2.inOut" },
          duration * 0.25
        );
      }

      if (lines) {
        tween.to(
          lines,
          {
            yPercent: 0,
            duration: staggerDuration,
            stagger: staggerDelay,
            ease: staggerEase,
            force3D: true,
          },
          staggerStart
        );
      }

      itemEl.dispatchEvent(
        new CustomEvent("faq:open", {
          detail: { item: itemEl, panel },
          bubbles: true,
        })
      );
    }

    function close() {
      if (!isOpen) return;

      isOpen = false;
      listCtx.openItems = listCtx.openItems.filter((entry) => entry !== api);
      setA11y(false);

      killTween();

      const fromHeight = panel.getBoundingClientRect().height;
      gsap.set(panel, { height: fromHeight, overflow: "hidden" });

      tween = gsap.timeline({
        onComplete() {
          gsap.set(panel, { height: 0, overflow: "hidden" });
          if (lines) gsap.set(lines, { yPercent: staggerY });
          tween = null;
        },
      });

      tween.to(
        panel,
        {
          height: 0,
          duration,
          ease,
        },
        0
      );

      if (icon) {
        tween.to(icon, { rotation: 0, duration, ease }, 0);
      }
      if (iconV) {
        tween.to(
          iconV,
          { opacity: 1, duration: duration * 0.5, ease: "power2.inOut" },
          duration * 0.25
        );
      }

      if (lines) {
        tween.to(
          lines,
          {
            yPercent: staggerY,
            duration: Math.min(staggerDuration * 0.55, duration * 0.45),
            stagger: staggerDelay * 0.4,
            ease: "power2.in",
            force3D: true,
          },
          0
        );
      }

      itemEl.dispatchEvent(
        new CustomEvent("faq:close", {
          detail: { item: itemEl, panel },
          bubbles: true,
        })
      );
    }

    function toggle() {
      if (isOpen) close();
      else open();
    }

    function refreshHeight() {
      if (!isOpen) return;
      if (lines) gsap.set(lines, { yPercent: 0 });
      gsap.set(panel, { height: Math.ceil(getAutoHeight(panel)) });
    }

    const api = {
      el: itemEl,
      isOpen: () => isOpen,
      open,
      close,
      toggle,
      refreshHeight,
      destroy() {
        killTween();
        if (split && typeof split.revert === "function") split.revert();
        trigger.removeEventListener("click", onClick);
        trigger.removeEventListener("keydown", onKeydown);
      },
    };

    function onClick() {
      toggle();
    }

    function onKeydown(event) {
      if (event.key === "Enter" || event.key === " ") {
        event.preventDefault();
        toggle();
      }
    }

    ensureAriaControls();

    if (isOpen) {
      if (lines) gsap.set(lines, { yPercent: 0 });
      const openHeight = Math.ceil(getAutoHeight(panel));
      gsap.set(panel, { height: openHeight, overflow: "hidden" });
      if (icon) gsap.set(icon, { rotation: iconRotation });
      if (iconV) gsap.set(iconV, { opacity: 0 });
      listCtx.openItems.push(api);
      setA11y(true);
    } else {
      setA11y(false);
    }

    trigger.addEventListener("click", onClick);
    trigger.addEventListener("keydown", onKeydown);

    return api;
  }

  function initList(root) {
    if (isDisabled(root)) return null;

    const listCtx = {
      root,
      allowMultiple: readAttr(root, "allow-multiple", CONFIG.allowMultiple),
      openItems: [],
    };

    const items = [];
    root.querySelectorAll("[data-faq-item]").forEach((itemEl) => {
      const item = createItem(itemEl, listCtx);
      if (item) items.push(item);
    });

    let resizeTimer = null;
    function onResize() {
      window.clearTimeout(resizeTimer);
      resizeTimer = window.setTimeout(() => {
        items.forEach((item) => item.refreshHeight());
      }, 100);
    }
    window.addEventListener("resize", onResize);

    return {
      root,
      items,
      destroy() {
        window.removeEventListener("resize", onResize);
        window.clearTimeout(resizeTimer);
        items.forEach((item) => item.destroy());
      },
    };
  }

  function initStatic() {
    document.querySelectorAll("[data-faq]").forEach((root) => {
      const allowMultiple = readAttr(root, "allow-multiple", CONFIG.allowMultiple);
      const items = Array.from(root.querySelectorAll("[data-faq-item]"));
      let openEl = null;

      items.forEach((itemEl) => {
        const trigger = itemEl.querySelector("[data-faq-trigger]");
        const panel = itemEl.querySelector("[data-faq-panel]");
        if (!trigger || !panel) return;

        let isOpen = readAttr(itemEl, "open", false) === true;
        if (isOpen) openEl = itemEl;

        panel.style.height = isOpen ? "auto" : "0";
        panel.style.overflow = "hidden";
        trigger.setAttribute("aria-expanded", isOpen ? "true" : "false");
        panel.setAttribute("aria-hidden", isOpen ? "false" : "true");

        trigger.addEventListener("click", () => {
          if (isOpen) {
            isOpen = false;
            panel.style.height = "0";
            trigger.setAttribute("aria-expanded", "false");
            panel.setAttribute("aria-hidden", "true");
            if (openEl === itemEl) openEl = null;
            return;
          }

          if (!allowMultiple && openEl && openEl !== itemEl) {
            const otherTrigger = openEl.querySelector("[data-faq-trigger]");
            if (otherTrigger) otherTrigger.click();
          }

          isOpen = true;
          openEl = itemEl;
          panel.style.height = "auto";
          trigger.setAttribute("aria-expanded", "true");
          panel.setAttribute("aria-hidden", "false");
        });
      });
    });
  }

  function init() {
    if (reduceMotion.matches) {
      initStatic();
      return;
    }

    if (typeof SplitText !== "undefined") {
      gsap.registerPlugin(SplitText);
    }

    const lists = [];

    function bootLists() {
      document.querySelectorAll("[data-faq]").forEach((root) => {
        const list = initList(root);
        if (list) lists.push(list);
      });
    }

    // Wait for fonts so line splits match final metrics (no later reflow jump)
    const fontsReady =
      document.fonts && document.fonts.ready
        ? document.fonts.ready
        : Promise.resolve();

    fontsReady.then(bootLists).catch(bootLists);

    window.FaqAccordion = {
      refresh() {
        lists.forEach((list) => list.destroy());
        lists.length = 0;
        bootLists();
      },
    };
  }

  waitForGsap(() => {
    if (document.readyState === "loading") {
      document.addEventListener("DOMContentLoaded", init);
    } else {
      init();
    }
  });
})();
````
