Sets the letter in the tile's top-left corner with the value staying bottom right — the in-game tile layout — instead of centring it. The glyph is placed by its ink box rather than the text origin, so every letter lands on the same corner whatever its side bearings and ascent. Adds day-spot: the same crop and camera as night-spot on a light ground, so the chosen composition exists as a light/dark pair.
Erudit — VK loading-screen logo (Lottie)
Animated logo for the VK app loading screen (shown before the SPA assets load).
The Erudit «Э» tile (score 8) drops in under gravity, lands with a soft squash —
its left/right edges bulging into a cushion — then springs back up, looping. A light
"glint" is caught at the moment of the bounce.
| File | Purpose |
|---|---|
erudit-loader.json |
The Lottie to upload to VK. |
erudit-loader-preview.gif |
Looping preview. Rendered on a green "baize" background only in the preview — the real asset has a transparent background. |
build/ |
The reproducible build pipeline (see Regenerate). |
VK requirements met: 96×96 px · vector Lottie JSON · ≤ 24 KB (~11 KB) · seamless ~1.1 s loop · transparent background.
Design reference: a photo of the physical wooden Erudit tile.
How it works
A single flat layer holds the tile — a rounded-rect face path, the two glyphs, and a thin border — and everything is driven by that layer's transform plus a few colour / shape tracks. The anchor sits on the tile's bottom edge, so the squash happens against the floor.
Bounce (gravity)
position.y of the bottom edge goes apex → floor → apex with no dwell (the apex is
an instantaneous turn-around). The fall uses a strong ease-in (accelerate) and the
rise a strong ease-out (decelerate), so it reads as real gravity. While falling fast
the tile slightly stretches (tall+thin); that is the squash-and-stretch setup.
Soft cushion (squash)
On contact the layer squashes (scaleY↓, scaleX↑) about the bottom anchor, with a
touch of skew. Crucially the squash/cushion is decoupled from the fall speed — it
eases in and out slowly (ES), so the landing feels soft even though the fall is
fast. The squash is deliberately gentle (≈ 86 % / 112 %).
The cushion shape (the hard part)
The face is not a plain rounded rect — it is a path whose left/right contour bows out into a convex cushion on impact:
- the rest rounded-rect outline is sampled (fine corner arcs + edge mids), and on impact
every point is pushed outward by a smooth barrel profile
f(y) = b·(1 − (y/HH)²)(max at the middle, fading to zero at the top/bottom); - so the whole side — corners included — bows out as one piece, never just the straight middle;
- bezier handles are computed as a chordal spline (handle length ∝ the adjacent chord), which stays smooth across the uneven corner/edge point spacing. This is what keeps the corner↔cushion junction kink-free — both at rest and fully bulged — which a naïve uniform Catmull-Rom (or moving discrete points with fixed tangents) does not.
Glint
At the bounce the face flashes a little lighter (FACE_GLINT) and the glyphs warm
toward brown-red (BLACK_LIT), then settle back. A cheap, warm "catch the light".
Border & background
The tile carries a thin static border a touch darker than the face (BORDER) so it
reads on any background. The green baize background is added only when rendering the
preview GIF — the Lottie itself is transparent.
Player compatibility
Only plain 2-D shapes (ddd:0) — no 3-D layers, expressions or effects — so every
Lottie player (lottie-web on the web, rlottie on native) renders it identically.
Glyphs
Э (U+042D) and 8 are real outlines from LiberationSans-Regular — metric-
compatible with Arial, matching the game's --font: system-ui … Arial stack
(see ui/src/app.css). build/extract.js pulls the contours into build/glyphs.json
as Lottie cubic paths; a hair of same-colour stroke adds a touch of weight.
Regenerate
Requirements: Node ≥ 18. extract.js additionally needs opentype.js
(npm i opentype.js); generate.js has no dependencies.
cd assets/vk
# 1. (optional) re-extract the glyphs — only if you change the font:
# default font: /usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf
node build/extract.js [/path/to/font.ttf] # -> build/glyphs.json
# 2. build the animation (reads build/glyphs.json):
node build/generate.js erudit-loader.json # -> erudit-loader.json
# 3. (optional) live preview — needs lottie-web (npm i lottie-web):
node build/build-preview.js erudit-loader.json preview.html
# then open preview.html in a browser.
The preview GIF is assembled by rendering the Lottie frame-by-frame (lottie-web canvas,
filled with the green baize) and stitching with ffmpeg; the live HTML preview is the
quickest way to eyeball changes.
Tunables (top of build/generate.js)
| Symbol | Meaning |
|---|---|
OP, FR |
loop length (frames) / fps — overall speed |
T_HIT / T_PEAK / T_LIFT / T_REC |
beats: contact / squash peak / lift-off / cushion recovered |
EI / EO / ES |
easings: fast fall / fast rise / slow soft cushion |
APEX, FLOOR |
bottom-edge screen-y at the top of the bounce / at rest |
HW, HH, CR |
tile half-width / half-height / corner radius |
scl squash values |
the squash amount (scaleX↑ / scaleY↓) |
bulge b |
cushion depth (how far the sides bow out) |
FACE / FACE_GLINT |
wood face / glint flash |
BORDER |
tile rim colour |
BLACK / BLACK_LIT |
glyph / glyph-at-glint (brown-red) |
preview bg #3C7858 |
the green-baize colour used only in the GIF |