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.
Files
| File | Role |
|---|---|
lsd-fx.css | Tier A: chainable animation utility classes |
lsd-fx.scss | SCSS mixin layer for compile-time FX authoring |
lsd-flt.css | Chainable CSS-filter utility classes |
lsd-flt.scss | SCSS mixin layer for compile-time filter authoring |
lsd-anim.js | Tier B: DSL parser for data-lsd-fx |
lsd-api.js | Runtime authoring API — installs window.lsd.* |
lsd-data.js | Reactive runtime: data-lsd-* bindings, loops, state |
lsd-alpine-bridge.js | Bridge: x-lsd-source, x-lsd-fx, $lsd magic |
Three authoring surfaces
Same tokens, same keyframes, three ways to reach them:
- Class chains —
class="lsd-fx fx-fade fx-up fx-delay-200"(declarative HTML) - SCSS mixins —
@include fx.animate(fade, $direction: up)(compile-time) - JS API —
lsd.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
| Class | Effect |
|---|---|
fx-fade | Opacity 0 → 1 |
fx-slide | Translate; pair with fx-up / fx-down / fx-left / fx-right |
fx-scale | Scale 0.8 → 1 |
fx-blur | Blur 8px → 0 |
fx-rotate | Rotate 12deg → 0 |
fx-flip | 3D flip |
fx-glitch | Chromatic-aberration shake |
fx-tilt | Perspective tilt on hover |
fx-marquee | Horizontal scroll loop |
fx-float | Gentle vertical oscillation |
fx-pulse | Scale oscillation |
fx-shake | Horizontal shake |
fx-bounce-in | Spring-eased entrance |
Modifier catalog
| Pattern | Values |
|---|---|
fx-delay-N | 0, 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-N | 50, 100, 150, 200 ms (applied to children) |
fx-loop / fx-loop-infinite | Replay |
fx-on-hover / fx-on-focus / fx-on-scroll | Trigger on interaction / viewport entry |
fx-pause-on-hover | Pause animation on hover |
Filter catalog
| Class | Effect |
|---|---|
flt-noir | Black-and-white + contrast |
flt-sepia | Sepia tone |
flt-muted | Desaturation |
flt-warm | Warm hue shift |
flt-cool | Cool hue shift |
flt-dream | Soft blur + saturation |
flt-crisp | Contrast + 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
| Directive | Meaning |
|---|---|
data-lsd-app | Root 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
| Addition | Behavior |
|---|---|
x-lsd-source | Fetch JSON and merge into the Alpine scope. |
x-lsd-fx | Run an LSD effect chain on this element. |
$lsd magic | Access $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.