ScrollVars / fx
docs · demo · bench · llms.txt · GitHub

Editorial manifesto

Big statements gain contrast in sequence as --sv-t advances. Natural document flow, no pin and no split spans.

Use it for: Studio manifestos, brand positioning and editorial introductions.
Knobs: label, paragraphs, closing; --sv-t drives a clamped slice per paragraph

What we believeScroll to read ↓

Less noise. More meaning.

We build things people want to spend time with.

Every detail should earn its place.

From the first idea to the final interaction. A small team, accountable for the whole experience.

Install: npx scrollvars add editorial-manifesto
Styles: Included in the component

// ScrollVars fx · editorial-manifesto
// Requires: npm i scrollvars · no preset stylesheet required
'use client'
import * as React from 'react'
import { Track } from 'scrollvars/react'

const css = `
.sv-manifesto { background: #e9e4db; color: #232722; padding: clamp(40px, 9vw, 128px) clamp(24px, 6vw, 88px); }
.sv-manifesto .manifesto-label { display: flex; justify-content: space-between; padding-bottom: 22px; border-bottom: 1px solid currentColor; font: 12px monospace; text-transform: uppercase; }
.sv-manifesto .manifesto-copy { padding-block: 32px; }
.sv-manifesto .manifesto-copy p { font-size: clamp(30px, 5.8vw, 82px); font-weight: 600; line-height: 1.08; letter-spacing: -.045em; margin: 0 0 .55em; }
.sv-on .sv-manifesto .manifesto-copy p { --read: clamp(0, calc(var(--sv-t, 1) * (var(--count) + 1) - var(--i)), 1); opacity: calc(.55 + .45 * var(--read)); } /* the floor keeps unread copy at 3:1 on this paper (axe, large text) */
.sv-manifesto .manifesto-end { max-width: 36ch; margin-left: auto; font-size: 16px; line-height: 1.6; }
[data-sv-off].sv-manifesto .manifesto-copy p { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .sv-on .sv-manifesto .manifesto-copy p { opacity: 1; } }
/* the same under html[data-sv-motion="reduce"], the site's own switch */
.sv-on:where([data-sv-motion="reduce"]) .sv-manifesto .manifesto-copy p { opacity: 1; }
`

export function EditorialManifesto({ label = 'What we believe', paragraphs, closing, className, nonce }: {
  label?: string
  paragraphs: string[]
  closing?: string
  className?: string
  nonce?: string
}) {
  return <Track as="section" travel className={className ? 'sv-manifesto ' + className : 'sv-manifesto'} style={{ '--count': paragraphs.length } as React.CSSProperties}>
    <style nonce={nonce} dangerouslySetInnerHTML={{ __html: css }} />
    <header className="manifesto-label"><span>{label}</span><span>Scroll to read ↓</span></header>
    <div className="manifesto-copy">{paragraphs.map((text, i) => <p key={i} style={{ '--i': i } as React.CSSProperties}>{text}</p>)}</div>
    {closing && <p className="manifesto-end">{closing}</p>}
  </Track>
}

Engine: npm i scrollvars, 12.9 KB gzip as ESM (this page's fx/sv.js IIFE: 13.1 KB). All effects respect prefers-reduced-motion and render complete without JS.