Skip to content
All posts
6 min read

Klippot: an interactive art piece with zero dependencies

Four elements, each shown pure and corrupted, in hand-written SVG, CSS, and a few hundred lines of vanilla TypeScript — a study in restraint, motion character, and accessibility as part of the art.

Case studyCraftMotion design

Klippot (Hebrew for “shells”) is a term from Lurianic Kabbalah for form that has outlived its purpose — the husk that remembers the shape of the light it held. The piece takes four classical elements — fire, water, earth, air — and shows each twice: as itself, and as the thing it becomes when it forgets what it was for. Each pair grows from a shared anchor — fire from its wick, water from its still center, air from its source line; earth alone splits the pivot on purpose, bearing from its footing and fracturing from its crown — so the corruption reads as the same thing turned, not a different image. Hover an element and it corrupts; hold Space and all four turn at once.

It's a self-initiated piece, and it doubles as the studio's clearest statement about craft: what you can build when the constraint is the web platform and nothing else.

Dependencies*
0
Lines total
~2.5k
CSS keyframes
32
Inline SVGs
9

* beyond Next.js and React themselves — no CSS framework, no animation library, no UI kit, no canvas.

Motion with an argument

The signature animations carry a written rationale in the CSS. Pure earth doesn't sit still — it “holds,” compressing by 0.3% on a thirteen-second cycle, because holding is active resistance, not stillness. Corrupt air's severed breath-line still breathes, at a third the period and a fraction of the amplitude: the same breath, suffocating. The hover transition is asymmetric on purpose — engaging the corruption answers eagerly, returning to pure is slow and reluctant. The timing itself carries the metaphor.

The unglamorous craft is loop concealment. Infinite CSS animations betray themselves the moment you spot the cycle, so the corrupt flame's layers run deliberately mismatched periods — 1.5s body, 1.1s tip, a 3.6s shared clock with a held almost-dark beat — and the embers ride three keyframe paths with negative delays. Water's drain rings accelerate inward on a custom easing curve while the vortex beneath them takes 36 seconds per revolution. You can watch for a long time before anything visibly repeats.

Restraint is documented alongside the work: a wave radiating off air's cut was considered and rejected as ornament — “this is only the consequence, two pixels of it.”

The state machine nobody sees

The hold-Space interaction sounds trivial and isn't. A quick tap still pages the document exactly as native Space would — re-implemented, since detecting the hold means intercepting the key, and a deliberate WCAG choice to never trap a single keystroke — so corruption only engages after a 180ms hold, sweeping the four elements in a staggered I→IV cascade and withdrawing in reverse on release. Then the defensive part: window blur, tab switches, and back-forward cache restores all release the held state, so losing the keyup to Cmd+Tab can never strand the page inverted. After release, hover is suppressed until the mouse moves, so the element under your cursor doesn't instantly re-corrupt.

Cross-browser reality got the same treatment, and the fixes are documented in the code: SVG radius animations needed explicit px units (unitless is Chrome-only — Firefox and Safari froze the water rings), focus-visible rules are split out of shared selector lists for older Safari, artworks get an aspect-ratio fallback so they don't render zero-height on Safari 14, and a guard turns the piece into a fully visible static page on browsers without IntersectionObserver. No-JS visitors get the complete page too: the scroll-reveal hide-states only apply under @media (scripting: enabled).

Accessibility as part of the art

  • Each artwork is a labeled image whose description covers both forms: “Fire — a single calm flame; corrupted, the same flame thrashes and throws embers.”
  • Reduced-motion collapses every layered swap to a plain crossfade — in the README's words, the shells are still shells when they do not move.
  • The Hebrew carries proper language tags throughout, and the hero title is RTL-isolated; the element names are sized optically against the tracked Latin labels with letter-spacing reset — Hebrew is never letterspaced.
  • The quietest text tier is a commented contrast calculation, not a guess.

Process, honestly

The piece went through the studio's AI-accelerated review process, and the git history says so plainly: a motion-deepening pass where a judged panel of specialist agents proposed thirteen animation upgrades — each vetted for taste and technical cost before landing — and a production-readiness sweep whose findings were adversarially verified, with three rejected against spec and browser-support data. The same discipline we apply to client code, applied to a poem.

What we'd tell a client

  • The platform is enough more often than you think — every dependency you skip is a dependency you don't debug.
  • Motion quality is mostly editing: mismatched periods, asymmetric timing, and the willingness to reject your own ornaments.
  • Accessibility handled early becomes part of the design language instead of a compliance checklist.
  • Write the why into the code. The comments are the piece's second reading.