# Morphing Accordion

> Accordion sections that morph height and shape fluidly — FAQ and settings patterns without hard cuts.

**Definition:** Morphing Accordion 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-01
- Canonical page: https://html.bosconvertpro.com/collection/morphing-accordion
- Full-page preview: https://html.bosconvertpro.com/collection/morphing-accordion/full
- Live demo: https://html.bosconvertpro.com/demos/morphing-accordion/index.html
- Markdown mirror: https://html.bosconvertpro.com/collection/morphing-accordion/index.md
- License: https://html.bosconvertpro.com/terms

## How to use
1. Open https://html.bosconvertpro.com/collection/morphing-accordion/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/morphing-accordion.
3. Paste the files into your project and adjust timing, easing, and content as needed.

## FAQ
### What is Morphing Accordion?

Morphing Accordion is a text UI animation from collections.html. Accordion sections that morph height and shape fluidly — FAQ and settings patterns without hard cuts.

### What is Morphing Accordion built with?

Morphing Accordion 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 Morphing Accordion in my project?

Open the animation page (https://html.bosconvertpro.com/collection/morphing-accordion), 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/morphing-accordion/index.html.

### Is Morphing Accordion free to use?

Yes. The Morphing Accordion 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>Morphing 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:ital,wght@0,400..700;1,400..700&display=swap"
      rel="stylesheet"
    />
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <div class="morph-faq">
      <div
        class="morph-faq__stage"
        data-morph-faq
        data-duration="0.8"
        data-ease="elastic.out(1,0.82)"
        data-goo="9"
        data-multiple="false"
        data-start-open="0"
        data-stagger="0.05"
      >
        <svg
          class="morph-faq__filters"
          width="0"
          height="0"
          aria-hidden="true"
          focusable="false"
        >
          <defs>
            <filter
              id="morph-faq-goo-template"
              color-interpolation-filters="sRGB"
              x="-50%"
              y="-50%"
              width="200%"
              height="200%"
            >
              <feGaussianBlur
                in="SourceGraphic"
                stdDeviation="0"
                result="morph_blur"
              />
              <feColorMatrix
                in="morph_blur"
                mode="matrix"
                values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 20 -10"
                result="morph_goo"
              />
            </filter>
          </defs>
        </svg>

        <div class="morph-faq__list">
          <div class="morph-faq__item" data-faq-item>
            <div class="morph-faq__goo" data-faq-goo aria-hidden="true">
              <div class="morph-faq__goo-header"></div>
              <div class="morph-faq__goo-body"></div>
            </div>
            <button
              class="morph-faq__trigger"
              data-faq-trigger
              id="faq-trigger-0"
              aria-controls="faq-panel-0"
              aria-expanded="false"
            >
              <span class="morph-faq__number">01</span>
              <span class="morph-faq__question"
                >How long does a typical project take?</span
              >
              <svg
                class="morph-faq__icon"
                data-faq-icon
                viewBox="0 0 16 16"
                fill="none"
                aria-hidden="true"
              >
                <path
                  d="M8 1V15"
                  stroke="currentColor"
                  stroke-width="1.5"
                  stroke-linecap="round"
                />
                <path
                  d="M1 8H15"
                  stroke="currentColor"
                  stroke-width="1.5"
                  stroke-linecap="round"
                />
              </svg>
            </button>
            <div class="morph-faq__header-bg" aria-hidden="true"></div>
            <div class="morph-faq__body-bg" aria-hidden="true"></div>
            <div
              class="morph-faq__panel"
              data-faq-panel
              id="faq-panel-0"
              role="region"
              aria-labelledby="faq-trigger-0"
              aria-hidden="true"
            >
              <div class="morph-faq__content">
                <p class="morph-faq__answer" data-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. We share a fixed
                  timeline before any work begins.
                </p>
              </div>
            </div>
          </div>

          <div class="morph-faq__item" data-faq-item>
            <div class="morph-faq__goo" data-faq-goo aria-hidden="true">
              <div class="morph-faq__goo-header"></div>
              <div class="morph-faq__goo-body"></div>
            </div>
            <button
              class="morph-faq__trigger"
              data-faq-trigger
              id="faq-trigger-1"
              aria-controls="faq-panel-1"
              aria-expanded="false"
            >
              <span class="morph-faq__number">02</span>
              <span class="morph-faq__question"
                >Do you work with early-stage startups?</span
              >
              <svg
                class="morph-faq__icon"
                data-faq-icon
                viewBox="0 0 16 16"
                fill="none"
                aria-hidden="true"
              >
                <path
                  d="M8 1V15"
                  stroke="currentColor"
                  stroke-width="1.5"
                  stroke-linecap="round"
                />
                <path
                  d="M1 8H15"
                  stroke="currentColor"
                  stroke-width="1.5"
                  stroke-linecap="round"
                />
              </svg>
            </button>
            <div class="morph-faq__header-bg" aria-hidden="true"></div>
            <div class="morph-faq__body-bg" aria-hidden="true"></div>
            <div
              class="morph-faq__panel"
              data-faq-panel
              id="faq-panel-1"
              role="region"
              aria-labelledby="faq-trigger-1"
              aria-hidden="true"
            >
              <div class="morph-faq__content">
                <p class="morph-faq__answer" data-faq-answer>
                  Yes. Roughly half of our clients are pre-Series A. We scope
                  lean sprints focused on a single high-impact surface—landing
                  page, onboarding, or core dashboard—so you ship something real
                  without a bloated retainer.
                </p>
              </div>
            </div>
          </div>

          <div class="morph-faq__item" data-faq-item>
            <div class="morph-faq__goo" data-faq-goo aria-hidden="true">
              <div class="morph-faq__goo-header"></div>
              <div class="morph-faq__goo-body"></div>
            </div>
            <button
              class="morph-faq__trigger"
              data-faq-trigger
              id="faq-trigger-2"
              aria-controls="faq-panel-2"
              aria-expanded="false"
            >
              <span class="morph-faq__number">03</span>
              <span class="morph-faq__question"
                >What tools do you deliver files in?</span
              >
              <svg
                class="morph-faq__icon"
                data-faq-icon
                viewBox="0 0 16 16"
                fill="none"
                aria-hidden="true"
              >
                <path
                  d="M8 1V15"
                  stroke="currentColor"
                  stroke-width="1.5"
                  stroke-linecap="round"
                />
                <path
                  d="M1 8H15"
                  stroke="currentColor"
                  stroke-width="1.5"
                  stroke-linecap="round"
                />
              </svg>
            </button>
            <div class="morph-faq__header-bg" aria-hidden="true"></div>
            <div class="morph-faq__body-bg" aria-hidden="true"></div>
            <div
              class="morph-faq__panel"
              data-faq-panel
              id="faq-panel-2"
              role="region"
              aria-labelledby="faq-trigger-2"
              aria-hidden="true"
            >
              <div class="morph-faq__content">
                <p class="morph-faq__answer" data-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>
          </div>

          <div class="morph-faq__item" data-faq-item>
            <div class="morph-faq__goo" data-faq-goo aria-hidden="true">
              <div class="morph-faq__goo-header"></div>
              <div class="morph-faq__goo-body"></div>
            </div>
            <button
              class="morph-faq__trigger"
              data-faq-trigger
              id="faq-trigger-3"
              aria-controls="faq-panel-3"
              aria-expanded="false"
            >
              <span class="morph-faq__number">04</span>
              <span class="morph-faq__question"
                >Can you support after launch?</span
              >
              <svg
                class="morph-faq__icon"
                data-faq-icon
                viewBox="0 0 16 16"
                fill="none"
                aria-hidden="true"
              >
                <path
                  d="M8 1V15"
                  stroke="currentColor"
                  stroke-width="1.5"
                  stroke-linecap="round"
                />
                <path
                  d="M1 8H15"
                  stroke="currentColor"
                  stroke-width="1.5"
                  stroke-linecap="round"
                />
              </svg>
            </button>
            <div class="morph-faq__header-bg" aria-hidden="true"></div>
            <div class="morph-faq__body-bg" aria-hidden="true"></div>
            <div
              class="morph-faq__panel"
              data-faq-panel
              id="faq-panel-3"
              role="region"
              aria-labelledby="faq-trigger-3"
              aria-hidden="true"
            >
              <div class="morph-faq__content">
                <p class="morph-faq__answer" data-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 call us per sprint when you
                  need a focused push.
                </p>
              </div>
            </div>
          </div>

          <div class="morph-faq__item" data-faq-item>
            <div class="morph-faq__goo" data-faq-goo aria-hidden="true">
              <div class="morph-faq__goo-header"></div>
              <div class="morph-faq__goo-body"></div>
            </div>
            <button
              class="morph-faq__trigger"
              data-faq-trigger
              id="faq-trigger-4"
              aria-controls="faq-panel-4"
              aria-expanded="false"
            >
              <span class="morph-faq__number">05</span>
              <span class="morph-faq__question"
                >How do we get started?</span
              >
              <svg
                class="morph-faq__icon"
                data-faq-icon
                viewBox="0 0 16 16"
                fill="none"
                aria-hidden="true"
              >
                <path
                  d="M8 1V15"
                  stroke="currentColor"
                  stroke-width="1.5"
                  stroke-linecap="round"
                />
                <path
                  d="M1 8H15"
                  stroke="currentColor"
                  stroke-width="1.5"
                  stroke-linecap="round"
                />
              </svg>
            </button>
            <div class="morph-faq__header-bg" aria-hidden="true"></div>
            <div class="morph-faq__body-bg" aria-hidden="true"></div>
            <div
              class="morph-faq__panel"
              data-faq-panel
              id="faq-panel-4"
              role="region"
              aria-labelledby="faq-trigger-4"
              aria-hidden="true"
            >
              <div class="morph-faq__content">
                <p class="morph-faq__answer" data-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>

    <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/CustomEase.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
*,
*::before,
*::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html,
body {
  height: 100%;
}

body {
  font-family: "Plus Jakarta Sans", -apple-system, BlinkMacSystemFont, "Segoe UI",
    Roboto, "Helvetica Neue", sans-serif;
  background: #e8e9ed;
  color: #0a0a0a;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.morph-faq {
  --faq-bg: #e8e9ed;
  --faq-ink: #12141a;
  --faq-ink-hover: #1c1f28;
  --faq-fg: rgba(255, 252, 248, 0.94);
  --faq-fg-soft: rgba(255, 255, 255, 0.72);
  --faq-fg-dim: rgba(255, 255, 255, 0.42);
  --faq-accent: #8b9cff;
  --faq-accent-soft: rgba(139, 156, 255, 0.18);
  --faq-margin: 40px;
  --faq-radius-panel: 18px;

  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 100vh;
  min-height: 100dvh;
  box-sizing: border-box;
  padding: var(--faq-margin);
  background:
    radial-gradient(ellipse 80% 55% at 50% -10%, rgba(139, 156, 255, 0.14), transparent 55%),
    radial-gradient(ellipse 50% 40% at 100% 100%, rgba(255, 180, 140, 0.1), transparent 50%),
    radial-gradient(ellipse 40% 35% at 0% 80%, rgba(120, 200, 180, 0.08), transparent 45%),
    var(--faq-bg);
  font-family: "Plus Jakarta Sans", -apple-system, BlinkMacSystemFont, sans-serif;
  -webkit-font-smoothing: antialiased;
}

.morph-faq__stage {
  position: relative;
  width: 100%;
  max-width: 580px;
}

.morph-faq__stage::before {
  content: "";
  position: absolute;
  inset: -48px -32px;
  border-radius: 40px;
  background: linear-gradient(
    160deg,
    rgba(255, 255, 255, 0.55) 0%,
    rgba(255, 255, 255, 0.12) 45%,
    rgba(255, 255, 255, 0.05) 100%
  );
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.65) inset,
    0 24px 64px rgba(18, 20, 26, 0.08),
    0 4px 16px rgba(18, 20, 26, 0.04);
  pointer-events: none;
  z-index: -1;
}

.morph-faq__filters {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
  pointer-events: none;
}

.morph-faq__list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.morph-faq__item {
  position: relative;
}

.morph-faq__goo {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

.morph-faq__goo-header,
.morph-faq__goo-body {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  background-color: var(--faq-ink);
  will-change: transform;
}

.morph-faq__goo-body {
  border-radius: var(--faq-radius-panel);
}

.morph-faq__header-bg {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 3;
  background-color: var(--faq-ink);
  pointer-events: none;
  transition: background-color 0.35s cubic-bezier(0.4, 0, 0.2, 1);
}

.morph-faq__trigger:hover + .morph-faq__header-bg {
  background-color: var(--faq-ink-hover);
}

.morph-faq__body-bg {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1;
  background-color: var(--faq-ink);
  border-radius: var(--faq-radius-panel);
  pointer-events: none;
  will-change: transform;
}

.morph-faq__trigger {
  position: relative;
  z-index: 4;
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  min-height: 60px;
  padding: 17px 20px 17px 18px;
  background-color: transparent;
  border: none;
  cursor: pointer;
  text-align: left;
  color: var(--faq-fg);
  font-family: inherit;
  box-sizing: border-box;
}

.morph-faq__trigger:focus {
  outline: none;
}

.morph-faq__trigger:focus-visible {
  outline: 2px solid var(--faq-accent);
  outline-offset: 3px;
  border-radius: 999px;
}

.morph-faq__number {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 34px;
  height: 28px;
  padding: 0 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
  color: var(--faq-accent);
  background: var(--faq-accent-soft);
}

.morph-faq__question {
  flex: 1;
  font-size: 15px;
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.4;
  color: var(--faq-fg);
}

.morph-faq__icon {
  width: 28px;
  height: 28px;
  padding: 6px;
  flex-shrink: 0;
  color: var(--faq-fg-soft);
  background: rgba(255, 255, 255, 0.08);
  border-radius: 50%;
  transform-origin: 50% 50%;
  will-change: transform;
  box-sizing: border-box;
  transition: background 0.3s cubic-bezier(0.4, 0, 0.2, 1), color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.morph-faq__trigger:hover .morph-faq__icon {
  background: rgba(255, 255, 255, 0.12);
  color: var(--faq-fg);
}

.morph-faq__panel {
  position: relative;
  z-index: 2;
  height: 0;
  overflow: hidden;
  will-change: height;
}

.morph-faq__content {
  margin-top: 10px;
  padding: 18px 22px 20px;
}

.morph-faq__answer {
  margin: 0;
  font-size: 14px;
  font-weight: 400;
  line-height: 1.7;
  letter-spacing: -0.01em;
  color: var(--faq-fg-soft);
}

.morph-faq__line {
  overflow-x: visible;
  overflow-y: clip;
  padding-bottom: 0.18em;
  margin-bottom: -0.18em;
}

@media (max-width: 767px) {
  .morph-faq {
    --faq-margin: 20px;
  }

  .morph-faq__stage::before {
    inset: -28px -16px;
    border-radius: 28px;
  }

  .morph-faq__trigger {
    padding: 15px 16px 15px 14px;
    gap: 12px;
    min-height: 56px;
  }

  .morph-faq__content {
    padding: 16px 18px 18px;
  }

  .morph-faq__question {
    font-size: 14px;
  }
}

@media (max-width: 479px) {
  .morph-faq__number {
    display: none;
  }

  .morph-faq__trigger {
    padding-left: 18px;
  }
}
````

## Source — script.js
````javascript
(() => {
  "use strict";

  let filterSeq = 0;

  function waitForDeps(cb, attempt) {
    attempt = attempt || 0;
    if (typeof gsap === "undefined" || typeof CustomEase === "undefined") {
      if (attempt < 100) {
        setTimeout(() => waitForDeps(cb, attempt + 1), 50);
      } else {
        console.error("[Morphing FAQ] gsap / CustomEase failed to load");
      }
      return;
    }
    cb();
  }

  function isDisabled(stage) {
    const raw = stage.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 initStage(stage) {
    const svg = stage.querySelector(".morph-faq__filters");
    const filterTemplate = svg ? svg.querySelector("filter") : null;
    const defs = svg ? svg.querySelector("defs") : null;
    const itemEls = stage.querySelectorAll("[data-faq-item]");

    if (!filterTemplate || !defs || !itemEls.length) return;

    let duration = parseFloat(stage.dataset.duration || "0.8");
    let stagger = parseFloat(stage.dataset.stagger || "0.05");
    const easeOpen = stage.dataset.ease || "elastic.out(1,0.82)";
    const gooAmount = parseFloat(stage.dataset.goo || "9");
    const allowMultiple = (stage.dataset.multiple || "false") === "true";
    const startOpenRaw = stage.dataset.startOpen;
    const startOpen =
      startOpenRaw !== undefined && startOpenRaw !== ""
        ? parseInt(startOpenRaw, 10)
        : -1;

    const easeClose =
      easeOpen.indexOf("elastic") === 0 ? "morphEase" : easeOpen;

    const PANEL_GAP = 10;
    const items = [];
    let resizeTimer = null;
    const filterBase = "morph-faq-goo-" + ++filterSeq;

    function setGooFilter(item, on) {
      item.gooEl.style.filter = on ? "url(#" + item.filterId + ")" : "none";
    }

    function applyBlur(item) {
      if (item.blurEl) {
        item.blurEl.setAttribute("stdDeviation", String(item.goo.std));
      }
    }

    function measure(item) {
      item.triggerH = item.trigger.offsetHeight;
      item.innerH = item.content.offsetHeight;
      item.rowW = item.el.offsetWidth;
    }

    function setPanelClosed(item) {
      gsap.set([item.gooBody, item.bodyBg], {
        x: 4,
        y: 4,
        width: item.rowW - 8,
        height: item.triggerH - 8,
        force3D: true,
      });
    }

    function setTriggerShape(item) {
      gsap.set([item.gooHeader, item.headerBg], {
        height: item.triggerH,
        borderRadius: item.triggerH / 2,
      });
    }

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

    function killTweens(item) {
      if (item.tl) {
        item.tl.kill();
        item.tl = null;
      }
      gsap.killTweensOf([
        item.gooBody,
        item.bodyBg,
        item.panel,
        item.el,
        item.icon,
        item.goo,
        item.answer,
      ]);
    }

    function killAnswer(item) {
      if (item.answerTl) {
        item.answerTl.kill();
        item.answerTl = null;
      }
      if (item.split && item.split.revert) {
        item.split.revert();
        item.split = null;
      }
      gsap.set(item.answer, { autoAlpha: 0 });
    }

    function revealAnswer(item) {
      if (typeof SplitText !== "undefined") {
        killAnswer(item);
        gsap.set(item.answer, { autoAlpha: 1 });
        item.split = SplitText.create(item.answer, {
          type: "lines",
          mask: "lines",
          linesClass: "morph-faq__line",
          onSplit(self) {
            gsap.set(self.lines, { yPercent: 100, y: 0, force3D: true });
            item.answerTl = gsap.timeline();
            item.answerTl.to(self.lines, {
              yPercent: 0,
              y: 0,
              duration: 0.6,
              stagger: stagger,
              ease: "morphEase",
              force3D: true,
            });
          },
        });
      } else {
        gsap.set(item.answer, { autoAlpha: 1 });
      }
    }

    function closeItem(item) {
      item.isOpen = false;
      setA11y(item, false);
      killTweens(item);

      const d = duration * 0.65;

      setGooFilter(item, true);

      item.tl = gsap.timeline({ defaults: { force3D: true } });

      item.tl
        .to(
          item.goo,
          {
            std: gooAmount,
            duration: 0.3 * d,
            ease: "power1.in",
            onUpdate() {
              applyBlur(item);
            },
          },
          0
        )
        .to(
          [item.gooBody, item.bodyBg],
          {
            x: 4,
            y: 4,
            width: item.rowW - 8,
            height: item.triggerH - 8,
            duration: d,
            ease: easeClose,
          },
          0
        )
        .to(item.panel, { height: 0, duration: d, ease: easeClose }, 0)
        .to(item.el, { marginTop: 0, duration: d, ease: easeClose }, 0)
        .to(
          item.icon,
          { rotation: 0, duration: 0.5, ease: "morphEaseInOut" },
          0
        )
        .to(
          item.answer,
          { autoAlpha: 0, duration: 0.15, ease: "power2.out" },
          0
        )
        .to(
          item.goo,
          {
            std: 0,
            duration: 0.3 * d,
            ease: "power1.out",
            onUpdate() {
              applyBlur(item);
            },
            onComplete() {
              setGooFilter(item, false);
              killAnswer(item);
            },
          },
          0.7 * d
        );
    }

    function openItem(item) {
      item.isOpen = true;
      measure(item);
      setA11y(item, true);
      killTweens(item);

      const openHeight = PANEL_GAP + item.innerH + 16;

      setGooFilter(item, true);
      item.goo.std = gooAmount;
      applyBlur(item);

      item.tl = gsap.timeline({ defaults: { force3D: true } });

      item.tl
        .to(
          [item.gooBody, item.bodyBg],
          {
            x: 0,
            y: item.triggerH + PANEL_GAP,
            width: item.rowW,
            height: item.innerH,
            duration: duration,
            ease: easeOpen,
          },
          0
        )
        .to(
          item.panel,
          { height: openHeight, duration: duration, ease: easeOpen },
          0
        )
        .to(item.el, { marginTop: 16, duration: duration, ease: easeOpen }, 0)
        .to(
          item.icon,
          { rotation: 135, duration: 0.5, ease: "morphEaseInOut" },
          0
        )
        .to(
          item.goo,
          {
            std: 0,
            duration: 0.25 * duration,
            ease: "power1.in",
            onUpdate() {
              applyBlur(item);
            },
            onComplete() {
              setGooFilter(item, false);
            },
          },
          0.4 * duration
        )
        .call(
          () => {
            revealAnswer(item);
          },
          null,
          0.32 * duration
        );
    }

    function toggle(index) {
      const item = items[index];
      if (!item) return;

      if (item.isOpen) {
        closeItem(item);
        return;
      }

      if (!allowMultiple) {
        items.forEach((other, i) => {
          if (i !== index && other.isOpen) closeItem(other);
        });
      }

      openItem(item);
    }

    function focusNeighbor(index, dir) {
      const n = items.length;
      if (!n) return;
      items[(index + dir + n) % n].trigger.focus();
    }

    itemEls.forEach((el, index) => {
      const gooEl = el.querySelector("[data-faq-goo]");
      const gooHeader = el.querySelector(".morph-faq__goo-header");
      const gooBody = el.querySelector(".morph-faq__goo-body");
      const headerBg = el.querySelector(".morph-faq__header-bg");
      const bodyBg = el.querySelector(".morph-faq__body-bg");
      const trigger = el.querySelector("[data-faq-trigger]");
      const panel = el.querySelector("[data-faq-panel]");
      const content = el.querySelector(".morph-faq__content");
      const answer = el.querySelector("[data-faq-answer]");
      const icon = el.querySelector("[data-faq-icon]");

      if (
        !gooEl ||
        !gooHeader ||
        !gooBody ||
        !headerBg ||
        !bodyBg ||
        !trigger ||
        !panel ||
        !content
      ) {
        return;
      }

      const filterClone = filterTemplate.cloneNode(true);
      const filterId = filterBase + "-" + index;
      filterClone.setAttribute("id", filterId);
      defs.appendChild(filterClone);

      items.push({
        el,
        gooEl,
        gooHeader,
        gooBody,
        headerBg,
        bodyBg,
        trigger,
        panel,
        content,
        answer,
        icon,
        filterId,
        blurEl: filterClone.querySelector("feGaussianBlur"),
        goo: { std: 0 },
        isOpen: false,
        triggerH: 0,
        innerH: 0,
        split: null,
        answerTl: null,
        tl: null,
      });
    });

    if (!items.length) return;
    if (isDisabled(stage)) return;

    items.forEach((item, index) => {
      item.trigger.addEventListener("click", () => toggle(index));
      item.trigger.addEventListener("keydown", (e) => {
        if (e.key === "ArrowDown" || e.key === "ArrowRight") {
          e.preventDefault();
          focusNeighbor(index, 1);
        } else if (e.key === "ArrowUp" || e.key === "ArrowLeft") {
          e.preventDefault();
          focusNeighbor(index, -1);
        }
      });
    });

    window.addEventListener("resize", () => {
      clearTimeout(resizeTimer);
      resizeTimer = setTimeout(() => {
        items.forEach((item) => {
          measure(item);
          setTriggerShape(item);
          if (item.isOpen) {
            gsap.set([item.gooBody, item.bodyBg], {
              x: 0,
              y: item.triggerH + PANEL_GAP,
              width: item.rowW,
              height: item.innerH,
            });
            gsap.set(item.panel, {
              height: PANEL_GAP + item.innerH + 16,
            });
            gsap.set(item.el, { marginTop: 16 });
          } else {
            setPanelClosed(item);
            gsap.set(item.el, { marginTop: 0 });
          }
        });
      }, 150);
    });

    document.addEventListener("visibilitychange", () => {
      if (document.hidden) {
        gsap.globalTimeline.pause();
      } else {
        gsap.globalTimeline.resume();
      }
    });

    items.forEach((item, index) => {
      measure(item);
      setTriggerShape(item);
      setPanelClosed(item);
      setGooFilter(item, false);
      gsap.set(item.answer, { autoAlpha: 0 });

      const open = index === startOpen;
      item.isOpen = open;
      setA11y(item, open);

      if (open) {
        gsap.set([item.gooBody, item.bodyBg], {
          x: 0,
          y: item.triggerH + PANEL_GAP,
          width: item.rowW,
          height: item.innerH,
        });
        gsap.set(item.panel, {
          height: PANEL_GAP + item.innerH + 16,
        });
        gsap.set(item.el, { marginTop: 16 });
        gsap.set(item.icon, { rotation: 135 });
        revealAnswer(item);
      }
    });

    gsap.matchMedia().add("(prefers-reduced-motion: reduce)", () => {
      const prevDuration = duration;
      const prevStagger = stagger;
      duration = 0.01;
      stagger = 0;
      return () => {
        duration = prevDuration;
        stagger = prevStagger;
      };
    });

    return {
      toggle,
      openAt(i) {
        if (items[i] && !items[i].isOpen) toggle(i);
      },
      closeAt(i) {
        if (items[i] && items[i].isOpen) toggle(i);
      },
    };
  }

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

    CustomEase.create("morphEase", "M0,0 C0.3,0.9 0.1,1 1,1");
    CustomEase.create("morphEaseInOut", "M0,0 C0.7,0 0.16,1 1,1");

    const instances = [];

    document.querySelectorAll("[data-morph-faq]").forEach((stage) => {
      const api = initStage(stage);
      if (api) instances.push(api);
    });

    window.MorphFAQ = {
      refresh() {
        instances.length = 0;
        document.querySelectorAll("[data-morph-faq]").forEach((stage) => {
          const api = initStage(stage);
          if (api) instances.push(api);
        });
      },
      openAt(i) {
        instances.forEach((api) => api.openAt(i));
      },
      closeAt(i) {
        instances.forEach((api) => api.closeAt(i));
      },
    };
  });
})();
````
