Building the Lumon macrodata refinement app

Emilio Passi
5 min readJun 21, 2022

Ever since building an interactive version of the Westworld attribute matrix, I’ve been looking for other Hollywood FUIs to make real. A few have caught my eye, but none quite like the macrodata refinement app in Severance.

Live demo below. GitHub repo here. Spoilers ahead.

Severance tells the story of employees working at the Lumon biotech company, where a controversial procedure separates their personal- and work-related memories. The main characters work in the Macrodata Refinement department, using a mysterious data-processing app to… nobody actually knows.

Rid the oceans of eels?
Cut swear words out of movies?
Kill baby goats?

Probably worse. We won’t know till at least next season what the “macrodata” are and why they need refining. Though I dread finding out what the app does, its retro aesthetic, motion design, and plausible UI made it an irresistible coding exercise.

My UI prototyping toolbox has evolved a lot since the Westworld project. React and Framer Motion have made it easier to develop interfaces like the macrodata refiner. But there’s still plenty of interesting math left to make this a satisfying challenge.

The hover effect

Animated demo of magnifying hover effect

The hover effect was the first thing to draw me in. I had several false starts thinking the magnification was based on a linear relationship between the cursor position and the center of a number cell. I eventually realized the algorithm is based on area. If you imagine an invisible lens that follows your cursor, the ratio of lens area to cell area tells you how much the number should scale.

Diagram showing how data field cells are enlarged

The motion path

Animated demo of numbers moving into a bin

Getting the numbers to move to a bin was tedious but straightforward: tracking the bin centers with the viewport size, and calculating the corresponding CSS translate(x, y) values to move the numbers the correct distance. I did hit a bump figuring out how to make the numbers slightly curve into the bin, and not just move in a straight line. But I remembered from designing particle systems that you could achieve this effect by changing the x and y positions at different rates.

const duration = 1.5;animate(x, binTarget.x, {
delay: 1,
duration: 0.75 * duration, // Faster change in x creates the curve
animate(y, binTarget.y, {
delay: 1,

The bin animation

Animated demo of opening bin and its console output

For such a bespoke animation, my first instinct was to use Lottie. But I wasn’t sure how well that would work for a fluid layout that changes with the viewport size, and I didn’t have an After Effects license anyway. So I explored a few ways to animate the bin with code.

A few attempts at animating the bin. I ended up modifying an SVG path over requestAnimationFrame().

I’d written enough code by this point that I needed to work in CodePen to keep my thoughts straight. The first approach tried squashing a parallelogram image with CSS, but it was awkward to control and I didn’t like that the image disappears mid-animation. I conceded to manipulating an SVG with the second approach, using CSS keyframes to animate the shape, but I would’ve needed a hundred keyframes to smooth the effect. So I ended up doing it in JavaScript with requestAnimationFrame(), recursively changing the shape based on the flap angle.

const [path, setPath] = useState("");
const openAngle = Math.PI * 0.75;
const increment = openAngle / 20;
const height = 16;
let angle = 0;const callback = () => {
const x = radius * Math.cos(angle);
const y = radius * Math.sin(angle);
const progress = angle / openAngle; setPath(
`M0 ${(1 - progress) * height}` + // top left
`L${x} ${-y + (1 - progress) * height}` + // top right
`L${x} ${-y + height}` + // bottom right
`L0 ${height}` + // bottom left
`L0 ${(1 - progress) * height}` // top left
angle += increment; if (angle < openAngle) {

The progress text outline

Close-up of outline around header progress text

Outlining the progress text was harder than it looked. The experimental webkit-text-stroke CSS property didn’t quite get the effect right, and I couldn’t specify that the stroke be drawn outside the shape. So I experimented with the more standard text-shadow property.

Close-up of text outline created by simple text-shadow
Text-shadow only the top, bottom, left, and right of the text.

I added text-shadow to the top, bottom, left, and right sides of the text. This started to look right, but I really wanted to smooth the corners of the shadow. So I wrote a loop that incrementally added a shadow at a regular radian interval.

const shadowOffset = (angle, radius) => ({
x: radius * Math.cos(angle),
y: radius * Math.sin(angle)
let shadow = textShadow;for (let i = 0; i < 2; i += 0.125) {
const {x, y} = shadowOffset(Math.PI * i, radius);
shadow += `${x}rem ${y}rem #FFF,`;
Close-up of text outline created by loop-generated text-shadow
Text-shadow at every eighth of pi. Surely there’s a better way to do this.

What’s next?

I can finally research how the visual effects were produced. I didn’t go deep on this, wanting to avoid technique spoilers. But those cursor movements tell me someone actually programmed them.

Some reflection too. This is mainly a coding exercise, but I don’t want to lose track of the message the show is leading up to, which will no doubt say something about the real world.

Till we know more, I’ll enjoy this as a fun art project. There’s plenty left to make it more interesting. Just don’t count on any waffle parties for reaching 100%.

Illustration of Sevy, Lumon company mascot