> ## Documentation Index
> Fetch the complete documentation index at: https://hyperframes.heygen.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Testimonial Proof Card

> A quote card whose testimonial reveals per-line through a soft mask, with an avatar disc (initials default, image slot), mono name and role, an optional company text mark, and one emphasis substring underlined in accent ink after the quote lands.

export const InstallCommand = ({command}) => {
  const [copied, setCopied] = React.useState(false);
  const copy = async () => {
    try {
      if (navigator.clipboard && window.isSecureContext) {
        await navigator.clipboard.writeText(command);
      } else {
        const previous = document.activeElement;
        const scratch = document.createElement("textarea");
        scratch.value = command;
        scratch.setAttribute("readonly", "");
        scratch.style.position = "fixed";
        scratch.style.opacity = "0";
        document.body.appendChild(scratch);
        scratch.select();
        document.execCommand("copy");
        document.body.removeChild(scratch);
        previous?.focus?.();
      }
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    } catch {}
  };
  return <div className="hf-install-command not-prose my-4 flex items-stretch overflow-hidden rounded-xl border border-zinc-200 bg-zinc-50 dark:border-zinc-800 dark:bg-zinc-900">
      <code className="flex-1 overflow-x-auto whitespace-nowrap border-r border-zinc-200 px-4 py-3 font-mono text-sm text-zinc-800 dark:border-zinc-800 dark:text-zinc-100">
        {command}
      </code>
      <button type="button" onClick={copy} data-copied={copied ? "true" : "false"} aria-label={`Copy ${command} to the clipboard`} className="hf-install-copy">
        <svg className="hf-install-copy-clipboard" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
          <path d="M14.25 5.25H7.25C6.14543 5.25 5.25 6.14543 5.25 7.25V14.25C5.25 15.3546 6.14543 16.25 7.25 16.25H14.25C15.3546 16.25 16.25 15.3546 16.25 14.25V7.25C16.25 6.14543 15.3546 5.25 14.25 5.25Z" />
          <path d="M2.80103 11.998L1.77203 5.07397C1.61003 3.98097 2.36403 2.96397 3.45603 2.80197L10.38 1.77297C11.313 1.63397 12.19 2.16297 12.528 3.00097" />
        </svg>
        <svg className="hf-install-copy-check" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
          <path d="M2.75 9.5L6.5 13.25L15.25 4.5" />
        </svg>
      </button>
      <span className="hf-install-copy-status" role="status" aria-live="polite">
        {copied ? "Copied" : ""}
      </span>
    </div>;
};

<iframe className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800" title="testimonial-proof-card preview" loading="lazy" srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@0.7/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/components/testimonial-proof-card.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/testimonial-proof-card.png");document.body.appendChild(p)});<\/script></body></html>`} />

## Install

<InstallCommand command="npx hyperframes add testimonial-proof-card" />

That writes one file: `compositions/components/testimonial-proof-card.html`.

## Paste it into your composition

Open `compositions/components/testimonial-proof-card.html` and copy what is inside into your own composition.

A component has no size or duration of its own. It takes both from the composition
you paste it into.

## Variables

Every one of these has a default, so the piece works untouched. Set the ones you
want to change on the element:

| Variable   | Default                                             | Accepts                   | What it does                                                                                                                  |
| ---------- | --------------------------------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `quote`    | `We shipped our launch video in a single afternoon` | string                    | The testimonial line. Wraps inside the card; each rendered line reveals through its own mask.                                 |
| `name`     | `Maya Chen`                                         | string                    | Attribution name in mono. Also seeds the avatar initials.                                                                     |
| `role`     | `Head of Product`                                   | string                    | Attribution role in muted mono. Empty hides the line.                                                                         |
| `company`  | `NORTHWIND`                                         | string                    | Company mark as wide-tracked mono text at the byline end. Empty hides it.                                                     |
| `emphasis` | `single afternoon`                                  | string                    | First case-insensitive substring match in quote, expanded to whole words, gets the accent underline. Empty disables the draw. |
| `accent`   | `green`                                             | `green`, `blue`, `violet` | Underline and glyph ink from the contract token map.                                                                          |
| `exit`     | `none`                                              | `none`, `fade`, `up`      | Optional departure. Default none: the card holds until the frame cuts.                                                        |

Set them with `data-variable-values` on the element that mounts it. These are the
defaults, so this behaves exactly like the preview above until you change one:

```html wrap theme={null}
<div
  data-composition-id="testimonial-proof-card"
  data-composition-src="compositions/components/testimonial-proof-card.html"
  data-variable-values='{"quote":"We shipped our launch video in a single afternoon","name":"Maya Chen","role":"Head of Product","company":"NORTHWIND","emphasis":"single afternoon","accent":"green","exit":"none"}'
></div>
```

## Source

<Accordion title={`testimonial-proof-card.html`}>
  ```html theme={null}
  <!doctype html>
  <!--
    testimonial-proof-card: HyperFrames video primitive (proof-stats / testimonial)

    A quote card: the quote reveals per measured line through a soft overflow
    mask, an avatar disc (initials by default, an image via the avatar slot),
    name and role in mono, an optional company mark as wide-tracked text; one
    emphasis substring gets an accent underline drawn hand-style after the
    quote lands (getTotalLength dash, the marker-highlight law). Dead-still
    hold: nothing breathes, nothing loops.

    Variables:
      quote (string): the testimonial line. Wraps inside the card; each
        rendered line reveals through its own mask.
      name (string): attribution name (mono). Also seeds the avatar initials.
      role (string): attribution role (mono, muted). Empty hides the line.
      company (string): company mark as wide-tracked mono text at the byline
        end. Empty hides it.
      emphasis (string): first case-insensitive substring match inside quote,
        expanded to whole words, gets the accent underline. Empty or unmatched
        disables the draw.
      accent (green | blue | violet): underline and glyph ink via the contract
        token map (green --brand, blue --accent, violet --accent-2).
      exit (none | fade | up, default none): frame roots own transitions;
        holds end films.

    Envelope, fixed IN and OUT with elastic HOLD only (never time-scaled):
      CARD_BASE  = 0.50s  card surface settles up
      LINE_BASE  = 0.60s  per-line rise through the mask, 0.18s stagger
      BYLINE     = 0.50s  avatar + attribution fade up, overlapping quote end
      DRAW_BASE  = 0.55s  underline draws after the last line lands
      HOLD       = max(0, D - phases), completely still
      OUT_BASE   = 0s for exit none, otherwise 0.45s
      If D is shorter than the fixed phases, every phase compresses together.

    Slot (before-after-wipe convention): the avatar disc is a named slot,
    <div class="tq-avatar" data-slot="avatar">. Its default child is the
    initials span; replace the slot's children in your installed copy with an
    <img> to show a photo (sized to cover the disc automatically).

    Mount contract: the runtime clones only this template. #root fills the
    host box, establishes the container query basis, has no data-width or
    data-height, and registers one paused timeline under the literal
    testimonial-proof-card key.
  -->
  <html
    lang="en"
    data-composition-id="testimonial-proof-card"
    data-composition-duration="4.5"
    data-composition-variables='[
      { "id": "quote", "type": "string", "role": "content", "label": "Quote", "description": "The testimonial line. Wraps inside the card; each rendered line reveals through its own mask.", "default": "We shipped our launch video in a single afternoon" },
      { "id": "name", "type": "string", "role": "content", "label": "Name", "description": "Attribution name in mono. Also seeds the avatar initials.", "default": "Maya Chen" },
      { "id": "role", "type": "string", "role": "content", "label": "Role", "description": "Attribution role in muted mono. Empty hides the line.", "default": "Head of Product" },
      { "id": "company", "type": "string", "role": "content", "label": "Company", "description": "Company mark as wide-tracked mono text at the byline end. Empty hides it.", "default": "NORTHWIND" },
      { "id": "emphasis", "type": "string", "role": "content", "label": "Emphasis", "description": "First case-insensitive substring match in quote, expanded to whole words, gets the accent underline. Empty disables the draw.", "default": "single afternoon" },
      { "id": "accent", "type": "enum", "role": "style", "label": "Accent", "description": "Underline and glyph ink from the contract token map.", "default": "green", "options": [{ "value": "green", "label": "Green" }, { "value": "blue", "label": "Blue" }, { "value": "violet", "label": "Violet" }] },
      { "id": "exit", "type": "enum", "role": "timing", "label": "Exit", "description": "Optional departure. Default none: the card holds until the frame cuts.", "default": "none", "options": [{ "value": "none", "label": "None" }, { "value": "fade", "label": "Fade" }, { "value": "up", "label": "Up" }] }
    ]'
  >
    <head>
      <meta charset="UTF-8" />
      <title>Testimonial Proof Card</title>
    </head>
    <body>
      <template>
        <div id="root" data-composition-id="testimonial-proof-card" data-duration="4.5" data-fps="30">
          <style>
            *,
            *::before,
            *::after {
              box-sizing: border-box;
            }

            #root {
              position: absolute;
              inset: 0;
              overflow: hidden;
              container-type: size;
              isolation: isolate;
              display: grid;
              place-items: center;
              background: var(--bg, #0b0c0e);
              color: var(--fg, #f8fafc);
              font-family: var(--font-display, Inter, system-ui, sans-serif);
              pointer-events: none;
            }

            .tq-clip {
              position: absolute;
              inset: 0;
              display: grid;
              place-items: center;
              overflow: hidden;
            }

            .tq-card {
              width: min(76cqw, 150cqh);
              padding: 6cqmin 7cqmin;
              border-radius: var(--radius, 24px);
              border: 1px solid color-mix(in srgb, var(--border, #343a46) 60%, transparent);
              background: color-mix(in srgb, var(--surface, #14171c) 88%, transparent);
              will-change: transform, opacity;
            }

            .tq-glyph {
              font-size: 9cqmin;
              line-height: 0.6;
              font-weight: 600;
              color: var(--tq-accent, var(--brand, #71f5a7));
              margin-bottom: 2.4cqmin;
            }

            .tq-quote {
              margin: 0;
              font-size: var(--tq-font-size, 5cqmin);
              font-weight: 600;
              line-height: 1.28;
              letter-spacing: -0.02em;
              text-wrap: balance;
            }

            /* Soft per-line mask. Negative margins cancel the padding so the
               expanded clip box (room for descenders and the underline ink
               below the baseline) never shifts layout. */
            .tq-line-mask {
              overflow: hidden;
              padding: 0.12em 0.24em 0.3em;
              margin: -0.12em -0.24em -0.3em;
            }

            .tq-line {
              will-change: transform, opacity;
            }

            .tq-em {
              position: relative;
              display: inline-block;
              white-space: nowrap;
            }

            .tq-em-text {
              position: relative;
              z-index: 1;
            }

            /* Underline box below the emphasized words; explicit width/height
               (svg is replaced), preserveAspectRatio="none" stretches the
               authored path so the stroke keeps its hand-swiped nib weight. */
            .tq-mark {
              position: absolute;
              left: -2%;
              bottom: -0.14em;
              width: 104%;
              height: 0.24em;
              overflow: visible;
              z-index: 2;
            }

            .tq-byline {
              display: flex;
              align-items: center;
              gap: 3cqmin;
              margin-top: 5cqmin;
              will-change: transform, opacity;
            }

            .tq-avatar {
              width: 10cqmin;
              height: 10cqmin;
              flex: none;
              border-radius: 50%;
              overflow: hidden;
              display: grid;
              place-items: center;
              border: 1px solid color-mix(in srgb, var(--border, #343a46) 70%, transparent);
              background: color-mix(in srgb, var(--tq-accent, #71f5a7) 16%, var(--surface, #14171c));
            }

            .tq-avatar img,
            .tq-avatar video {
              width: 100%;
              height: 100%;
              object-fit: cover;
            }

            .tq-initials {
              font-family: var(--font-mono, ui-monospace, "SF Mono", Menlo, Consolas, monospace);
              font-size: 3.4cqmin;
              font-weight: 600;
              letter-spacing: 0.08em;
              color: var(--fg, #f8fafc);
            }

            .tq-person {
              display: grid;
              gap: 0.8cqmin;
              font-family: var(--font-mono, ui-monospace, "SF Mono", Menlo, Consolas, monospace);
            }

            .tq-name {
              font-size: 2.9cqmin;
              font-weight: 600;
              letter-spacing: 0.02em;
            }

            .tq-role {
              font-size: 2.4cqmin;
              color: var(--muted, #94a3b8);
            }

            .tq-company {
              margin-left: auto;
              font-family: var(--font-mono, ui-monospace, "SF Mono", Menlo, Consolas, monospace);
              font-size: 2.3cqmin;
              letter-spacing: 0.3em;
              text-transform: uppercase;
              color: var(--muted, #94a3b8);
            }
          </style>

          <div
            id="testimonial-proof-card-clip"
            class="tq-clip clip"
            data-start="0"
            data-duration="4.5"
            data-track-index="0"
          >
            <figure class="tq-card" style="margin: 0">
              <div class="tq-glyph" aria-hidden="true">&#8220;</div>
              <blockquote class="tq-quote" style="margin: 0"></blockquote>
              <figcaption class="tq-byline">
                <div class="tq-avatar" data-slot="avatar">
                  <span class="tq-initials"></span>
                </div>
                <div class="tq-person">
                  <span class="tq-name"></span>
                  <span class="tq-role"></span>
                </div>
                <span class="tq-company"></span>
              </figcaption>
            </figure>
          </div>

          <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
          <script>
            (function () {
              "use strict";

              var root = document.getElementById("root");
              // Literal key: the flattening step strips data-composition-id off
              // the mounted root, so reading it back would register "null".
              var compositionId = "testimonial-proof-card";
              var card = root.querySelector(".tq-card");
              var quoteEl = root.querySelector(".tq-quote");
              var bylineEl = root.querySelector(".tq-byline");
              var vars =
                window.__hyperframes && window.__hyperframes.getVariables
                  ? window.__hyperframes.getVariables()
                  : {};

              function str(value, fallback) {
                return (value == null ? fallback : String(value)).trim().replace(/\s+/g, " ");
              }
              var quote = str(vars.quote, "We shipped our launch video in a single afternoon");
              var name = str(vars.name, "Maya Chen");
              var role = str(vars.role, "Head of Product");
              var company = str(vars.company, "NORTHWIND");
              var emphasis = str(vars.emphasis, "single afternoon");

              var accentColors = {
                green: "var(--brand, #71f5a7)",
                blue: "var(--accent, #61a8ff)",
                violet: "var(--accent-2, #c5a3ff)",
              };
              var accent = Object.prototype.hasOwnProperty.call(accentColors, vars.accent)
                ? vars.accent
                : "green";
              root.style.setProperty("--tq-accent", accentColors[accent]);
              var exit = vars.exit === "fade" || vars.exit === "up" ? vars.exit : "none";

              // Byline content. Empty strings hide their element.
              root.querySelector(".tq-name").textContent = name;
              var roleEl = root.querySelector(".tq-role");
              if (role) roleEl.textContent = role;
              else roleEl.setAttribute("hidden", "");
              var companyEl = root.querySelector(".tq-company");
              if (company) companyEl.textContent = company;
              else companyEl.setAttribute("hidden", "");

              // Avatar initials: only when the default initials span is still
              // in the slot (a user-supplied img replaces the slot children).
              var initialsEl = root.querySelector(".tq-avatar .tq-initials");
              if (initialsEl) {
                var parts = name.split(" ").filter(Boolean);
                var initials = parts
                  .slice(0, 2)
                  .map(function (part) {
                    return Array.from(part)[0].toUpperCase();
                  })
                  .join("");
                initialsEl.textContent = initials;
              }

              // Emphasis match expanded to whole words so the quote tokenizes
              // cleanly into word spans (the underline never splits a word).
              var matchStart = -1;
              var matchEnd = -1;
              if (emphasis.length > 0) {
                var index = quote.toLowerCase().indexOf(emphasis.toLowerCase());
                if (index >= 0) {
                  matchStart = quote.lastIndexOf(" ", index) + 1;
                  matchEnd = quote.indexOf(" ", index + emphasis.length);
                  if (matchEnd < 0) matchEnd = quote.length;
                }
              }

              // Deterministic fit: font size rides 1/sqrt(chars) so the quote
              // lands around two to three lines at any card width.
              var characters = Math.max(1, Array.from(quote).length);
              var fitted = Math.max(3.8, Math.min(7.2, 46 / Math.sqrt(characters)));
              root.style.setProperty("--tq-font-size", fitted.toFixed(3) + "cqmin");

              // Tokenize into inline-block word spans (emphasis is one nowrap
              // token), measure wrapped lines, then rebuild one mask per line.
              var tokens = [];
              function pushWords(text) {
                text.split(" ").forEach(function (wordText) {
                  if (!wordText) return;
                  var span = document.createElement("span");
                  span.style.display = "inline-block";
                  span.textContent = wordText;
                  tokens.push(span);
                });
              }
              var pathEl = null;
              var emEl = null;
              if (matchStart >= 0) {
                pushWords(quote.slice(0, matchStart));
                emEl = document.createElement("span");
                emEl.className = "tq-em";
                var svgNS = "http://www.w3.org/2000/svg";
                var svg = document.createElementNS(svgNS, "svg");
                svg.setAttribute("class", "tq-mark");
                svg.setAttribute("viewBox", "0 0 100 12");
                svg.setAttribute("preserveAspectRatio", "none");
                svg.setAttribute("aria-hidden", "true");
                pathEl = document.createElementNS(svgNS, "path");
                // Authored wobble, marker-highlight underline law: dash reveal
                // from getTotalLength, never pathLength, never
                // non-scaling-stroke; the viewBox stretch is the nib feel.
                pathEl.setAttribute("d", "M 2 7 C 20 4.5, 46 8.5, 66 6 C 80 4.5, 92 7.5, 98 5.5");
                pathEl.setAttribute("fill", "none");
                pathEl.setAttribute("stroke-width", "4.5");
                pathEl.setAttribute("stroke-linecap", "round");
                pathEl.setAttribute("stroke-linejoin", "round");
                // style.stroke, not the presentation attribute: var() only
                // resolves as a CSS value.
                pathEl.style.stroke = accentColors[accent];
                svg.appendChild(pathEl);
                var emText = document.createElement("span");
                emText.className = "tq-em-text";
                emText.textContent = quote.slice(matchStart, matchEnd);
                emEl.appendChild(svg);
                emEl.appendChild(emText);
                tokens.push(emEl);
                pushWords(quote.slice(matchEnd));
              } else {
                pushWords(quote);
              }
              tokens.forEach(function (token, i) {
                if (i > 0) quoteEl.appendChild(document.createTextNode(" "));
                quoteEl.appendChild(token);
              });
              quoteEl.setAttribute("aria-label", quote);

              // Group tokens by rendered row, then rebuild per-line masks.
              var rows = [];
              var lastTop = null;
              tokens.forEach(function (token) {
                var top = token.offsetTop;
                if (lastTop === null || Math.abs(top - lastTop) > 2) {
                  rows.push([]);
                  lastTop = top;
                }
                rows[rows.length - 1].push(token);
              });
              quoteEl.textContent = "";
              var lineEls = rows.map(function (row) {
                var mask = document.createElement("div");
                mask.className = "tq-line-mask";
                var line = document.createElement("div");
                line.className = "tq-line";
                row.forEach(function (token, i) {
                  if (i > 0) line.appendChild(document.createTextNode(" "));
                  line.appendChild(token);
                });
                mask.appendChild(line);
                quoteEl.appendChild(mask);
                return line;
              });

              // RETIME RANGE: fixed phases; HOLD is the only elastic span.
              var duration = Math.max(0.001, parseFloat(root.dataset.duration || "4.5"));
              var CARD_BASE = 0.5;
              var LINE_BASE = 0.6;
              var STAG_BASE = 0.18;
              var LINES_AT_BASE = 0.15;
              var BYLINE_BASE = 0.5;
              var DRAW_BASE = 0.55;
              var OUT_BASE = exit === "none" ? 0 : 0.45;
              var quoteEndBase =
                LINES_AT_BASE + Math.max(0, lineEls.length - 1) * STAG_BASE + LINE_BASE;
              var bylineAtBase = Math.max(0, quoteEndBase - 0.15);
              var drawAtBase = quoteEndBase + 0.1;
              var inEndBase = Math.max(
                CARD_BASE,
                bylineAtBase + BYLINE_BASE,
                pathEl ? drawAtBase + DRAW_BASE : 0,
              );
              var s = duration < inEndBase + OUT_BASE ? duration / (inEndBase + OUT_BASE) : 1;
              var OUT = OUT_BASE * s;
              var OUT_START = duration - OUT;

              gsap.set(card, { opacity: 0, y: "4cqh" });
              gsap.set(lineEls, { yPercent: 130, opacity: 0 });
              gsap.set(bylineEl, { opacity: 0, y: "2.5cqh" });

              var tl = gsap.timeline({ paused: true });

              // IN: the card surface settles up.
              tl.fromTo(
                card,
                { opacity: 0, y: "4cqh" },
                { opacity: 1, y: "0cqh", duration: CARD_BASE * s, ease: "power3.out" },
                0,
              );

              // Quote lines rise through their soft masks, top to bottom.
              lineEls.forEach(function (line, i) {
                tl.fromTo(
                  line,
                  { yPercent: 130, opacity: 0 },
                  {
                    yPercent: 0,
                    opacity: 1,
                    duration: LINE_BASE * s,
                    ease: "power3.out",
                  },
                  (LINES_AT_BASE + i * STAG_BASE) * s,
                );
              });

              // Byline fades up, overlapping the last line's landing.
              tl.fromTo(
                bylineEl,
                { opacity: 0, y: "2.5cqh" },
                { opacity: 1, y: "0cqh", duration: BYLINE_BASE * s, ease: "power3.out" },
                bylineAtBase * s,
              );

              // DRAW: the underline after the quote lands; both dash endpoints
              // explicit so any seek is correct without playing through. At
              // dashoffset == length the round cap still paints a dot at the
              // path start, so the ink is opacity 0 until the exact cue frame.
              if (pathEl) {
                var length = pathEl.getTotalLength();
                pathEl.style.strokeDasharray = String(length);
                gsap.set(pathEl, { strokeDashoffset: length, opacity: 0 });
                tl.set(pathEl, { opacity: 1 }, drawAtBase * s);
                tl.fromTo(
                  pathEl,
                  { strokeDashoffset: length },
                  { strokeDashoffset: 0, duration: DRAW_BASE * s, ease: "power2.inOut" },
                  drawAtBase * s,
                );
              }

              // HOLD: dead still.

              // OUT: optional departure; exit none holds until the frame cuts.
              if (exit === "fade") {
                tl.to(card, { opacity: 0, duration: OUT, ease: "power2.in" }, OUT_START);
              } else if (exit === "up") {
                tl.to(card, { opacity: 0, y: "-6cqh", duration: OUT, ease: "power2.in" }, OUT_START);
              }

              tl.seek(0);
              window.__timelines = window.__timelines || {};
              window.__timelines[compositionId] = tl;
            })();
          </script>
        </div>
      </template>
    </body>
  </html>
  ```
</Accordion>

Tagged `motion-primitive` `proof` `testimonial` `quote` `social-proof` `deterministic`.

## Related topics

* [Browse the complete Catalog](/catalog)
* [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
* [Build a richer composition](/go-further)


## Related topics

- [Testimonial Card](/catalog/components/testimonial-card.md)
- [Capstone — every technique, one journey](/prompting/capstone.md)
- [Logo Wall](/catalog/components/logo-wall.md)
