LSD
@ferretdev/lsd-framework v0.8 MIT Zero deps

LSD Framework

Token-driven animation, filter, and reactive-data runtime. Three authoring surfaces — class chains, SCSS mixins, JS API — all built on the same keyframe set and the same token vocabulary.

Part of LSD — a brand-to-code compiler and token-first design system authoring tool. The framework package stands alone and is MIT-licensed. You do not need the editor to use it.

Install

Download a custom bundle

Pick only what you need — the server concatenates the selected modules + bakes your brand tokens into :root and streams back a zip. No npm registry required.

Loading modules…

Estimated size: —

Files

FileRole
lsd-fx.cssTier A: chainable animation utility classes
lsd-fx.scssSCSS mixin layer for compile-time FX authoring
lsd-flt.cssChainable CSS-filter utility classes
lsd-flt.scssSCSS mixin layer for compile-time filter authoring
lsd-anim.jsTier B: DSL parser for data-lsd-fx
lsd-api.jsRuntime authoring API — installs window.lsd.*
lsd-data.jsReactive runtime: data-lsd-* bindings, loops, state
lsd-alpine-bridge.jsBridge: x-lsd-source, x-lsd-fx, $lsd magic

Three authoring surfaces

Same tokens, same keyframes, three ways to reach them:

  1. Class chainsclass="lsd-fx fx-fade fx-up fx-delay-200" (declarative HTML)
  2. SCSS mixins@include fx.animate(fade, $direction: up) (compile-time)
  3. JS APIlsd.fx('fade up', '.hero') (runtime programmatic)

Tier A — class chains

<div class="lsd-fx fx-fade fx-up fx-delay-200 fx-ease-bounce fx-dur-lg">
  Fades in from below on load.
</div>

<img class="lsd-flt flt-noir flt-on-hover-clear" src="portrait.jpg" />

Effect catalog

ClassEffect
fx-fadeOpacity 0 → 1
fx-slideTranslate; pair with fx-up / fx-down / fx-left / fx-right
fx-scaleScale 0.8 → 1
fx-blurBlur 8px → 0
fx-rotateRotate 12deg → 0
fx-flip3D flip
fx-glitchChromatic-aberration shake
fx-tiltPerspective tilt on hover
fx-marqueeHorizontal scroll loop
fx-floatGentle vertical oscillation
fx-pulseScale oscillation
fx-shakeHorizontal shake
fx-bounce-inSpring-eased entrance

Modifier catalog

PatternValues
fx-delay-N0, 100, 200, 300, 500, 700, 1000 ms
fx-dur-*xs, sm, md, lg, xl, slow
fx-ease-*linear, in, out, inOut, bounce, elastic, smooth
fx-stagger-N50, 100, 150, 200 ms (applied to children)
fx-loop / fx-loop-infiniteReplay
fx-on-hover / fx-on-focus / fx-on-scrollTrigger on interaction / viewport entry
fx-pause-on-hoverPause animation on hover

Filter catalog

ClassEffect
flt-noirBlack-and-white + contrast
flt-sepiaSepia tone
flt-mutedDesaturation
flt-warmWarm hue shift
flt-coolCool hue shift
flt-dreamSoft blur + saturation
flt-crispContrast + saturation boost

Hover-state variants: flt-on-hover-clear, flt-on-hover-noir, flt-on-hover-crisp.

Tier B — DSL

Single attribute, three positional groups separated by |:

<div data-lsd-fx="fade up | delay:200 ease:bounce dur:slow | trigger:scroll stagger:50">
  …
</div>

Grammar: effects | key:value modifiers | trigger:value + stagger:N. On DOMContentLoaded, lsd-anim.js parses, sets --fx-* vars, adds the matching Tier A classes, and wires scroll triggers via IntersectionObserver.

Fire document.dispatchEvent(new CustomEvent('lsd-anim-rescan')) after inserting content dynamically.

JS authoring API

lsd.fx('fade up', '.hero', { delay: 200 })    // Promise
lsd.filter('noir', 'img.poster')
lsd.chain('fade up | delay:200 ease:bounce', el)
lsd.apply('fx-scale fx-dur-lg', el)
lsd.on('click', '.btn', (e) => { /* … */ })
lsd.tokens.get('color-primary')
lsd.tokens.set('color-primary', '#ff0000')
lsd.presets.list()
lsd.rescan()

Targets accept selectors, Elements, or NodeLists. prefers-reduced-motion is honored — animations short-circuit and Promises resolve immediately.

SCSS mixin layer

@use '@ferretdev/lsd-framework/fx.scss'  as fx;
@use '@ferretdev/lsd-framework/flt.scss' as flt;

.hero-title { @include fx.animate(fade, $direction: up, $delay: 200ms); }
.card-grid  { @include fx.stagger(100); }
.avatar     { @include flt.preset(noir); }
.feature    { @include fx.chain('fade up delay:200 ease:bounce'); }

Mixins: animate(), chain(), filter(), stagger(), on-hover(), on-focus(), on-scroll(), respect-reduced-motion().

Functions: fx-duration($key), fx-easing($key), fx-keyframe($effect, $direction).

Reactive data — lsd-data.js

Progressive-enhancement reactive layer. Any element with data-lsd-app becomes a scope; children use data-lsd-* directives. Zero deps, uses Proxy for dep tracking.

Directive reference

DirectiveMeaning
data-lsd-appRoot of a reactive scope
data-lsd-state='{…}'Inline JSON initial state
data-lsd-source="/url"Fetch JSON, merge into state
data-lsd-poll="5000"Re-fetch every N ms
data-lsd-text="path"Bind textContent to a state path
data-lsd-html="path"Bind innerHTML — trusted templates only
data-lsd-bind:attr="path"Bind any attribute
data-lsd-class:name="path"Toggle a class when path is truthy
data-lsd-show="path"Toggle visibility
data-lsd-if="path"Insert / remove the node
data-lsd-for="item in xs"Repeat node per array element
data-lsd-on:event="expr"Event binding
data-lsd-model="path"Two-way bind input / select / textarea / checkbox

Inline example

<div data-lsd-app data-lsd-state='{"people":[
    {"name":"Ada","role":"Math"},
    {"name":"Grace","role":"Compilers"},
    {"name":"Hedy","role":"Signals"}
  ]}'>
  <ul>
    <li data-lsd-for="p in people"
        class="lsd-fx fx-fade fx-up fx-stagger-100 fx-on-scroll">
      <strong data-lsd-text="p.name"></strong>
      <span data-lsd-text="p.role"></span>
    </li>
  </ul>
</div>

Alpine bridge

AdditionBehavior
x-lsd-sourceFetch JSON and merge into the Alpine scope.
x-lsd-fxRun an LSD effect chain on this element.
$lsd magicAccess $lsd.fx(), $lsd.tokens from Alpine expressions.
<div x-data="{ status: 'idle' }" x-lsd-source="/api/status.json">
  <span x-text="status"></span>
  <button x-on:click="$lsd.fx('pulse', $el)">Ping</button>
</div>

Tokens

Override at any scope:

.hero { --fx-duration: 1200ms; --fx-easing: var(--easing-elastic); }

Reduced motion

Both CSS files end with a @media (prefers-reduced-motion: reduce) block that neutralizes all animations and transitions. lsd-anim.js skips IntersectionObserver wiring when reduced motion is active. lsd-api.js short-circuits fx() / chain().

Browser support

Safari 15.4+, Chrome 94+, Firefox 93+. Modern evergreen.

License

MIT.