# Highlight Marker Text Reveal

> Text that reveals under a highlighter stroke as you scroll — expressive for headlines and storytelling.

**Definition:** Highlight Marker Text Reveal 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/highlight-marker-text-reveal
- Full-page preview: https://html.bosconvertpro.com/collection/highlight-marker-text-reveal/full
- Live demo: https://html.bosconvertpro.com/demos/highlight-marker-text-reveal/index.html
- Markdown mirror: https://html.bosconvertpro.com/collection/highlight-marker-text-reveal/index.md
- License: https://html.bosconvertpro.com/terms

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

## FAQ
### What is Highlight Marker Text Reveal?

Highlight Marker Text Reveal is a text UI animation from collections.html. Text that reveals under a highlighter stroke as you scroll — expressive for headlines and storytelling.

### What is Highlight Marker Text Reveal built with?

Highlight Marker Text Reveal 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 Highlight Marker Text Reveal in my project?

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

### Is Highlight Marker Text Reveal free to use?

Yes. The Highlight Marker Text Reveal 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>Highlight Marker Text Reveal — 4 Versions</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=Inter:wght@400;500;600&family=Syne:wght@700;800&display=swap"
      rel="stylesheet"
    />
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/lenis@1.2.3/dist/lenis.css"
    />
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <div class="grain" aria-hidden="true"></div>

    <!-- ═══════════════════════════════════════════
         VERSION 01 — Right reveal · white marker
         ═══════════════════════════════════════════ -->
    <section class="demo demo--v1" data-demo="1">
      <div class="demo__glow" aria-hidden="true"></div>
      <header class="demo__meta">
        <div class="demo__meta-left">
          <span class="demo__index">01</span>
          <span class="demo__divider" aria-hidden="true"></span>
          <span class="demo__label">Right · White</span>
        </div>
        <span class="demo__attrs">direction · right &nbsp;·&nbsp; theme · white</span>
      </header>

      <div class="demo__stage">
        <h1
          class="highlight-title"
          data-highlight-marker-reveal
          data-marker-direction="right"
          data-marker-theme="white"
          data-marker-stagger="110"
        >
          Here's a text reveal that looks like a highlight marker
        </h1>
      </div>

      <div class="demo__scroll-hint" data-scroll-hint aria-hidden="true">
        <span>Scroll</span>
        <span class="demo__scroll-line"></span>
      </div>
    </section>

    <!-- ═══════════════════════════════════════════
         VERSION 02 — Left reveal · pink marker
         ═══════════════════════════════════════════ -->
    <section class="demo demo--v2" data-demo="2">
      <div class="demo__glow" aria-hidden="true"></div>
      <header class="demo__meta">
        <div class="demo__meta-left">
          <span class="demo__index">02</span>
          <span class="demo__divider" aria-hidden="true"></span>
          <span class="demo__label">Left · Pink</span>
        </div>
        <span class="demo__attrs">direction · left &nbsp;·&nbsp; theme · pink</span>
      </header>

      <div class="demo__stage">
        <h2
          class="highlight-title"
          data-highlight-marker-reveal
          data-marker-direction="left"
          data-marker-theme="pink"
          data-marker-stagger="130"
        >
          Control theme, direction, stagger, and more with attributes
        </h2>
      </div>
    </section>

    <!-- ═══════════════════════════════════════════
         VERSION 03 — Up reveal · lime accent
         ═══════════════════════════════════════════ -->
    <section class="demo demo--v3" data-demo="3">
      <div class="demo__glow" aria-hidden="true"></div>
      <header class="demo__meta">
        <div class="demo__meta-left">
          <span class="demo__index">03</span>
          <span class="demo__divider" aria-hidden="true"></span>
          <span class="demo__label">Up · Accent</span>
        </div>
        <span class="demo__attrs"
          >direction · up &nbsp;·&nbsp; stagger · end</span
        >
      </header>

      <div class="demo__stage">
        <h2
          class="highlight-title"
          data-highlight-marker-reveal
          data-marker-direction="up"
          data-marker-theme="#7CFF6B"
          data-marker-stagger-start="end"
          data-marker-stagger="140"
        >
          These will reveal bottom to top, for a unique look
        </h2>
      </div>
    </section>

    <!-- ═══════════════════════════════════════════
         VERSION 04 — Down reveal · body copy
         ═══════════════════════════════════════════ -->
    <section class="demo demo--v4" data-demo="4">
      <div class="demo__glow" aria-hidden="true"></div>
      <header class="demo__meta">
        <div class="demo__meta-left">
          <span class="demo__index">04</span>
          <span class="demo__divider" aria-hidden="true"></span>
          <span class="demo__label">Down · Body</span>
        </div>
        <span class="demo__attrs"
          >direction · down &nbsp;·&nbsp; theme · lavender</span
        >
      </header>

      <div class="demo__stage demo__stage--body">
        <p
          class="highlight-body"
          data-highlight-marker-reveal
          data-marker-direction="down"
          data-marker-theme="lavender"
          data-marker-stagger="100"
          data-marker-scroll-start="top 85%"
        >
          Longer lines still work — every row gets its own marker wipe
        </p>
      </div>
    </section>

    <!-- Floating version nav -->
    <nav class="version-nav" aria-label="Demo versions">
      <div class="version-nav__track" aria-hidden="true">
        <span class="version-nav__indicator" data-nav-indicator></span>
      </div>
      <button type="button" data-nav="1" class="is-active" aria-label="Version 1">
        <span>01</span>
      </button>
      <button type="button" data-nav="2" aria-label="Version 2">
        <span>02</span>
      </button>
      <button type="button" data-nav="3" aria-label="Version 3">
        <span>03</span>
      </button>
      <button type="button" data-nav="4" aria-label="Version 4">
        <span>04</span>
      </button>
    </nav>

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

## Source — styles.css
````css
/* ─────────────────────────────────────────────
   Highlight Marker Text Reveal
   Professional · smooth · production-ready
   ───────────────────────────────────────────── */

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

:root {
  --font-display: "Syne", system-ui, sans-serif;
  --font-ui: "Inter", system-ui, sans-serif;

  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in-out-smooth: cubic-bezier(0.65, 0, 0.35, 1);

  --bg-v1: #160816;
  --bg-v2: #0a0a0c;
  --bg-v3: #050f08;
  --bg-v4: #0b0b12;

  --text-v1: #f0e0f6;
  --text-v2: #f4f4f5;
  --text-v3: #e6ffe2;
  --text-v4: #ddd8f0;

  --nav-size: 2.5rem;
  --nav-gap: 0.35rem;
}

html {
  scroll-behavior: auto; /* Lenis owns scrolling */
}

html.lenis,
html.lenis body {
  height: auto;
}

body {
  font-family: var(--font-ui);
  color: #fff;
  background: #050505;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

/* ── Film grain (subtle polish) ───────────── */

.grain {
  position: fixed;
  inset: 0;
  z-index: 40;
  pointer-events: none;
  opacity: 0.035;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-repeat: repeat;
  background-size: 180px 180px;
}

/* ── Sections — exact full viewport ───────── */

.demo {
  position: relative;
  isolation: isolate;
  width: 100%;
  height: 100svh;
  min-height: 100svh;
  max-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  padding: 0 clamp(1.25rem, 5vw, 4rem);
  text-align: center;
  overflow: hidden;
}

.demo--v1 {
  background: var(--bg-v1);
  color: var(--text-v1);
}

.demo--v2 {
  background: var(--bg-v2);
  color: var(--text-v2);
}

.demo--v3 {
  background: var(--bg-v3);
  color: var(--text-v3);
}

.demo--v4 {
  background: var(--bg-v4);
  color: var(--text-v4);
}

/* Soft ambient glow per version */
.demo__glow {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0.55;
}

.demo--v1 .demo__glow {
  background:
    radial-gradient(
      ellipse 70% 55% at 50% 58%,
      rgba(180, 60, 200, 0.18) 0%,
      transparent 68%
    ),
    radial-gradient(
      ellipse 40% 30% at 80% 20%,
      rgba(120, 40, 140, 0.12) 0%,
      transparent 70%
    );
}

.demo--v2 .demo__glow {
  background:
    radial-gradient(
      ellipse 65% 50% at 50% 55%,
      rgba(199, 0, 239, 0.12) 0%,
      transparent 70%
    ),
    radial-gradient(
      ellipse 35% 28% at 20% 25%,
      rgba(255, 255, 255, 0.04) 0%,
      transparent 70%
    );
}

.demo--v3 .demo__glow {
  background:
    radial-gradient(
      ellipse 70% 55% at 50% 55%,
      rgba(124, 255, 107, 0.08) 0%,
      transparent 68%
    ),
    radial-gradient(
      ellipse 30% 25% at 75% 30%,
      rgba(80, 200, 100, 0.06) 0%,
      transparent 70%
    );
}

.demo--v4 .demo__glow {
  background:
    radial-gradient(
      ellipse 65% 50% at 50% 55%,
      rgba(184, 164, 255, 0.1) 0%,
      transparent 70%
    ),
    radial-gradient(
      ellipse 35% 28% at 25% 20%,
      rgba(140, 120, 220, 0.07) 0%,
      transparent 70%
    );
}

/* ── Meta header ──────────────────────────── */

.demo__meta {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.65rem 1.5rem;
  padding: clamp(1.35rem, 3.5vw, 2.25rem) clamp(1.35rem, 4vw, 2.75rem);
  font-family: var(--font-ui);
  pointer-events: none;
}

.demo__meta-left {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.demo__index {
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  opacity: 0.9;
}

.demo__divider {
  width: 1.25rem;
  height: 1px;
  background: currentColor;
  opacity: 0.28;
}

.demo__label {
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  opacity: 0.55;
}

.demo__attrs {
  font-size: 0.65rem;
  font-weight: 400;
  letter-spacing: 0.06em;
  text-transform: lowercase;
  opacity: 0.35;
  font-variant-numeric: tabular-nums;
}

/* ── Stage — shared center axis for all text ─ */

.demo__stage {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 100%;
  max-width: min(92vw, 1100px);
  margin: 0 auto;
  text-align: center;
}

.demo__stage--body {
  max-width: min(88vw, 920px);
}

/* ── Display titles — same size on every section ── */

.highlight-title,
.highlight-body {
  width: 100%;
  max-width: 16ch;
  margin: 0 auto;
  font-family: var(--font-display);
  /* Shared size — slightly smaller for clearer full-word reads */
  font-size: clamp(2.15rem, 6.2vw, 5.25rem);
  font-weight: 800;
  line-height: 0.92;
  letter-spacing: -0.035em;
  text-transform: uppercase;
  text-align: center;
  text-wrap: balance;
  opacity: 1;
}

/* ── Marker reveal core ───────────────────── */

[data-highlight-marker-reveal] {
  visibility: hidden;
  text-align: center;
}

/*
 * Each SplitText line is a centered row:
 * width: fit-content → bar only as wide as the words
 * margin auto → lines stack centered with each other
 */
[data-highlight-marker-reveal] .highlight-marker-line {
  display: block !important;
  width: fit-content !important;
  max-width: 100%;
  margin-left: auto !important;
  margin-right: auto !important;
  margin-top: -0.06em;
  margin-bottom: -0.06em;
  text-align: center;
  transform: translateZ(0);
  backface-visibility: hidden;
}

.highlight-marker-bar {
  position: absolute;
  inset: -0.06em 0;
  z-index: 1;
  pointer-events: none;
  transform: translateZ(0);
  backface-visibility: hidden;
}

.highlight-body .highlight-marker-line {
  margin-top: -0.04em;
  margin-bottom: -0.04em;
}

.highlight-body .highlight-marker-bar {
  inset: -0.04em 0;
}

/* ── Scroll hint (first section only) ─────── */

.demo__scroll-hint {
  position: absolute;
  bottom: clamp(1.5rem, 4vw, 2.5rem);
  left: 50%;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.65rem;
  transform: translateX(-50%);
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  opacity: 0.4;
  pointer-events: none;
}

.demo__scroll-line {
  display: block;
  width: 1px;
  height: 2.25rem;
  background: linear-gradient(
    to bottom,
    currentColor 0%,
    transparent 100%
  );
  transform-origin: top center;
  animation: scrollPulse 2.4s var(--ease-in-out-smooth) infinite;
}

@keyframes scrollPulse {
  0%,
  100% {
    transform: scaleY(1);
    opacity: 0.55;
  }
  50% {
    transform: scaleY(0.45);
    opacity: 0.15;
  }
}

/* ── Version nav ──────────────────────────── */

.version-nav {
  position: fixed;
  right: clamp(0.85rem, 2.2vw, 1.4rem);
  top: 50%;
  z-index: 50;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--nav-gap);
  transform: translateY(-50%);
}

.version-nav__track {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.version-nav__indicator {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--nav-size);
  height: var(--nav-size);
  border-radius: 999px;
  background: #fff;
  box-shadow:
    0 0 0 1px rgba(255, 255, 255, 0.08),
    0 8px 28px rgba(0, 0, 0, 0.35);
  will-change: transform;
  transition: none; /* GSAP drives motion */
}

.version-nav button {
  position: relative;
  z-index: 1;
  width: var(--nav-size);
  height: var(--nav-size);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 999px;
  background: rgba(8, 8, 10, 0.42);
  backdrop-filter: blur(14px) saturate(1.2);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  color: rgba(255, 255, 255, 0.48);
  font-family: var(--font-ui);
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  cursor: pointer;
  transition:
    color 0.4s var(--ease-out-expo),
    border-color 0.4s var(--ease-out-expo),
    background 0.4s var(--ease-out-expo);
}

.version-nav button span {
  display: block;
  transition: transform 0.45s var(--ease-out-expo);
}

.version-nav button:hover {
  color: rgba(255, 255, 255, 0.92);
  border-color: rgba(255, 255, 255, 0.28);
  background: rgba(20, 20, 24, 0.55);
}

.version-nav button:hover span {
  transform: scale(1.06);
}

.version-nav button.is-active {
  color: #0a0a0a;
  border-color: transparent;
  background: transparent;
}

.version-nav button:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.55);
  outline-offset: 3px;
}

/* ── Reduced motion ───────────────────────── */

@media (prefers-reduced-motion: reduce) {
  .demo__scroll-line {
    animation: none;
  }

  [data-highlight-marker-reveal] {
    visibility: visible;
  }

  .highlight-marker-bar {
    display: none !important;
  }

  .grain {
    display: none;
  }
}

/* ── Small screens ────────────────────────── */

@media (max-width: 720px) {
  .demo__attrs {
    display: none;
  }

  .highlight-title,
  .highlight-body {
    max-width: 14ch;
    font-size: clamp(1.95rem, 8.5vw, 3.15rem);
  }

  .version-nav {
    right: 0.55rem;
  }

  :root {
    --nav-size: 2.2rem;
  }
}

@media (max-width: 480px) {
  .demo {
    padding-left: 1rem;
    padding-right: 3.1rem; /* room for nav */
  }
}
````

## Source — script.js
````javascript
/**
 * Highlight Marker Text Reveal
 * ─────────────────────────────────────────────────────────────
 * Premium marker-bar text reveal driven by GSAP SplitText +
 * ScrollTrigger, with Lenis smooth scrolling.
 *
 * 
 */

(() => {
  "use strict";

  gsap.registerPlugin(ScrollTrigger, SplitText);

  /** Shared Lenis instance (null when unavailable / reduced motion) */
  let lenis = null;

  /* ─────────────────────────────────────────
     Smooth scroll (Lenis ↔ ScrollTrigger)
     ───────────────────────────────────────── */

  function initSmoothScroll() {
    if (typeof Lenis === "undefined") return null;

    const prefersReduce = window.matchMedia(
      "(prefers-reduced-motion: reduce)"
    ).matches;
    if (prefersReduce) return null;

    const instance = new Lenis({
      duration: 1.35,
      easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
      smoothWheel: true,
      touchMultiplier: 1.15,
      wheelMultiplier: 0.92,
    });

    instance.on("scroll", ScrollTrigger.update);

    // Drive Lenis from GSAP’s ticker for perfect sync
    gsap.ticker.add((time) => {
      instance.raf(time * 1000);
    });
    gsap.ticker.lagSmoothing(0);

    return instance;
  }

  /* ─────────────────────────────────────────
     Core highlight-marker engine
     ───────────────────────────────────────── */

  function initHighlightMarkerTextReveal() {
    const defaults = {
      direction: "right",
      theme: "pink",
      scrollStart: "top 88%",
      staggerStart: "start",
      stagger: 110,
      // Slightly longer + custom ease = smoother “marker wipe”
      barDuration: 0.95,
      // Expo-ish in-out — accelerates into the wipe, eases out cleanly
      barEase: "expo.inOut",
    };

    const colorMap = {
      pink: "#C700EF",
      white: "#FFFFFF",
      lavender: "#B8A4FF",
      lime: "#7CFF6B",
      black: "#0A0A0A",
    };

    const directionMap = {
      right: { prop: "scaleX", origin: "right center" },
      left: { prop: "scaleX", origin: "left center" },
      up: { prop: "scaleY", origin: "center top" },
      down: { prop: "scaleY", origin: "center bottom" },
    };

    function resolveColor(value) {
      if (!value) return colorMap.pink;
      if (colorMap[value]) return colorMap[value];
      if (value.startsWith("--")) {
        const resolved =
          getComputedStyle(document.documentElement)
            .getPropertyValue(value)
            .trim() ||
          getComputedStyle(document.body).getPropertyValue(value).trim();
        return resolved || value;
      }
      return value;
    }

    function createBar(color, origin) {
      const bar = document.createElement("div");
      bar.className = "highlight-marker-bar";
      bar.setAttribute("aria-hidden", "true");
      Object.assign(bar.style, {
        backgroundColor: color,
        transformOrigin: origin,
      });
      return bar;
    }

    function cleanupElement(el) {
      if (!el._highlightMarkerReveal) return;
      el._highlightMarkerReveal.timeline?.kill();
      el._highlightMarkerReveal.scrollTrigger?.kill();
      el._highlightMarkerReveal.split?.revert();
      el.querySelectorAll(".highlight-marker-bar").forEach((b) => b.remove());
      delete el._highlightMarkerReveal;
    }

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

    if (reduceMotion) {
      document
        .querySelectorAll("[data-highlight-marker-reveal]")
        .forEach((el) => {
          gsap.set(el, { autoAlpha: 1, visibility: "visible" });
        });
      return;
    }

    document
      .querySelectorAll("[data-highlight-marker-reveal]")
      .forEach(cleanupElement);

    const elements = document.querySelectorAll(
      "[data-highlight-marker-reveal]"
    );
    if (!elements.length) return;

    elements.forEach((el) => {
      const direction =
        el.getAttribute("data-marker-direction") || defaults.direction;
      const theme = el.getAttribute("data-marker-theme") || defaults.theme;
      const scrollStart =
        el.getAttribute("data-marker-scroll-start") || defaults.scrollStart;
      const staggerStart =
        el.getAttribute("data-marker-stagger-start") || defaults.staggerStart;
      const staggerOffset =
        (parseFloat(el.getAttribute("data-marker-stagger")) ||
          defaults.stagger) / 1000;
      const color = resolveColor(theme);
      const dirConfig = directionMap[direction] || directionMap.right;

      // Per-element duration override (optional)
      const barDuration =
        parseFloat(el.getAttribute("data-marker-duration")) ||
        defaults.barDuration;

      el._highlightMarkerReveal = {};

      const split = SplitText.create(el, {
        type: "lines",
        linesClass: "highlight-marker-line",
        autoSplit: true,
        onSplit(self) {
          const instance = el._highlightMarkerReveal;

          instance.timeline?.kill();
          instance.scrollTrigger?.kill();
          el.querySelectorAll(".highlight-marker-bar").forEach((b) =>
            b.remove()
          );

          const lines = self.lines;
          if (!lines?.length) {
            gsap.set(el, { autoAlpha: 1, visibility: "visible" });
            return;
          }

          const tl = gsap.timeline({
            paused: true,
            defaults: {
              ease: defaults.barEase,
              force3D: true,
            },
          });

          lines.forEach((line, i) => {
            gsap.set(line, {
              position: "relative",
              overflow: "hidden",
              // Prevent sub-pixel gaps between stacked marker bars
              verticalAlign: "top",
            });

            const bar = createBar(color, dirConfig.origin);
            line.appendChild(bar);

            // Cover fully before reveal
            gsap.set(bar, {
              [dirConfig.prop]: 1,
              force3D: true,
            });

            const staggerIndex =
              staggerStart === "end" ? lines.length - 1 - i : i;

            // Marker wipe: scale → 0 toward the named edge
            tl.to(
              bar,
              {
                [dirConfig.prop]: 0,
                duration: barDuration,
                ease: defaults.barEase,
                // Avoid residual hairline after wipe
                onComplete: () => {
                  gsap.set(bar, { autoAlpha: 0 });
                },
              },
              staggerIndex * staggerOffset
            );
          });

          // Show text only after bars are painted on top
          gsap.set(el, { autoAlpha: 1, visibility: "visible" });

          const st = ScrollTrigger.create({
            trigger: el,
            start: scrollStart,
            once: true,
            // Small anticipatory delay feels more intentional
            onEnter: () => {
              // Micro lead-in so the wipe never feels abrupt
              gsap.delayedCall(0.06, () => tl.play());
            },
          });

          // Already in viewport on load
          if (st.isActive) {
            gsap.delayedCall(0.18, () => tl.play());
          }

          instance.timeline = tl;
          instance.scrollTrigger = st;
        },
      });

      el._highlightMarkerReveal.split = split;
    });
  }

  /* ─────────────────────────────────────────
     Version nav + sliding indicator
     ───────────────────────────────────────── */

  function initVersionNav() {
    const nav = document.querySelector(".version-nav");
    const buttons = [...document.querySelectorAll(".version-nav [data-nav]")];
    const sections = [...document.querySelectorAll(".demo[data-demo]")];
    const indicator = document.querySelector("[data-nav-indicator]");
    if (!nav || !buttons.length || !sections.length) return;

    function moveIndicator(btn, animate = true) {
      if (!indicator || !btn) return;
      const navRect = nav.getBoundingClientRect();
      const btnRect = btn.getBoundingClientRect();
      const y = btnRect.top - navRect.top;

      gsap.to(indicator, {
        y,
        duration: animate ? 0.55 : 0,
        ease: "expo.out",
        overwrite: "auto",
      });
    }

    function setActive(id, animate = true) {
      buttons.forEach((btn) => {
        const active = btn.getAttribute("data-nav") === id;
        btn.classList.toggle("is-active", active);
        if (active) moveIndicator(btn, animate);
      });
    }

    // Initial position
    const initial = buttons.find((b) => b.classList.contains("is-active"));
    if (initial) moveIndicator(initial, false);

    buttons.forEach((btn) => {
      btn.addEventListener("click", () => {
        const id = btn.getAttribute("data-nav");
        const target = document.querySelector(`.demo[data-demo="${id}"]`);
        if (!target) return;

        setActive(id);

        if (lenis) {
          lenis.scrollTo(target, {
            offset: 0,
            duration: 1.45,
            easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
          });
        } else {
          target.scrollIntoView({ behavior: "smooth", block: "start" });
        }
      });
    });

    sections.forEach((section) => {
      ScrollTrigger.create({
        trigger: section,
        start: "top 55%",
        end: "bottom 55%",
        onEnter: () => setActive(section.getAttribute("data-demo")),
        onEnterBack: () => setActive(section.getAttribute("data-demo")),
      });
    });

    // Keep indicator aligned on resize
    window.addEventListener(
      "resize",
      () => {
        const active = buttons.find((b) => b.classList.contains("is-active"));
        if (active) moveIndicator(active, false);
      },
      { passive: true }
    );
  }

  /* ─────────────────────────────────────────
     Scroll hint fade on first interaction
     ───────────────────────────────────────── */

  function initScrollHint() {
    const hint = document.querySelector("[data-scroll-hint]");
    if (!hint) return;

    ScrollTrigger.create({
      trigger: document.body,
      start: 40,
      once: true,
      onEnter: () => {
        gsap.to(hint, {
          autoAlpha: 0,
          y: -8,
          duration: 0.6,
          ease: "power2.out",
        });
      },
    });
  }

  /* ─────────────────────────────────────────
     Meta / chrome entrance (subtle polish)
     ───────────────────────────────────────── */

  function initChromeEntrance() {
    const reduceMotion = window.matchMedia(
      "(prefers-reduced-motion: reduce)"
    ).matches;
    if (reduceMotion) return;

    const firstMeta = document.querySelector(".demo--v1 .demo__meta");
    const nav = document.querySelector(".version-nav");
    const hint = document.querySelector("[data-scroll-hint]");

    if (firstMeta) {
      gsap.fromTo(
        firstMeta,
        { autoAlpha: 0, y: -10 },
        { autoAlpha: 1, y: 0, duration: 0.9, ease: "power3.out", delay: 0.15 }
      );
    }

    if (nav) {
      gsap.fromTo(
        nav,
        { autoAlpha: 0, x: 12 },
        { autoAlpha: 1, x: 0, duration: 0.9, ease: "power3.out", delay: 0.35 }
      );
    }

    if (hint) {
      gsap.fromTo(
        hint,
        { autoAlpha: 0 },
        { autoAlpha: 0.4, duration: 1, ease: "power2.out", delay: 1.1 }
      );
    }
  }

  /* ─────────────────────────────────────────
     Boot
     ───────────────────────────────────────── */

  function boot() {
    lenis = initSmoothScroll();
    initHighlightMarkerTextReveal();
    initVersionNav();
    initScrollHint();
    initChromeEntrance();

    // One refresh after layout settles (fonts / split lines)
    requestAnimationFrame(() => {
      ScrollTrigger.refresh();
    });
  }

  document.addEventListener("DOMContentLoaded", () => {
    if (document.fonts?.ready) {
      document.fonts.ready.then(boot).catch(boot);
    } else {
      boot();
    }
  });
})();
````
