web design
COMPONENT X / SCROLL-SCRUBBED CREED · WORD-BY-WORD SCRUB

The Creed

The Doctrine

Noise, sold as information.

We return the instruments to the individual.

Research without permission. Backtest without limits. Deploy without intermediaries.

This is not a brokerage. It is an order.

GUIDE / SCROLL-SCRUBBED CREED

Overview

On desktop the whole block pins to the viewport and the creed writes itself in word by word, opacity scrubbed from fifteen percent to one over a real scroll distance, then releases cleanly to the closing call. On mobile the same scrub runs unpinned against the natural page scroll.

When to use

A manifesto or statement of intent, once. Give it real scroll length so each word lands; never auto-play it. Recompute ScrollTrigger once the display font has settled.

Spec

Tokens
--ivory words, --gold kicker, --faint floor
Motion
opacity .15 to 1, staggered, scrub .5
A11y
every word opaque under reduced-motion
Layout
5/7 grid, pinned desktop, scrub mobile

Anti-patterns

No fade on the rail heading that blocks reading. Do not shorten the scroll distance or the scrub feels jumpy. Do not leave words invisible for reduced-motion users.

<!-- Requires GSAP: https://cdnjs.cloudflare.com/ajax/libs/gsap/3.13.0/gsap.min.js -->

<style>
:root{
  --bg:#0b0a08;--surface:#14110b;--ivory:#efe8d6;--muted:#9c9078;--faint:#5a5446;
  --line:#2a2519;--line-faint:rgba(239,232,214,.08);--gold:#c9a24b;--gold-soft:#e7cd8c;
  --font-display:'Instrument Serif',ui-serif,Georgia,'Times New Roman',serif;
  --font-title:'Libre Caslon Condensed',ui-serif,Georgia,'Times New Roman',serif;
  --font-body:'Inter',system-ui,-apple-system,sans-serif;
  --font-mono:'JetBrains Mono',ui-monospace,monospace;
  --ease:cubic-bezier(.22,1,.36,1);--dur-fast:200ms;--dur-base:400ms;
}
*{margin:0;padding:0;box-sizing:border-box}
body{background:var(--bg);color:var(--ivory);font-family:var(--font-body);min-height:100vh}
@keyframes hf-rise{from{opacity:0;transform:translateY(22px)}to{opacity:1;transform:translateY(0)}}
.animate-rise{opacity:0;animation:hf-rise 1.15s var(--ease) forwards}
.creed-wrap{max-width:80rem;margin:0 auto;padding:0 48px}
.creed-grid{display:grid;gap:48px}
@media(min-width:768px){.creed-grid{grid-template-columns:5fr 7fr;gap:64px}}
.creed-rail{}
.creed-rail .sticky{position:relative}
@media(min-width:768px){.creed-rail .sticky{position:sticky;top:6rem}}
.creed-kick{font-family:var(--font-mono);font-size:.7rem;font-weight:500;text-transform:uppercase;letter-spacing:.42em;color:var(--gold)}
.creed-title{margin-top:20px;font-family:var(--font-display);font-weight:300;font-size:clamp(3rem,8vw,5.5rem);line-height:.92;color:var(--ivory)}
.creed-body{max-width:36rem}
.creed-body p{font-family:var(--font-title);font-size:clamp(1.4rem,2.6vw,2rem);line-height:1.5;color:var(--ivory);margin-bottom:36px}
.creed-body p:last-child{margin-bottom:0}
.creed-body .word{display:inline-block}
.spacer{height:22vh}
@media(prefers-reduced-motion:reduce){.animate-rise{opacity:1;animation:none}.creed-body .word{opacity:1 !important}}
</style>

<div class="spacer"></div>

<div class="creed-wrap">
  <div id="pin">
    <div class="creed-grid">
      <div class="creed-rail">
        <div class="sticky">
          <p class="creed-kick animate-rise">The Creed</p>
          <h1 class="creed-title animate-rise">The Doctrine</h1>
        </div>
      </div>
      <div class="creed-body" id="words">
        <p data-line>Noise, sold as information.</p>
        <p data-line>We return the instruments to the individual.</p>
        <p data-line>Research without permission. Backtest without limits. Deploy without intermediaries.</p>
        <p data-line>This is not a brokerage. It is an order.</p>
      </div>
    </div>
  </div>
</div>

<div class="spacer"></div>

<script>
gsap.registerPlugin(ScrollTrigger);

// Split each line into word spans.
var reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
var lines = document.querySelectorAll("#words [data-line]");
lines.forEach(function(line){
  var text = line.textContent.trim();
  var words = text.split(/\s+/);
  line.innerHTML = words.map(function(w){
    return '<span class="word" data-word>' + w + '</span>';
  }).join(" ");
});

if(reduced){
  document.querySelectorAll(".word").forEach(function(w){ w.style.opacity = 1; });
} else {
  var words = document.querySelectorAll("[data-word]");
  gsap.set(words, {opacity:.15});
  var mm = gsap.matchMedia();
  mm.add("(min-width: 768px)", function(){
    gsap.timeline({
      scrollTrigger:{trigger:"#pin", start:"top top", end:"+=" + (words.length * 28), pin:true, scrub:.5, anticipatePin:1}
    }).to(words, {opacity:1, ease:"none", stagger:.5});
  });
  mm.add("(max-width: 767.98px)", function(){
    gsap.to(words, {opacity:1, ease:"none", stagger:.4, scrollTrigger:{trigger:"#words", start:"top 85%", end:"bottom 55%", scrub:true}});
  });
  if(document.fonts && document.fonts.ready){ document.fonts.ready.then(function(){ ScrollTrigger.refresh(); }); }
}
</script>