# frankaa > A photo slider (procession), a photo lightbox (unfurl) and a strip of pictures under a ruler (drumline, after dondo, the talking drum) for the web. All three take the same items; combining is one option. Framework-free TypeScript, zero runtime dependencies, optional React bindings, MIT. The lightbox springs from the photo's spot and changes photos with WebGL shader effects (built-in or custom) or a CSS renderer (transform and opacity only). Install: `npm install frankaa`. Import the CSS once: `import "frankaa/style.css"`. ## Docs - [Live demo and playground](https://jabclari.github.io/frankaa/) - [Full reference and recipes](https://jabclari.github.io/frankaa/llms-full.txt): also `node_modules/frankaa/llms-full.txt` - [README](https://github.com/JABClari/frankaa#readme) - [Agent guide](https://github.com/JABClari/frankaa/blob/main/AGENTS.md) ## Imports - `frankaa`: everything below - `frankaa/slider`: `procession(element, options)` - `frankaa/drumline`: `drumline(el, items, options)`, `DRUMLINE_PRESETS` - `frankaa/lightbox`: `unfurl(options)`, `supported()`, `registerEffect(name, effect)`, `checkEffect(effect)`, `effectNames()` - `frankaa/react`: ``, ``, `useUnfurl(items, { strip, ... })` returning `{ open(i), close(), portal }` (renderControls(api) renders into the top bar through `portal`; unmount fires onDestroy, not the close hooks) - `frankaa/style.css` ## Items (shared) `(Item | string)[]`. Image: { src, thumb?, full?, srcset?, sizes?, sources?, alt?, caption?, title?, width?, height?, placeholder? }. Video: { type: "video", provider, src, poster?, thumb?, alt?, caption? }. The slider shows src, the lightbox full ?? src, strips thumb ?? src. Every part takes (items, options) or ({ items, ...options }). ## Drumline options preset ("ruler" | "numbers" | "minimal"), ticks, rulerHover, numbers ("frame" | "fraction" | false), focusFeedback, colorFocus, grow, fluid ("off" | "lerp" | "chain" | "bend"; bend is WebGL with a DOM fallback), look ("stories" | "bars"), renderer ("auto" | "dom"), wheel ("both"), drag, inertia, snap, expand (true: opens the lightbox), lightbox (unfurl options), start, label, labels, reducedMotion, announce. Hooks: onChange, onAnimationStart/End, onSelect, onExpand, onError. Returns { index, renderer, go, next, prev, expand, image, destroy }. ## Slider options photos (required), label, labels, start, dots, arrows, snap ("mandatory" | "proximity"), loop, autoplay (ms or false, default false), priority, lazyMargin ("100px"), reducedMotion ("system" | "always" | "never"), announce, href (track becomes a link), tabbable (true), counter (false), zoom (false: pinch and double tap), zoomCursor (false), focusRing ("outside" | "inset"), icons. Photo: { src, srcset?, sizes?, alt?, width?, height?, placeholder? }. onSelect(index, img, event). renders the first photo on the server. Returns { index, el, go(i, smooth?), next(), prev(), play(), pause(), image(i), destroy() }. ## Lightbox options photos (required: images { src, alt?, caption? } or string, and videos { type: "video", provider: "youtube" | "vimeo" | "tiktok" | "instagram" | "mp4", src, poster, title? }), topActions ([{ label, onClick(api), icon? }]), controls ((host, api) => cleanup), start, origin ((i) => Element | DOMRect | null), effect ("displacement" | "ripple" | "fade" | "zoom" | "none" | registered name | { fragment, uniforms?, duration?, easing?, css? }), renderer ("auto" | "webgl" | "css"), reducedMotion, label, labels, focusTrap (true), returnFocus (true | HTMLElement | false), announce (true), hideOrigin (true), radius (16, [tl, tr, br, bl], or (i) => radius), openRadius (12 or four), zoom (true or { max: 4 }: pinch, double tap, Ctrl+wheel, + and -, pan), icons, image srcset/sizes/placeholder, container, loop (false), strip (true | "ruler" | "numbers" | "minimal" | drumline options: a filmstrip along the bottom). Returns { index, renderer, next(), prev(), show(i), close(), destroy() }. Swipes slide a photo strip with the neighbours peeking in; keys and buttons use the effect. ## Hooks (also DOM CustomEvents "frankaa:", bubbling) onOpenStart, onOpenEnd, onCloseStart({ index, reason: "close" | "destroy" }), onCloseEnd (destroy() fires both), onChange(index, detail), onSlideChange(index, detail) (slider), onAnimationStart({ type }), onAnimationEnd({ type }), onError({ index, src, reason: "cors" | "load" | "webgl" | "shader", fallback, message? }), onSelect(index, img) (slider). ## Custom shader contract GLSL ES 1.0 fragment shader: `precision mediump float;`, `varying vec2 vUv`, uniforms uFrom, uTo (sampler2D), uProgress (0 to 1), uResolution, uFromSize, uToSize (vec2), uTime, uDirection (float), plus your own; write gl_FragColor. Macros coverUv(uv, texSize, resolution) and mirrorUv(uv) are predefined. Compile errors fire onError and fall back to CSS.