> ## 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.

# Decline Chart

> A metric line draws downward as its value counts down and the ambient background darkens in lockstep.

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="decline-chart 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/decline-chart.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/decline-chart.png");document.body.appendChild(p)});<\/script></body></html>`} />

## Install

<InstallCommand command="npx hyperframes add decline-chart" />

That writes one file: `compositions/components/decline-chart.html`.

## Paste it into your composition

Open `compositions/components/decline-chart.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                               |
| ------------- | ----------- | ---------------- | ------------------------------------------ |
| `label`       | `Retention` | string           | Label shown above the declining value.     |
| `start_value` | `82`        | 0 to 100, step 1 | Metric value at the start of the decline.  |
| `end_value`   | `34`        | 0 to 100, step 1 | Metric value at the bottom of the decline. |

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="decline-chart"
  data-composition-src="compositions/components/decline-chart.html"
  data-variable-values='{"label":"Retention","start_value":82,"end_value":34}'
></div>
```

## Source

<Accordion title={`decline-chart.html`}>
  ```html theme={null}
  <!doctype html>
  <!--
    decline-chart: HyperFrames video primitive

    One metric line draws downward while its numeric value counts down and the
    ambient background dims and desaturates from the same progress value.

    Envelope:
      IN_BASE  = 0.55s  chart frame settles into view
      HOLD     = elastic decline draw, countdown, and ambient darkening
      OUT_BASE = 0.45s  bottomed-out endpoint locks in with no recovery
      If D is shorter than IN_BASE + OUT_BASE, IN and OUT compress together
      and HOLD collapses to zero. Longer mounts stretch HOLD only.

    Variables:
      label       (string, default "Retention")
      start_value (number, default 82)
      end_value   (number, default 34)
  -->
  <html
    lang="en"
    data-composition-id="decline-chart"
    data-composition-duration="4"
    data-composition-variables='[
      { "id": "label", "type": "string", "role": "content", "label": "Metric label", "description": "Label shown above the declining value.", "default": "Retention" },
      { "id": "start_value", "type": "number", "role": "content", "label": "Start value", "description": "Metric value at the start of the decline.", "default": 82, "min": 0, "max": 100, "step": 1 },
      { "id": "end_value", "type": "number", "role": "content", "label": "End value", "description": "Metric value at the bottom of the decline.", "default": 34, "min": 0, "max": 100, "step": 1 }
    ]'
  >
    <head>
      <meta charset="UTF-8" />
      <title>Decline Chart</title>
    </head>
    <body>
      <template>
        <div id="root" data-composition-id="decline-chart" data-duration="4" data-fps="30">
          <style>
            *,
            *::before,
            *::after {
              box-sizing: border-box;
            }

            #root {
              position: absolute;
              inset: 0;
              overflow: hidden;
              container-type: size;
              isolation: isolate;
              color: var(--fg, #f8fafc);
              font-family: var(--font-body, Inter, system-ui, sans-serif);
              pointer-events: none;
            }

            .dc-clip,
            .dc-ambient,
            .dc-gloom {
              position: absolute;
              inset: 0;
            }

            .dc-clip {
              overflow: hidden;
            }

            .dc-ambient {
              background:
                radial-gradient(circle at 24% 16%, rgba(47, 129, 150, 0.55), transparent 46%),
                linear-gradient(145deg, #152f3c 0%, #101a25 48%, #0c1118 100%);
              transform: scale(1.02);
              transform-origin: center;
              will-change: filter;
            }

            .dc-gloom {
              background: #030507;
              opacity: 0;
            }

            .dc-layout {
              position: absolute;
              inset: 9cqh 9cqw 8cqh;
              display: grid;
              /* minmax(0, 1fr): a bare 1fr row inflates to the svg's intrinsic
                 aspect height (min-height auto), overflowing the card. */
              grid-template-rows: auto minmax(0, 1fr);
              gap: 5cqh;
              opacity: 0;
            }

            .dc-header {
              display: flex;
              align-items: end;
              justify-content: space-between;
              gap: 5cqw;
            }

            .dc-label {
              max-width: 58cqw;
              overflow: hidden;
              color: rgba(226, 232, 240, 0.72);
              font-size: clamp(12px, 3.6cqh, 38px);
              font-weight: 620;
              letter-spacing: 0.05em;
              line-height: 1.1;
              text-overflow: ellipsis;
              text-transform: uppercase;
              white-space: nowrap;
            }

            .dc-value {
              flex: 0 0 auto;
              color: #f8fafc;
              font-family: var(--font-display, Inter, system-ui, sans-serif);
              font-size: clamp(30px, 12cqh, 118px);
              font-variant-numeric: tabular-nums;
              font-weight: 760;
              letter-spacing: -0.07em;
              line-height: 0.8;
            }

            .dc-chart {
              width: 100%;
              height: 100%;
              overflow: visible;
            }

            .dc-grid {
              stroke: rgba(148, 163, 184, 0.18);
              stroke-width: 1;
              vector-effect: non-scaling-stroke;
            }

            .dc-line {
              fill: none;
              stroke: #fb7185;
              stroke-linecap: round;
              stroke-linejoin: round;
              /* No non-scaling-stroke here: with preserveAspectRatio=none Chromium
                 applies the dash pattern in screen space, so the user-space
                 dasharray from getTotalLength() splits the draw into two visible
                 segments. Scaled stroke keeps dashes in user space. */
              stroke-width: 4;
            }

            .dc-endpoint {
              fill: #fecdd3;
              opacity: 0;
              transform-box: fill-box;
              transform-origin: center;
            }
          </style>

          <div
            id="decline-chart-clip"
            class="dc-clip clip"
            data-start="0"
            data-duration="4"
            data-track-index="0"
          >
            <div class="dc-ambient"></div>
            <div class="dc-gloom"></div>
            <div class="dc-layout">
              <div class="dc-header">
                <div class="dc-label"></div>
                <div class="dc-value"></div>
              </div>
              <svg
                class="dc-chart"
                viewBox="0 0 260 240"
                preserveAspectRatio="none"
                aria-hidden="true"
              >
                <line class="dc-grid" x1="8" y1="55" x2="252" y2="55"></line>
                <line class="dc-grid" x1="8" y1="120" x2="252" y2="120"></line>
                <line class="dc-grid" x1="8" y1="185" x2="252" y2="185"></line>
                <path
                  class="dc-line"
                  d="M 8 24 C 28 28, 44 43, 61 56 S 93 76, 112 92 S 144 111, 163 134 S 193 159, 213 186 S 239 211, 252 222"
                ></path>
                <circle class="dc-endpoint" cx="252" cy="222" r="6"></circle>
              </svg>
            </div>
          </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");
              var layout = root.querySelector(".dc-layout");
              var ambient = root.querySelector(".dc-ambient");
              var gloom = root.querySelector(".dc-gloom");
              var label = root.querySelector(".dc-label");
              var value = root.querySelector(".dc-value");
              var line = root.querySelector(".dc-line");
              var endpoint = root.querySelector(".dc-endpoint");
              var lineLength = line.getTotalLength();
              var vars =
                window.__hyperframes && window.__hyperframes.getVariables
                  ? window.__hyperframes.getVariables()
                  : {};

              line.style.strokeDasharray = lineLength + " " + lineLength;

              var startValue = Number.isFinite(vars.start_value) ? vars.start_value : 82;
              var endValue = Number.isFinite(vars.end_value) ? vars.end_value : 34;
              var labelText = vars.label == null ? "Retention" : String(vars.label);

              label.textContent = labelText;

              var IN_BASE = 0.55;
              var OUT_BASE = 0.45;
              var duration = Math.max(0.001, parseFloat(root.dataset.duration || "4"));
              var fixedTotal = IN_BASE + OUT_BASE;
              var phaseScale = duration < fixedTotal ? duration / fixedTotal : 1;
              var IN = IN_BASE * phaseScale;
              var OUT = OUT_BASE * phaseScale;
              var HOLD = Math.max(0, duration - IN - OUT);
              var OUT_START = IN + HOLD;
              var progress = { value: 0 };

              function renderProgress() {
                var p = progress.value;
                line.style.strokeDashoffset = String(lineLength * (1 - p));
                ambient.style.filter =
                  "saturate(" + (1 - p * 0.72) + ") brightness(" + (1 - p * 0.52) + ")";
                gloom.style.opacity = String(p * 0.46);
                endpoint.style.opacity = String(Math.max(0, (p - 0.94) / 0.06));
                value.textContent = String(Math.round(startValue + (endValue - startValue) * p));
              }

              renderProgress();

              var tl = gsap.timeline({ paused: true });
              tl.fromTo(
                layout,
                { opacity: 0, y: "2.5cqh" },
                { opacity: 1, y: 0, duration: IN, ease: "power2.out" },
                0,
              );

              if (HOLD > 0) {
                tl.to(
                  progress,
                  { value: 1, duration: HOLD, ease: "power2.out", onUpdate: renderProgress },
                  IN,
                );
              } else {
                tl.call(
                  function () {
                    progress.value = 1;
                    renderProgress();
                  },
                  [],
                  IN,
                );
              }

              tl.fromTo(
                endpoint,
                { scale: 0.72 },
                { scale: 1, duration: OUT, ease: "power2.out" },
                OUT_START,
              );

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

Tagged `motion-primitive` `data` `chart` `decline` `metric` `problem`.

## 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

- [Data Chart](/catalog/blocks/data-chart.md)
- [Chart Story](/catalog/components/chart-story.md)
- [Animated Bar Chart](/catalog/components/animated-bar-chart.md)
