Hyperbridge's rainbow hero

A single hand-authored Lottie.

Hyperbridge's landing page opens with a hero you feel before you read it: a glowing chip in the middle, two rainbow ribbons flaring out of it like a sideways hourglass, and a stream of blockchain logos sliding outward over a dark vignette. I went in assuming it was CSS and SVG - it isn't. The whole thing is a single hand-authored Lottie animation, so the most faithful "recreation" is to play the very file they ship. Here's the animation, followed by exactly how I got it out of their site and back onto this page.

Hyperbridge hero (their Lottie)

Confirm it's a Lottie, not CSS

The first move with any "how did they do that" is to open devtools and look at what the element actually is. The hero isn't a stack of <div>s - it's one big <svg> whose groups are clipped by __lottie_element_* masks. That naming is the fingerprint of lottie-web rendering an After Effects export, confirmed by a vendor-lottie chunk in the network tab. So every part of the effect - the rainbow, the chip's glow, the sliding logos, the darkening - is keyframed in a vector file, not composed from the DOM.

// In the page console, on hyperbridge.network:
document.querySelectorAll('g[mask^="url(#__lottie_element"]').length   // > 0
// → the hero is lottie-web output, driven by one animationData object

Find where the animation data lives

Normally a Lottie is fetched as a .json file you can just download - but the Network panel shows no such request. Their Vite build inlines the animation straight into the page's JS chunk. Searching the chunk for Lottie's tell-tale keys ("ddd", "layers", "ip") lands on two big JSON.parse('…') string literals, surrounded by a handful of minified variables that get assembled into the animationData object by hand:

// …minified page chunk, lightly renamed:
const S = "5.7.5", v = 100, N = 0, G = 1200, b = 1440, H = 800, w = "Comp 1", B = 0
const Y = { backgroundColor: { r: 3, g: 3, b: 3 } }   // near-black stage
const D = JSON.parse('[{"id":"0","layers":[ … ]}]')   // ← assets
const z = JSON.parse('[{"ddd":0,"ind": … }]')         // ← main layers
const L = { v: S, fr: v, ip: N, op: G, w: b, h: H, nm: w, ddd: B,
            metadata: Y, assets: D, layers: z, markers: [] }
// …later: <Lottie animationData={L} rootMargin="240px 0px" … />

Reassemble the Lottie JSON

Now it's just bookkeeping: pull the two JSON.parse string literals out of the chunk (scan from the opening quote to the first un-escaped closing quote, then JSON.parse each), map the minified scalars to their real Lottie keys, and stitch them back into the standard shape. The result is byte-for-byte the object lottie-web was being fed - a 1440×800 comp, 100 fps, 1200 frames (~12 s), on a #030303 background.

const anim = {
  v: "5.7.5", fr: 100, ip: 0, op: 1200,   // version, fps, in/out frames
  w: 1440, h: 800, nm: "Comp 1", ddd: 0,
  metadata: { backgroundColor: { r: 3, g: 3, b: 3 } },
  assets,   // ← extracted JSON.parse blob #1
  layers,   // ← extracted JSON.parse blob #2
  markers: []
}
fs.writeFileSync("public/hyperbridge/hero.json", JSON.stringify(anim))

Look inside - the rainbow is a raster image

Cracking the file open explains why a CSS rebuild never stood a chance. There are no gradient or solid fills at all - almost all of the colour is three embedded base64 images: a 2880×800 JPEG (the rainbow band itself) and two PNGs (the chip's glow). The motion is a tree of pre-comps with names that read like a layer panel: Flow, Chains left, Chains right, Chip, Region. Each chain logo is its own little pre-comp riding a path.

assets: 20   →  17 pre-comps + 3 embedded images
images:  2880×800 JPEG   +  1232² PNG  +  1040² PNG     // rainbow + glow
fills:   0 gradients · 0 solids                          // colour is all raster
layers:  27 shapes · 17 pre-comps · 12 nulls · 4 images
comp 0:  Flow · Hyperbridge Core · Chains right · Chains left · Region …

Serve it and play it back, lazily

The JSON sits in /public so it isn't bundled into the page's JS. The ~290 KB shouldn't load until you can see it, so the demo waits on an IntersectionObserver with a 240px root margin (the same lazy-load Hyperbridge uses) before fetching it and handing it to lottie-react. preserveAspectRatio: "xMidYMid slice" lets the 9 / 5 comp fill any width, and prefers-reduced-motion holds it on a static frame.

const io = new IntersectionObserver(([e]) => {
  if (e.isIntersecting) { setVisible(true); io.disconnect() }
}, { rootMargin: "240px 0px" })          // start loading just before it's on-screen
io.observe(el)

// once visible: fetch("/hyperbridge/hero.json").then(r => r.json()).then(setData)

<Lottie
  animationData={data}
  loop autoplay
  rendererSettings={{ preserveAspectRatio: "xMidYMid slice" }}
/>

Credit where it's due: the animation itself is Hyperbridge's own artwork. This article is about how it's built and served - discovering the technique, reconstructing the asset, and wiring it back up - not a claim to the art.

Newsletter

Stay updated with my latest articles and projects. No spam, no nonsense.