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

# ASCII Trail Reveal

> An authored S-curve sweeps through a deterministic ASCII grid, flipping cells away to reveal a labeled panel.

# ASCII Trail Reveal

An authored S-curve sweeps through a deterministic ASCII grid, flipping cells away to reveal a labeled panel.

**ASCII Trail Reveal**

```html ascii-trail-reveal.html
<!doctype html>
<!--
  ascii-trail-reveal: HyperFrames video primitive (intros and reveals)

  Concept: a luminous actor follows an authored S-curve across an ASCII
  signal grid. Cells flip away behind the actor to reveal the real panel,
  while the lagging edge dissolves in a deterministic cascade.

  Variables:
    - label (string, default "The reveal"): text on the revealed panel.
    - accent (green | blue | violet, default "green"): trail and panel accent.
    - trail_width (narrow | wide, default "narrow"): width of the reveal tail.

  Envelope (fixed IN and OUT, elastic HOLD only, never timeScale):
    IN_BASE  = 3.20s  the actor crosses the authored path, rests briefly,
               then dissolves as the final cells complete their flip
    HOLD     = elastic = max(0, D - IN_BASE), fully revealed content drifts
               subtly and remains legible
    OUT_BASE = 0.00s  the revealed panel is the required end state
    If D is shorter than IN_BASE, IN compresses to D and HOLD is zero.

  Determinism: glyphs, cell timing jitter, and reveal order come only from
  row, column, and index math. No clock or random source participates.
-->
<html
  lang="en"
  data-composition-id="ascii-trail-reveal"
  data-composition-duration="4"
  data-composition-variables='[
    { "id": "label", "type": "string", "role": "content", "label": "Panel label", "description": "Text shown on the revealed panel.", "default": "The reveal" },
    { "id": "accent", "type": "enum", "role": "style", "label": "Accent", "description": "Color used by the trail and revealed panel.", "default": "green", "options": [{ "value": "green", "label": "Green" }, { "value": "blue", "label": "Blue" }, { "value": "violet", "label": "Violet" }] },
    { "id": "trail_width", "type": "enum", "role": "layout", "label": "Trail width", "description": "Width of the cell reveal tail around the authored path.", "default": "narrow", "options": [{ "value": "narrow", "label": "Narrow" }, { "value": "wide", "label": "Wide" }] }
  ]'
>
  <head>
    <meta charset="UTF-8" />
    <title>ASCII Trail Reveal</title>
  </head>
  <body>
    <template>
      <div id="root" data-composition-id="ascii-trail-reveal" 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;
          }

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

          .atr-panel {
            position: relative;
            width: 88cqw;
            height: 76cqh;
            overflow: hidden;
            border: 0.16cqmin solid color-mix(in srgb, var(--atr-accent) 52%, #475569);
            border-radius: 3cqmin;
            background: #111820;
            box-shadow:
              0 4cqh 12cqh rgba(0, 0, 0, 0.45),
              inset 0 0 0 0.12cqmin rgba(255, 255, 255, 0.04);
            perspective: 80cqmin;
          }

          .atr-content {
            position: absolute;
            inset: 0;
            display: grid;
            place-items: center;
            padding: 8cqw;
            background:
              radial-gradient(
                circle at 50% 45%,
                color-mix(in srgb, var(--atr-accent) 24%, transparent),
                transparent 48%
              ),
              linear-gradient(145deg, #17212c, #0c1219);
            transform-origin: 50% 50%;
          }

          .atr-label {
            max-width: 76cqw;
            color: #f8fafc;
            font-family: var(--font-display, Inter, system-ui, sans-serif);
            font-size: clamp(24px, 10cqmin, 112px);
            font-weight: 760;
            line-height: 0.96;
            letter-spacing: -0.045em;
            text-align: center;
            text-wrap: balance;
            text-shadow: 0 0 4cqmin color-mix(in srgb, var(--atr-accent) 28%, transparent);
          }

          .atr-grid {
            --atr-columns: 24;
            --atr-rows: 14;
            position: absolute;
            inset: 0;
            z-index: 2;
            display: grid;
            grid-template-columns: repeat(var(--atr-columns), 1fr);
            grid-template-rows: repeat(var(--atr-rows), 1fr);
            overflow: hidden;
            background: #0b1016;
            transform-style: preserve-3d;
          }

          .atr-cell {
            display: grid;
            place-items: center;
            min-width: 0;
            min-height: 0;
            overflow: hidden;
            border-right: 0.06cqmin solid rgba(148, 163, 184, 0.08);
            border-bottom: 0.06cqmin solid rgba(148, 163, 184, 0.08);
            background: color-mix(in srgb, #111820 94%, var(--atr-accent));
            color: color-mix(in srgb, var(--atr-accent) 72%, #dbeafe);
            font-family: var(--font-mono, ui-monospace, "SF Mono", Menlo, monospace);
            font-size: clamp(7px, 2.35cqmin, 22px);
            font-weight: 650;
            line-height: 1;
            transform-origin: 50% 50%;
            backface-visibility: hidden;
            will-change: transform, opacity;
          }

          .atr-path-layer {
            position: absolute;
            inset: 0;
            z-index: 3;
            width: 100%;
            height: 100%;
            overflow: visible;
          }

          .atr-path-guide {
            fill: none;
            stroke: color-mix(in srgb, var(--atr-accent) 34%, transparent);
            stroke-width: 2;
            stroke-linecap: round;
            stroke-dasharray: 3 14;
          }

          .atr-actor-halo {
            fill: color-mix(in srgb, var(--atr-accent) 22%, transparent);
          }

          .atr-actor-core {
            fill: #f8fafc;
            stroke: var(--atr-accent);
            stroke-width: 7;
            filter: drop-shadow(0 0 18px var(--atr-accent));
          }
        </style>

        <div
          id="ascii-trail-reveal-clip"
          class="atr-clip clip"
          data-start="0"
          data-duration="4"
          data-track-index="0"
        >
          <div class="atr-panel">
            <div class="atr-content">
              <div class="atr-label"></div>
            </div>
            <div class="atr-grid" aria-hidden="true"></div>
            <svg
              class="atr-path-layer"
              viewBox="0 0 1000 600"
              preserveAspectRatio="none"
              aria-hidden="true"
            >
              <path class="atr-path-guide"></path>
              <g id="ascii-trail-reveal-actor">
                <circle class="atr-actor-halo" r="42"></circle>
                <circle class="atr-actor-core" r="13"></circle>
              </g>
            </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 content = root.querySelector(".atr-content");
            var label = root.querySelector(".atr-label");
            var grid = root.querySelector(".atr-grid");
            var guide = root.querySelector(".atr-path-guide");
            var actor = root.querySelector("#ascii-trail-reveal-actor");
            var vars =
              window.__hyperframes && window.__hyperframes.getVariables
                ? window.__hyperframes.getVariables()
                : {};

            var labelText = vars.label == null ? "The reveal" : String(vars.label);
            var accent = vars.accent === "blue" || vars.accent === "violet" ? vars.accent : "green";
            var trailWidth = vars.trail_width === "wide" ? "wide" : "narrow";
            var accentColors = {
              green: "var(--brand, #4ade80)",
              blue: "var(--accent, #38bdf8)",
              violet: "var(--accent-2, #a78bfa)",
            };

            label.textContent = labelText;
            root.style.setProperty("--atr-accent", accentColors[accent]);

            var COLS = 24;
            var ROWS = 14;
            var GLYPHS = " .:-=+*#%@01/\\<>[]{}";
            var PATH_POINTS = [
              [-80, 460],
              [340, 460],
              [660, 140],
              [1080, 140],
            ];
            var cells = [];

            function sCurvePoint(progress) {
              var inverse = 1 - progress;
              var startWeight = inverse * inverse * inverse;
              var firstWeight = 3 * inverse * inverse * progress;
              var secondWeight = 3 * inverse * progress * progress;
              var endWeight = progress * progress * progress;
              return {
                x:
                  startWeight * PATH_POINTS[0][0] +
                  firstWeight * PATH_POINTS[1][0] +
                  secondWeight * PATH_POINTS[2][0] +
                  endWeight * PATH_POINTS[3][0],
                y:
                  startWeight * PATH_POINTS[0][1] +
                  firstWeight * PATH_POINTS[1][1] +
                  secondWeight * PATH_POINTS[2][1] +
                  endWeight * PATH_POINTS[3][1],
              };
            }

            guide.setAttribute(
              "d",
              "M " +
                PATH_POINTS[0].join(" ") +
                " C " +
                PATH_POINTS.slice(1)
                  .map(function (point) {
                    return point.join(" ");
                  })
                  .join(" "),
            );

            for (var row = 0; row < ROWS; row += 1) {
              for (var column = 0; column < COLS; column += 1) {
                var index = row * COLS + column;
                var cell = document.createElement("span");
                var glyphIndex = (index * 17 + row * 11 + column * 7) % GLYPHS.length;
                cell.className = "atr-cell";
                cell.textContent = GLYPHS.charAt(glyphIndex);
                grid.appendChild(cell);
                cells.push({ element: cell, row: row, column: column, index: index });
              }
            }

            var IN_BASE = 3.2;
            var duration = Math.max(0.001, parseFloat(root.dataset.duration || "4"));
            var IN = Math.min(IN_BASE, duration);
            var HOLD = Math.max(0, duration - IN);
            var phaseScale = IN / IN_BASE;
            var actorState = { progress: 0 };

            function updateActor() {
              var point = sCurvePoint(actorState.progress);
              actor.setAttribute("transform", "translate(" + point.x + " " + point.y + ")");
            }

            gsap.set(content, { scale: 0.985 });
            gsap.set(guide, { opacity: 0 });
            gsap.set(actor, { opacity: 0 });
            updateActor();

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

            tl.to(content, { scale: 1, duration: IN, ease: "power2.out" }, 0);
            tl.to(
              guide,
              { opacity: 0.72, duration: 0.24 * phaseScale, ease: "power2.out" },
              0.18 * phaseScale,
            );
            tl.to(
              actor,
              { opacity: 1, duration: 0.24 * phaseScale, ease: "power2.out" },
              0.22 * phaseScale,
            );
            tl.to(
              actorState,
              {
                progress: 1,
                duration: 2.35 * phaseScale,
                ease: "power2.out",
                onUpdate: updateActor,
              },
              0.34 * phaseScale,
            );

            cells.forEach(function (cellData) {
              var progress = (cellData.column + 0.5) / COLS;
              var pathPoint = sCurvePoint(progress);
              var cellY = ((cellData.row + 0.5) / ROWS) * 600;
              var distanceFromPath = Math.abs(cellY - pathPoint.y) / 600;
              var distanceLag = distanceFromPath * (trailWidth === "wide" ? 0.5 : 0.8);
              var indexJitter = ((cellData.index * 37) % 11) * 0.009;
              var revealAt = (0.42 + progress * 1.92 + distanceLag + indexJitter) * phaseScale;
              var flipDuration = (0.2 + ((cellData.index * 13) % 5) * 0.018) * phaseScale;

              tl.to(
                cellData.element,
                {
                  rotationY: 88,
                  scaleX: 0.3,
                  opacity: 0,
                  duration: flipDuration,
                  ease: "power2.in",
                },
                revealAt,
              );
            });

            tl.to(
              guide,
              { opacity: 0, duration: 0.24 * phaseScale, ease: "power2.in" },
              2.82 * phaseScale,
            );
            tl.to(
              actor,
              { opacity: 0, duration: 0.24 * phaseScale, ease: "power2.in" },
              2.92 * phaseScale,
            );

            if (HOLD > 0) {
              tl.to(
                content,
                {
                  scale: 1.008,
                  duration: HOLD / 2,
                  ease: "sine.inOut",
                  yoyo: true,
                  repeat: 1,
                },
                IN,
              );
            }

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

Copy npx hyperframes add ascii-trail-reveal to the clipboard

That writes one file: `compositions/components/ascii-trail-reveal.html`.

## Paste it into your composition

Open `compositions/components/ascii-trail-reveal.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` | `The reveal` | string | Text shown on the revealed panel. |
| `accent` | `green` | `green`, `blue`, `violet` | Color used by the trail and revealed panel. |
| `trail_width` | `narrow` | `narrow`, `wide` | Width of the cell reveal tail around the authored path. |

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
<div
  data-composition-id="ascii-trail-reveal"
  data-composition-src="compositions/components/ascii-trail-reveal.html"
  data-variable-values='{"label":"The reveal","accent":"green","trail_width":"narrow"}'
></div>
```

{/* hf:generated-footer */}

Tagged `motion-primitive` `intro` `reveal` `ascii` `trail` `grid`.

## Related topics

* [Browse the complete Catalog](/catalog.md)
* [Add assets and Catalog items in the browser editor](/studio/assets-and-blocks.md)
* [Put blocks together into one video](/concepts/compositions.md)
