Pictogram Motion 象形运动图形
Aichi-Nagoya 2026 — All 43 Sports·2:44·Lemo-Opuscarby Lemomo
A beat-locked flash-card film built from three things only: a square-grid "core graphic" of circles, half-discs and tiny patterned cells whose rows slide past each other; geometric pictograms made of round-capped bars and a disc head; and bold bilingual type that slides up out of masks. Every cut lands on a drum hit.
The demo: All 43 sports of the 2026 Asian Games as beat-locked geometric pictograms.
A beat-locked flash-card film built from three things only: a square-grid "core graphic" of circles, half-discs and tiny patterned cells whose rows slide past each other; geometric pictograms made of round-capped bars and a disc head; and bold bilingual type that slides up out of masks. Every cut lands on a drum hit. References (grammar only): Otl Aicher's 1972 Munich pictograms (geometric athletes); multi-sport-games "look of the games" identity systems (a modular pattern + a colour set used for everything); Swiss sports-broadcast graphics (mono meta text, tick-bar progress). Learn the grammar; never copy any real event's pictograms, emblem, slogan, pattern artwork or colour names, and never name them in the film.
1. Essence, and what it is not
A catalogue film: N items (products, dishes, steps, species…) shown one card at a time, fast, on the beat. Each card = one pictogram doing one action + a huge title + a second-language line + a mono index "07 / N". Cards group into chapters; each chapter owns one hue, opened by a full-bleed pattern card.
What makes a frame read as this style at a glance:
- The grid pattern: big discs and half-discs spanning square cells, about half the cells carrying a small motif, all in five tones of one hue, with whole rows cut and shifted sideways: the signature that drives backgrounds and transitions.
- The pictogram figure: flat, faceless, rounded bars and a disc head; far-side limbs mixed toward the background. Props are discs, rings, capsules, lines.
- Type as architecture: a 900-weight grotesk title set very large, a heavy second-language line under it, small mono meta text in the corners like a broadcast HUD.
- Beat lock: every shot starts on a beat, every key action lands on a beat.
Mood: premium broadcast identity; flat colour, light grain, one soft light sweep as the only gradient.
Not an isometric infographic (no depth, no 3D), not Swiss motion graphics (the figure and the pattern carry as much as the type), not a cartoon (figures never emote, have no faces), not a data film (numbers are labels, not charts).
2. Materials & rendering
- Flat Canvas2D, rendered clean; grain only at mux time. One square grid (cell ≈ 1/6 of the frame height) rules bands, transitions and walls.
- Pattern model: fill with a mid-dark tone; on grid nodes randomly place cell-radius discs in random tones and discs-with-inner-discs; then a fraction of cells get one motif from a library of generic geometry (quarter-discs, fish-scale waves, dot and triangle scales, concentric arcs, sun wheels, checkers, crescents…), rotated by multiples of 90°; never mascots, landmarks or logos. Draw it wider than the screen so any row can shift one cell.
- Figure model: unit = body height, origin at the hip; draw far limbs (mixed ~40 % toward the background), torso, near limbs, head. A second person sits behind, mixed further (~60 %).
- Stage: the figure stands on a large sun disc in a neighbouring tone, crossed by one faint light sweep.
- Figure environments (water, nets, ramps) live in the figure's own buffer, masked to zero before the text column.
3. Colour logic
- A set of five hues + one dark neutral, each hue in five tones (darkest → lightest, the middle one is the base). A chapter = one hue; a multi-hue chapter rotates through the set per card.
- Under type the pattern runs at low contrast; on chapter cards at full contrast.
- Light hues take dark type and dark figures, and their disc uses the lighter tone; dark hues take cream. On the dark neutral, the disc uses a lighter tone or it vanishes; its accent is one warm hue.
- One cream and one ink serve every hue. No outlines, no highlights except the sweep.
- Choose the set from the topic. Examples: warm festival (red, violet, forest green, gold, ochre); coastal (navy, teal, coral, sand, sky); kitchen (tomato, basil, saffron, aubergine, flour cream). Invent names for your hues; never borrow an event's colour names.
4. Type & subtitles
- Title: a 900-weight grotesk (e.g. Inter Tight), 150–330 px at 1080p, tight tracking, auto-fitted. Second language: a heavy face under it. Meta: a mono (e.g. DM Mono) ~22 px, wide tracking; tags as mono chips.
- HUD on every card: mono corner text and a progress bar of N ticks (current tall, done dimmed, rest faint), N derived from the item list.
- Bilingual rule: one language is always the big title; the second language is chosen per version by a switch, never by hand-editing cards.
- The card type is the text layer. With narration, captions are a mono line on a solid band in the chapter hue, bottom, never over the title; hold ≥ max(1.8 s, speech + 0.6 s). A card that must be read (a spec, a number) holds ≥ 1.6 s.
- Deliver an
.srtof the title cards, generated from the edit list.
5. Motion quality
- A grid of time: pick one tempo for the film; every shot starts on a beat and every key action in a pose (release, contact, landing) lands on an integer beat.
- Pace ladder: normal cards last one bar; related sub-items become half-length rapid-fire runs. A second tempo comes from dividing the beat, never from changing tempo.
- Easing: exponential ease-out for entrances, exponential in-out for whips and wipes; a back ease once at most.
- Type enters by mask slide-up: title first, second line ~0.08 s later, tag after; meta lines type on without a caret. Short cards compress all timings.
- Figure assembly: the figure arrives as horizontal bands sliding into register from alternating sides.
- Backgrounds breathe: rows drift in alternating directions; on chapter cards they jump a quarter-cell on beats.
- Transitions snap to the grid: sliding rows, dropping columns, a push, quarter-discs from cell corners, an iris from the last figure, a tile flip. Rotate them; each lasts a few frames around the cut.
- Motion blur only where things move: sub-frame averaging inside transition and whip windows; static frames render once.
6. Camera grammar
Mostly a locked flat frame; energy comes from row shifts, transitions and figure actions. When the camera moves, it moves along the grid. Opening and ending come from the topic.
| Move | What it expresses | Can serve |
|---|---|---|
| Locked flat frame | clarity, the card as a poster | any item; a spec that must be read |
| Lateral track with a one-screen whip per beat | one continuous family, momentum | a line of stations; a production line; a route |
| Slow drift along a row | calm, browsing | a gallery; a menu; a slower chapter |
| Vertical scroll by grid rows | ranking, depth, descent | a top-ten; floors of a building; ocean depths |
| Push into one motif cell until it becomes the next frame | zooming into a part | an ingredient inside a dish; a city inside a map |
| Pull back to a tiled wall of items | the whole set at once | a summary; "all of them"; a comparison |
| Tiles collapsing to one point | many become one | a merger; a shared goal; a count reaching zero |
| Split screen along a grid line | two items side by side | versus; before/after; two versions |
Framing: text and figure never overlap (one text column, one stage); horizontal poses move the stage outward. The HUD stays in the corners. Allowed transitions are the grid ones in §5; never dissolves, 3D camera moves, handheld shake or depth of field.
7. Sound palette
- Type and music can carry the film alone; a voice is optional, calm, one line per chapter at most.
- Percussion is the spine: choose a drum family that suits the topic (frame drums, taiko, marching snares, steel pans, body percussion, an electronic kit) plus rim clicks, claps, shakers and crashes.
- Colour instruments on top: Karplus-Strong plucks (any lute, koto or harp-like voice, with pitch bends), mallets (marimba, glockenspiel), detuned-saw chord stabs, a synth bass, pads, risers and a drone.
- Per-family accents: every shot onset gets a hit chosen by chapter (a splash, a pok, a clank, a crack, a whoosh, a sizzle): foley reads the chapter's matter.
- Picture-locked by data: the score reads the edit list's shot times and places accents there; check every onset to ±15 ms.
- Silence as a tool: half a beat of drums-and-bass-muted before a big hit, or one whole bar stripped to a single ticking sound under a count.
- Mix: glue compressor and limiter, −14 LUFS, ≤ −1 dBTP.
8. Native moves
A menu: use the ones your film needs.
- One card = one item. The count is the hook ("ALL N"). Fits content like: every tool in a workshop; every station of a subway line; every dish of a regional cuisine.
- Chapter = colour. Families become hues so the viewer always knows where they are. Fits content like: instrument sections of an orchestra; food groups; departments of a company.
- One verb per item. One body action readable in a bar, or a prop action (a gear turns, a cup pours). Fits content like: yoga poses; kitchen techniques; jobs on a construction site.
- Pace ladder run. A sub-family at double speed. Fits content like: four knife cuts; the bones of a hand; weekday routines.
- Long take down a track. Stations one screen apart, the camera whipping on each cut. Fits content like: the steps of a recipe; checkpoints of a marathon; stages of a rocket launch.
- Count-up slam. A big number rolls to its value on a bar. Fits content like: tonnes recycled; members of a club; years of a company.
- Icon wall. All pictograms return as a grid and turn one colour. Fits content like: a summary of a year; a periodic table of habits; a team roster.
9. Pitfalls of the medium
- Figure environments spread into the text column → render the figure layer into its own buffer and fade it out with a gradient mask before the text.
- Cream on light hues fails contrast → dark type and dark figures on light hues; their disc is the lighter tone.
- The sun disc vanishes on the dark neutral → use a lighter tone for it.
- Pattern canvases are large and seeded per card → cache with a bounded LRU; an unbounded cache runs out of memory with parallel renderers.
- Horizontal poses (swimming, lying, crawling) cross the title → move the stage outward and narrow the text column.
- Imitating a real identity too closely → your own palette, mark and slogan from the grid motifs.
10. Engine
In demo/: engine.js (G.pattern, G.motif, G.joints / G.drawFigure, G.keyPose, G.maskText, easings), poses/base.js (pose contract, props G.P), edl.js (edit list in beats), scenes.js (layouts, transitions, HUD, motion blur), lab.mjs (pose sheets), music/music.py (picture-locked score with onset report). API table, pose convention and a minimal example: DEMO.md.
11. Variation space
You decide the items, grouping, verbs, palette, chapter order, any long take, the pace ladder, opening, ending, music and voice. All far from our demo:
- Structures: a countdown (items ranked from N to 1, the camera scrolling down rows, the pace speeding up as the list shortens); a day in pictograms (one figure, one card per hour, the palette moving from dawn to night hues); an assembly (each card adds one part to a single growing pictogram machine seen in the corner).
- Openings: a single motif cell (one cell fills the frame and splits into the grid); mid-action (the first pictogram is already running when the type slams in); a blank grid (hairlines draw the grid on the beat before any colour appears).
- Endings: one card held long (the last item stays while the pattern rows stop, one by one); the figure walks off (the last pictogram exits the frame and the HUD ticks out); reverse assembly (the grid disassembles into rows that slide off-screen, leaving cream).
How our demo was made (story, shots, score, end card, build, engine reference): DEMO.md. Read it after your treatment exists.
Director's notes for the demo film
One example among many. Don't reuse its story, arc, shots, props or timings.
Demo: Aichi-Nagoya 2026 — All 43 Sports (EN-JP version, 163.6 s, 1920×1080 @ 60 fps, 150 BPM, 70 sport cards, no narration) · pictogram-motion.mp4 · source in demo/ (local, not published) · engine: Canvas2D (engine.js + scenes.js + poses/*.js), headless Chrome frame capture, numpy-synthesised score.
Rights note. The demo is a fan-made promo for a real event, the 20th Asian Games Aichi-Nagoya 2026. The event name (AICHI-NAGOYA 2026 / THE 20TH ASIAN GAMES), the slogan (IMAGINE ONE ASIA / ここで、ひとつに。), the emblem-like red-sun-and-rings mark drawn in code, the five-colour palette and its colour names, and the grid pattern language all follow the organiser's identity. They belong to the organiser (Aichi-Nagoya Asian Games Organising Committee / Olympic Council of Asia).
demo/ref/holds official images downloaded from the Games' website for reference only. They are never drawn into the film and must not be redistributed. Any other film made from this code must replace all event-specific elements (see Replacing the event elements).
Story & structure
A fan promo listing every sport of a multi-sport games, one card per sport, on the beat. How the demo used the style's native powers:
| Native power | Story use |
|---|---|
| One card = one item | Any list of 20–80 things: every event of a games, every tool in a kit, every station of a process. The count is the hook ("ALL 43 SPORTS"). |
| Chapters = colours | Group the list into 5–7 families. Each family gets a palette hue, so the viewer always knows where they are. |
| Pictogram action | Every item needs one verb that reads in 1.6 s (throw, jump, swing, pedal). If an item has no body action, give it a prop action (a gear turns, a cup pours). |
| Pace ladder | Normal cards are 4 beats (1.6 s). Sub-families become 2-beat rapid-fire runs (4 swim strokes, 4 cycling disciplines, the racket sports), which gives the film a second tempo without changing BPM. |
| Long take | The first and biggest chapter is one sideways camera move down a track: 14 stations one screen apart, with a whip-pan on each beat-cut. Use it for the "hero" family. |
| Grand finale grid | All items come back as a 9×5 icon wall, flip in, turn one colour, collapse into a single sun, then the slogan and the end card. |
Story shape (demo): heartbeat cold open (a red dot pulsing on ink, rings drawing around it) → title slam on a full five-colour pattern → three count-ups (43 SPORTS / 469 EVENTS / 16 DAYS) → "ON YOUR MARKS… SET…" with half a beat of silence → 7 chapters, 70 cards → icon-wall finale → slogan → end card → fade to black over the music's ring-out.
Adapting a topic: list the items; group them into 5–7 chapters; give every item a one-beat verb; pick one chapter for the long take; choose which runs go 2-beat; write a two-line slogan for the finale.
The demo's look, as built
A catalogue film: a set of N items (sports, products, features, dishes, steps) is shown one card at a time, fast, on the beat. Each card = one pictogram doing one action + a huge title + a second-language line + a mono index "07 / 43". Cards are grouped into chapters. Each chapter owns one colour of a five-colour palette. A chapter card with a full-bleed pattern opens each group.
The recognisable ingredients are:
- The grid pattern ("core graphic"): a 180 px square grid. Big discs and half-discs span cells. About half of the cells carry a small motif (fish-scale waves, dot scales, triangle scales, striped sunset, gear/sun wheel, crescent, concentric arcs, checker, gradient quarter-disc…). The pattern uses 5 tones of one hue, and whole rows are cut and shifted sideways. The row shift is the signature: it drives the backgrounds, the chapter beats and the main transitions.
- The pictogram athlete: flat, faceless, built from rounded bars with a disc head. The near-side limbs are drawn in the foreground colour and the far-side limbs in a colour 42 % mixed toward the background. Props are just as geometric: discs, rings, capsules, straight lines.
- Type as architecture: a 900-weight grotesk title (Inter Tight) set 150–330 px tall, and a heavy CJK line (Noto Sans JP) under it. Small DM Mono meta text sits in the corners, as on a broadcast HUD.
The mood is premium sports-broadcast identity: flat colour, no gradients except one soft light sweep, no outlines, no textures except a very light film grain.
Shots
| # | Section | Camera / picture | Native move / note |
|---|---|---|---|
| 1 | Cold open | Locked frame: a red dot pulsing on ink like a heartbeat, rings drawing around it | the only oBack ease |
| 2 | Title | Slam on a full five-colour pattern; rows transition out | row-shift reveal |
| 3 | Count-ups | 43 SPORTS / 469 EVENTS / 16 DAYS, one bar each | count-up slam |
| 4 | "ON YOUR MARKS… SET…" | Locked; half a beat of silence before the first chapter | silence before a big hit |
| 5 | Athletics (chapter 1) | The long take: 14 stations one screen apart, whip on every cut | long take down a track |
| 6 | Chapters 2–7 | Locked cards, layouts A/B/C/M rotated per chapter, 2-beat runs for swim strokes, cycling and rackets | chapter = colour; pace ladder |
| 7 | Finale | 9×5 icon wall flips in, turns one colour, zooms to 0.9, tiles collapse into one red sun | icon wall; many become one |
| 8 | Slogan + end card | Sun and rings rise and shrink; slogan, title, dates, colour bars, credit line; fade to black over the ring-out | see End card |
The camera exists only in the long take (trackScene). There, 14 stations are laid one screen width apart. The camera drifts at 42 px/s and on every cut whips one full screen (E.ioExp from 0.14 s before the beat to 0.24 s after). Two background rows of 360 px motif cells scroll at parallax 0.28 and 0.42. The deep-red track (y 780) has cream lane lines, and dashes roll faster for running events (RUNNING speeds). An event spec (100M, 4×100M, 7.26 KG…) is painted on the track, sheared −0.35, at 12 % cream. Everywhere else the frame is locked and flat; energy comes from the row shifts, transitions and figure actions.
Intro and finale "camera": the finale wall zooms to 0.9 with 10 px gutters, then every tile collapses to the centre and a red sun grows out of it. The end card's sun and rings rise from the centre to y 330 as they shrink to 0.62×.
Score structure
- No narration. The film is carried by type and music. The subtitle file is a title-card track only (see End card).
- Score: original, fully synthesised in
demo/music/music.py(numpy + scipy; no samples). "Wadaiko × electronic", 150 BPM 4/4, D miyako-bushi (D Eb G A Bb) over D minor harmony, 101 bars = 161.6 s plus a 2 s ring-out. Instruments are synthesised: odaiko / nagado / shime taiko, ka rim clicks, kick, clap, snare, hats, shaker, crash, plus foley-like hits (splash, drop, pok, clank, crack). Shamisen and koto are Karplus-Strong plucks (ks_render, koto with pitch bends); there are also a synth bass, pads, detuned-saw chord stabs, risers and a drone. - Picture-locked by data:
music.pyreadsmusic/timeline.json(exported fromedl.js). Every shot start gets an accent chosen by chapter (card_accent: splashes for aquatics, pok for rackets, odaiko + stab for combat, clank for power…). Chapter and finale starts get abig_hit, preceded by a half-beat of silence (drums and bass muted). The report checks each of the 79 shot onsets to within ±15 ms (demo: 79/79 pass, offsets ≤ 0.2 ms). - Master: stems
music_bed.wav+sfx.wav, a 2:1 glue compressor and a limiter, at −14 LUFS integrated and ≤ −1 dBTP (demo: −14.11 LUFS, −1.12 dBTP).music/report.txtholds loudness, band energy, onset and gap checks. Render time is about 30 s. - Section layout is hard-coded in
compose()(EXPECTED = dict(intro=0, ath=12, …, finale=89)in bars). If the EDL changes, the assert fires. For a new film, re-writecompose()for the new chapter bars (hand it to a music sub-agent withtimeline.jsonand this section) and keepcard_accent,big_hitand the report as they are.
Palette & props
Canvas: 1920×1080, grid G.CELL = 180 (10.7 columns × 6 rows). Everything snaps to that grid: the chapter band is 2 rows tall, transitions cut by 180 px rows or 240 px columns, and the finale wall is 9×5.
Palette (engine.js → G.PAL; each hue has five tones t[0] darkest → t[4] lightest; t[2] is the base). The demo's values were sampled from the official core graphic:
| key | demo name | t[0] | t[1] | t[2] base | t[3] | t[4] | fg (type & figure) |
|---|---|---|---|---|---|---|---|
| red | Jonetsu Red | #c21d15 |
#d22419 |
#e83220 |
#ec5a26 |
#f07a45 |
cream #fbf6ec |
| purple | Kakitsubata Purple | #231a6e |
#2c2084 |
#4e3a93 |
#644d9d |
#7a62a9 |
cream |
| green | Shinrin Green | #006d35 |
#008440 |
#079a3e |
#2da547 |
#68b15d |
cream |
| gold | Kinshachi Gold | #b48900 |
#c69b00 |
#d5b102 |
#debc2f |
#e7d16c |
dark #211904 |
| ochre | Dento Ocher | #946a2e |
#ac7d38 |
#c18e46 |
#d2a55c |
#e0bf84 |
dark #1e1409 |
| ink | Sumi | #0f0c15 |
#16121d |
#1e1a27 |
#2a2535 |
#3a3447 |
cream, accent = gold |
Cream G.CREAM = #fbf6ec, ink G.INK = #15111c. Gold and ochre take dark type and dark figures; the others take cream. G.CYCLE = ['red','purple','green','gold','ochre'] is the rotation used by multi-colour chapters and the intro pattern. On cards the pattern is drawn at spread 0.42 (low contrast, so the type reads), and at spread 0.95 on chapter cards (full contrast).
The pattern (G.pattern(palKey, seed, {spread, density})): fill with t[1]. On each grid node, 34 % of the time place a disc of radius CELL in a random tone, and 16 % of the time a t[3] disc with a t[0] half-radius disc inside it. Then a fraction density of the cells get one of 14 motifs (G.motif kinds 0–13): quarter-disc, half-disc, disc + dot, striped sunset, seigaiha fish-scale waves, dot-scale disc, triangle scales, concentric arcs, sun wheel / gear, checker, crescent, S-wave, gradient quarter-disc, fading bars. Each motif is rotated by a random multiple of 90°. The canvas is 3 cells wider than the screen, so any row can shift ±1 cell. All the motifs are generic geometry. Do not draw event mascots, landmarks or products into cells.
Pictogram figure (G.joints + G.drawFigure): the unit is body height = 1 and the origin is the hip. Segment lengths are torso 0.30, neck 0.045, head radius 0.082, upper arm 0.16, forearm 0.15, thigh and shin 0.225. Widths are torso 0.145, arm 0.076, leg 0.094, all with round caps. The draw order is far leg, far arm (colour far = fg mixed 42 % toward the background), then torso, near leg, near arm, head (fg). A second person (opponent or partner) is drawn behind in far2 (60 % mix). In cards the figure stands on a sun disc (r 360–380 px, tone t[1] on light-fg hues, t[4] on gold/ochre, t[3] on ink) with one slow 9 % white light sweep across it.
Card layouts (layoutOf(s), rotated per chapter by an order string such as ball: 'ACBAB'):
- A: text block left (x 130, baseline 610); disc and figure right (centre 1290, or 1370 for horizontal swimmers); a 300 px outline index number bottom-right at 20 % opacity.
- M: mirror of A (figure left at 640, text right-aligned at 1800).
- B: the title fills the frame (330 px, colour
t[1]/t[4]"ghost" tone, a single line fitted to 1760 px). The disc and figure sit over the title. A bottom info bar has the JP name at 80 px, the EN name at 44 px and the mono meta right-aligned. 2-beat racket runs always use B. - C: a solid 820 px left panel (
t[0]ort[4]) that wipes in, with a 90 px hairline grid at 10 % and the text block on it; the pattern, disc and figure are on the right (1370).
Type (demo/fonts/, all OFL):
- Title: Inter Tight 900, 170 px (150 in C, 160 on the track), letter-spacing −0.02 em, auto-fitted with
G.fitFontto 820 px. - Second language: Noto Sans JP 900 at 92 px (EN-JP) or Noto Sans SC 900 (ZH version, with an extra JP line at 28 px/500).
- Meta: DM Mono 500 22 px, letter-spacing 3 px, e.g.
ATHLETICS 01 / 43. Tags: a mono chip (5×5 · 3×3,NEWin red). - HUD (every card and chapter): DM Mono 17 px in all four corners (event line top-left, chapter top-right) and a 43-tick progress bar at the bottom. The current item is a tall 4 px tick; done items are at 75 %, the rest at 28 %.
Grain: added only at mux time (noise=c0s=4:c0f=t+u). The canvas itself is clean.
Motion, as built
- Grid of time: 150 BPM, 1 beat = 0.4 s, 1 bar = 1.6 s. Every shot starts on a beat (
edl.js), and every key action in a pose lands on an integer beatu(release, contact, landing). - Easing:
E.oExp(exponential ease-out) for almost every entrance;E.ioExpfor whip-pans and wipes;E.o5/E.o3for panels and discs;E.oBackonly for the intro's first dot. - Type entrance:
G.maskTextslides each line up from below a clip rectangle (title over 0–0.5 s, JP line at +0.08 s, tag at +0.2 s). Meta lines type on (G.typeText, no caret). 2-beat cards compress all timings by 0.7. - Figure assembly: for the first 0.42 s (0.28 s on 2-beat cards) the figure layer is cut into 54 px horizontal bands that start offset alternately left and right by 90–250 px and slide into register (
stageFigure(..., asm)). This echoes the pattern's row shift. - Backgrounds breathe: odd rows drift right and even rows drift left at 26 px/s, plus a fixed sine offset per row. On chapter cards every row jumps a quarter-cell on each beat of the second bar (beats 4–7), in time with the drums.
- Chapter card: a 2-row solid band grows from the centre (
E.o5). A 330 px outline chapter numeral rises into it, and the title and JP line slide up. Hairlines sit above and below the band. - Transitions (
transType, window 0.12 s before to 0.26 s after the cut):rows(6 rows slide in alternating directions, used into every chapter and out of the intro),cols(8 columns drop or rise, used after chapter cards and in 2-beat runs),slide(racket runs: a push with a thin shadow edge),quarter(quarter-discs grow from cell corners, used into the finale),iris(a circle opens from the previous card's figure centre, with a shrinking ring),flip(an 11×6 grid of tiles flips over). Normal cards rotate['quarter','rows','iris','flip','cols'][ci % 5]. Inside the athletics long take there is no transition: the camera whips instead. - Motion blur only where it moves:
isFast(t)is true in the transition windows, and thenG.renderFrameaverages 5 sub-frames over a 0.5/60 s shutter. Static card frames render once.
End card
- The type on the cards is the text layer; nothing extra is burned in.
pictogram-motion.srtlists the title cards (intro lines, chapter titles, each card "01/43 ATHLETICS — SPRINT / 短距離", slogan, end card), generated from the EDL bydemo/srt.cjs. - Bilingual rule: EN is always the big title. The second language is chosen by
?lang=:ejputs Japanese in the big second line (s.jpBig || s.jp) and drops the small JP line; the default (ZH) puts Chinese big and Japanese small. - End sequence (demo): cream end card, red sun + five-colour rings → IMAGINE ONE ASIA / ここで、ひとつに。 (the official slogan) → AICHI-NAGOYA 2026, the dates, five colour bars and the credit line → fade to black (the last 1.3 s) → 2 s black tail over the music's ring-out.
- Credit line: our library's demo ends with "LemoLab × Claude Opus 5.5". It sits on the end card, centred under the five colour bars:
G.maskText(ctx, 'LemoLab × Claude Opus 5.5', W / 2, 1000, F.mono(24), rgba(G.INK, 0.6), inv(0.6, 1.2, l), { ls: '4px', align: 'center' })infinaleScene(right after the colour-bar loop). It slides up 0.6 s after the end card lands (just after the bars), holds for about 3.5 s and fades out with the card. It is quieter than the date line, so it never competes with the title. This sign-off belongs to this library's demo only; a user's film carries no LemoLab credit and no copy of this card (take the line out offinaleScenewhen you adapt the code).
Build notes
styles/pictogram-motion/demo/
index.html loads fonts + edl.js + engine.js + poses/*.js + scenes.js; exposes window.render(t), DUR, READY
edl.js edit decision list: every intro/chapter/card/finale shot with beats, names (EN/ZH/JP), pose, flags
engine.js G: easing, rng, palette, grid pattern + motifs, pictogram skeleton, text helpers
poses/ base.js (pose API + props G.P) + one file per chapter: ath aqua ball combat power nature asia (70 poses)
scenes.js intro, chapter card, card layouts A/B/C/M, athletics long take, finale, HUD, transitions, motion blur
render.mjs headless-Chrome frame renderer (stills / video, N workers → seg_*.mp4 + list.txt)
shot.mjs quick JPEG stills (+ --sheet contact sheet via sheet.py)
lab.mjs lab.html pose check sheets; lab.mjs also screenshots cover.html / cover34.html (4:3 and 3:4 posters)
mux.sh concat segments → 2 s tail → grain → + music.wav → mp4
srt.cjs title-card .srt from the EDL
music/ music.py (score), export_timeline.cjs (EDL → timeline.json), timeline.json, report.txt
fonts/ Inter Tight, Noto Sans SC, Noto Sans JP, DM Mono (OFL)
ref/ official reference images (NOT used by the film, do not publish)
All commands run from styles/pictogram-motion/demo/. Node uses the repo's node_modules/playwright-core, and Chrome is found by core/render/browser.mjs (the playwright headless shell or PLAYWRIGHT_CHROME). The language defaults to EN-JP.
cd styles/pictogram-motion/demo
# 1. Edit → timeline for the music (re-run after every change to edl.js)
node music/export_timeline.cjs # → music/timeline.json
# 2. Score (≈30 s; writes music/music_bed.wav, sfx.wav, music.wav, report.txt)
../../../.venv/bin/python music/music.py
grep "shots pass" music/report.txt # expect 79/79
# 3. Pose check sheets and stills while working
node lab.mjs stills/lab_ath.png "p=sprint,hurdles,relay&n=8&beats=4"
node shot.mjs 24.8 63.2 104.9 153 --sheet # EN-JP stills → stills/t_*.jpg + stills/sheet.jpg
LANGQ=zh node shot.mjs 63.2 # Chinese version still
# 4a. Render — EN-JP (default): 9696 frames → out_ej/seg_*.mp4
node render.mjs video 12 # 12 workers ≈ 2 min on an M-series Mac; use 2–3 when sharing the machine
./mux.sh # → ../pictogram-motion.mp4 (163.6 s, 9816 frames)
# 4b. Render — Chinese version
LANGQ=zh node render.mjs video 12 # → out/seg_*.mp4
OUTDIR=out ./mux.sh # → out/pictogram-motion_zh.mp4
# Partial re-render (e.g. only the ending): START/END in seconds, into a separate folder
START=148.1333 END=161.6 OUTDIR=out_tail node render.mjs video 2
# 5. Subtitles and posters
node srt.cjs # → ../pictogram-motion.srt
node lab.mjs stills/cover_ej.png "lang=ej" cover.html # 2880×2160 poster
node lab.mjs stills/cover34_ej.png "lang=ej" cover34.html # 2160×2880 poster
How the two language versions differ: the only switch is the URL query ?lang=ej. render.mjs and shot.mjs add it from LANGQ (default ej); scenes.js reads it into G.EJ and changes the second-language lines (intro title, count-up labels, ON YOUR MARKS, cards, chapter cards, end card). render.mjs writes EN-JP segments to out_ej/ and ZH to out/; mux.sh reads OUTDIR (default out_ej). Picture timing and music are identical.
render.mjs pipes JPEG-100 screenshots into ffmpeg (libx264 -crf 12) per worker; mux.sh re-encodes the joined film at -preset slow -crf 14, 60 fps, GOP 120, AAC 320k, with a 2 s cloned tail so the music's ring-out plays over black.
Pitfalls we hit (several are tied to this demo's props):
- Figure environments ate the type. Water surfaces, ramps, fences and nets belong to the figure layer and spread into the text column. Fix:
stageFigurerenders into its own buffer and applies adestination-ingradient mask ({x0|x1|y1, f}) that fades the layer to zero 90–120 px before the text area. - Gold and ochre need dark type and dark figures (
fg #211904 / #1e1409), and their disc is the lighter tonet[4]. Cream on gold fails contrast. - The ink chapter's disc vanished at
t[1]. Uset[3]for the sun disc on ink. - Pattern canvases are big (2520×1260 each, and every card uses a new seed). Cache them, but cap the cache:
G.patternkeeps an LRU of about 10 entries. An unbounded cache eats memory fast when 12 browsers render at once. - zsh loops: in
while read lineloops the variable does not word-split. Use${=line}when passing lists of times tonode shot.mjs. - Horizontal poses (swimming) need the stage moved right (
wide: disc at 1370, text max width 700) or the arms cross the title. - Official identity: the demo imitates a real event's identity closely. That is fine for a fan piece but not for a style library; see Replacing the event elements.
Engine reference
engine.js (window.G)
| API | What it does |
|---|---|
G.E.{lin,io,o2,o3,i3,io3,oExp,iExp,ioExp,oBack,o5} |
easings (input clamped 0..1) |
G.clamp, G.lerp, G.inv(a,b,x) |
inv = normalised progress of x between a and b, clamped; used for every timing |
G.rng(seed) |
deterministic mulberry-style RNG |
G.PAL[key], G.CYCLE, G.CREAM, G.INK, G.mix(a,b,t), G.rgba(hex,a) |
palette and colour helpers |
G.pattern(pk, seed, {spread=1, density=0.5, cell=180}) |
returns a cached pattern canvas (3 cells wider than the screen) |
G.drawPattern(ctx, pat, offs(row)→px, {oy}) |
draws the pattern with a per-row horizontal offset (clamped ±1 cell) |
G.motif(ctx, kind 0–13, x, y, size, [bg, main, sub, accent], rng) |
one patterned cell |
G.joints(pose) → {hip, neck, sho, head, up, aL, aR, lL, lR} |
skeleton from a pose object |
G.drawFigure(ctx, J, nearCol, farCol) |
draw the pictogram (unit = body height) |
G.keyPose(keys, u, loop?), G.lerpPose(a,b,t) |
keyframed poses in beats; each key {b, ...angles, e:'easing'} |
G.runCycle(phase, k, {lean}), G.walkCycle(phase, k) |
procedural gait (phase in cycles) |
G.fitFont(ctx, text, 'en'/'zh'/'jp', weight, px, maxW, ls) |
shrink font size to fit |
G.maskText(ctx, text, x, y, font, col, p, {align, ls, dir}) |
mask slide-up entrance, p 0→1 |
G.typeText(ctx, text, x, y, font, col, p, {align, ls, caret}) |
typewriter |
G.hair(ctx, x, y, w, col, p, lw) |
hairline wipe |
Pose angle convention: rot = whole body (positive = pitch forward); torso = upper-body lean; head = nod. Limbs are measured from "straight down = 0°", with forward = 90°, overhead = 180° and backward negative. Arms are in torso space and legs in body space. aR2 and lR2 (forearm, shin) are absolute in the same frame, not relative to the upper segment. R = near side (bright), L = far side (dim). x, y move the hip.
Pose definitions (poses/*.js, contract in poses/base.js)
POSES.name = {
pose: (u) => ({...}), // or keys: [...] + loop: beats (u = beats since the card started)
back: (ctx, J, u, C) => {}, // props/environment behind the figure (unit = body height, origin = hip)
front: (ctx, J, u, C) => {}, // props in front (ball, racket, bow)
second: (u) => ({...}), secondX: [dx, dy], secondFace: -1, // opponent / partner, drawn in C.far2
fig: { s, x, y }, // scale/offset inside the stage
iconU: 0.6, // which beat to freeze for the finale icon wall
};
// C = { fg, far, far2, acc, bg, line } from G.colorsFor(pk, discColour)
// props: G.P.ball, G.P.racket, G.P.stick, G.P.ground, G.P.speed (speed lines), G.P.arc (flight arc)
Check poses with node lab.mjs out.png "p=a,b,c&n=8&beats=4&pal=red": each row is one pose sampled at n moments over beats.
edl.js
chapter(id, no, en, zh, jp, palKey) adds an 8-beat chapter card. card(n, en, zh, jp, pose, {beats=4, sub, fam, jpBig, track, water, racket, family}) adds one item. fam: true means the big title is sub and en becomes the small family line. track: true puts a card into the long take (all track cards must be consecutive). racket: true gives 2-beat B-layout cards with slide transitions. palKey 'multi' rotates G.CYCLE per card. Intro and finale are {kind:'intro'|'finale', beats: 48}. The script computes b0, t0, dur, then EDL.DUR = total beats × 0.4 s.
scenes.js
G.renderFrame(ctx, t) → frame() finds the shot by binary search, decides whether t is inside a transition window, renders A and B into offscreen buffers and composites them, then draws the HUD. Key functions: cardScene (layouts via layoutOf), chapterScene, trackScene (trackCam, stationX, SPEC, RUNNING), introScene, finaleScene (GRID 9×5, rings()), hud, transType / composite, stageFigure(ctx, poseName, u, C, x, y, scalePx, asm, mask), patBg, multiBg, textBlock, sun, bigNumber.
Minimal example — a new card and a new pose
// poses/nature.js (or a new poses/xxx.js added to index.html)
POSES.kite = {
iconU: 1,
keys: [
{ b: 0, torso: -6, aR1: 150, aR2: 160, aL1: 20, aL2: 40, lR1: 10, lL1: -12 },
{ b: 1, torso: -12, aR1: 170, aR2: 175, aL1: 30, aL2: 60, lR1: 18, lL1: -20, e: 'oExp' }, // tug on the beat
],
loop: 2,
front: (ctx, J, u, C) => { // string + diamond kite, all geometry
const h = J.aR[2], k = [h[0] + 0.55, h[1] - 0.75 + 0.04 * Math.sin(u * Math.PI)];
G.seg(ctx, h, k, 0.01, C.line);
G.poly(ctx, [[k[0], k[1] - 0.12], [k[0] + 0.08, k[1]], [k[0], k[1] + 0.12], [k[0] - 0.08, k[1]]], C.acc);
},
};
// edl.js, inside a chapter
card(44, 'KITE FLYING', '放风筝', '凧揚げ', 'kite', { sub: 'NEW' }); // n = index shown as "44 / N"; update the total (see below)
Then: node lab.mjs stills/lab_kite.png "p=kite&n=8&beats=4" → node music/export_timeline.cjs → (update compose() if chapters moved) → node shot.mjs <t> → render.
Replacing the event elements
| What | Where | Replace with |
|---|---|---|
| Event name, edition, host city, dates | scenes.js 372–373, 398–407, 417, 539–541, 562–566; cover.html 94–114; cover34.html 90–113 |
your own title and date line |
| Slogan "IMAGINE ONE ASIA / ここで、ひとつに。/ 在这里,合而为一" | scenes.js 534–536; covers |
your own two-line slogan |
Emblem-like mark (red sun + five rings, rings(), RINGS) |
scenes.js 341–352, intro 356–375, finale 509–531 |
an original mark, e.g. your own shape built from the grid motifs |
| Palette names (Jonetsu Red, Kakitsubata Purple, Shinrin Green, Kinshachi Gold, Dento Ocher) and hexes | engine.js PAL (lines 45–51); the name is printed on chapter cards (scenes.js 246) |
a custom five-hue set with 5 tones each; keep the dark-fg rule for light hues |
| Item list, counts "/ 43", 43 HUD ticks, "469 EVENTS / 16 DAYS", finale "2026" tile | edl.js; scenes.js 118, 184, 414–418, 512, 570 |
your list and numbers (derive from CARDS instead of hard-coding 43) |
| Chapter layout in the score | music/music.py EXPECTED + compose() |
re-composed sections |
| Official reference images | demo/ref/ |
never ship; collect your own references |
Keep the grammar: the grid pattern with row shifts, pictogram athletes, the bilingual mask-slide type, the HUD with index and progress, beat-locked cards with a pace ladder, one long take, and the icon-wall finale.




