Files
scrabble-game/assets/vk
Ilia Denisov 92a97c6c93 feat(vk): draft the VK community cover
Adds VK's own cover template (1920x768) and a self-contained generator that
renders an Эрудит board in perspective with ГРАМОТНОСТЬ / СОПЕРНИК /
ЭРУДИЦИЯ / УМ already played, in six art-direction variants for review.

The position is checked, not asserted: assertLegal re-reads the finished grid
and fails the build unless the centre is covered, every declared word is on the
board, and no maximal run of two or more letters is a word nobody played. All
four words are in the Эрудит dictionary and tile values come from
scrabble-solver rules.Erudit().

The camera has pitch only, so the perspective warp reduces to an exact
per-scanline remap of a flat texture; tile thickness is filled as 3D quads
between the board plane and the tile plane. skia-canvas is borrowed from the
render sidecar rather than installed a second time for a one-off asset build.

Composition respects the two VK keep-outs measured off the template: the top
130 px some clients crop, and the mobile avatar circle over the bottom-left —
which is why the default word arrangement keeps that corner empty.
2026-07-27 15:21:46 +02:00
..

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