The motion rules General Translation work follows on the web and in films.
node scripts/install-skills.mjs gt-motion --project <dir>Run it from a Prototemplate checkout. It links skills/gt-motion into the project’s .claude/skills and .agents/skills; --copy vendors the folder instead, and --dry-run prints each step first.
General Translation (GT) makes localization tools for developers. Its product site, dashboard and blog live in the gt-cloud monorepo, and its design canon, brand deck, design lab and films live in Prototemplate. GT motion explains structure. Things assemble, reveal, connect or translate, and nothing moves to fill time. These rules hold for the web (Prototemplate, the gt-cloud landing, dashboard and blog) and for the HyperFrames films; the film process itself (scripts, narration, music, renders, critics, the composition contract) is in gt-films. Paths are relative to a Prototemplate checkout ($PROTOTEMPLATE) unless marked gt-cloud ($GT_CLOUD, origin/main unless a branch is named) or wiki (Kevin's skill wiki, skills/engineering/<name>/SKILL.md).
setLocale() from its host; the dossier's locale belt is its page's one clock; a scroll story runs on one scrubbed dial; a film runs on one paused timeline (DESIGN.md sections 8, 9 and 14).motion/films/jihe-yuanben/NOTES.md). In an assembly one group moves at a time with rests of 0.25 s or more between groups, turns stay in the plane, and paths stay short and never cross. When planning paths cannot make a move calm, change the idea..plate-row-in, with none under reduced motion and none for rows present at mount. The landing's one entrance is the quiet reveal (section 6).gt-ui/no-smooth-scroll enforces it (Prototemplate's .oxlintrc.json turns it off only for /present, which still scrolls on Lenis). gt-ui/no-gif-mark keeps every mark and demo frame drawn: SVG, the canvas field or LocadexMark.| Use | Ease |
|---|---|
| An arrival (fast in, long settle) | expo.out or power3.out in films; power2.out in the landing reveal and the stack story |
| A move from one place to another | power2.inOut |
| A process: a scan, a pulse, a counter, a loop lap, a scrubbed rise | none |
| A tone mix between dither fields | smoothstep, k * k * (3 - 2 * k), zero slope at both ends |
| Shell chrome | --pt-ease-out (ease-out); --pt-ease (cubic-bezier(0.2, 0, 0, 1)) for the sidebar column |
Bounce, elastic and back overshoot are refused (MOTION.md, "Motion"). The one overshoot in the gt-cloud landing is the split-flap face settle (apps/landing/src/components/blog/flap.ts, back.out(2.1) over 0.09 s), a mechanical flap landing. New work adds none; the back.out tweens in Prototemplate's /d/ directions and /present slides are explorations.
| Clock | Value | Source |
|---|---|---|
| Film beat grid | 0.5 s at 30 or 60 fps; arrivals and scene changes land on beats | MOTION.md |
| Reading hold | (words / 3) + 1 s after a sentence has fully arrived; nothing important changes while the eye reads it | MOTION.md |
| Stagger | 40 to 90 ms, in reading direction (left to right, top to bottom, right to left for Arabic) | MOTION.md |
| Dither resolve (globe into picture) | 350 ms | TransitionDemo.tsx RESOLVE_MS |
| Dither step (picture to picture) | 150 ms | TransitionDemo.tsx and gt-cloud FieldStack.tsx STEP_MS; Kevin, 2026-09-29: "make the dither transitions 2x faster" (the resolve went from 700 to 350 ms and the step from 300 to 150 ms) |
| Landing reveal | 0.62 s, stagger 0.055 s | gt-cloud reveal.ts |
| Plate row reveal | 180 ms ease-out, from opacity 0 and 4 px down | gt-cloud apps/dashboard/src/app/brand-tokens.css |
| Field first paint | 240 ms ease-out opacity keyframe on the empty field region, once, when the first tone is drawn; this is the one alpha change a dithered field takes, and every change between two states is a tone mix | same file, brand-field-picture-in |
| Shell chrome | --pt-dur-fast 120ms, -leave 140ms, -toast 160ms, -slide 180ms, -enter 200ms, -sb 220ms; all 0ms under reduced motion | src/components/viewer/tokens.css |
| Dither loop frame budget | 30 fps by default; dither reads well at low rates and keeps the main thread free | src/lib/dither.ts |
A camera drift (scale 1.00 to 1.04 over a scene, or a slow pan) is allowed on a plate image or a dither field and never on type. A still field may drift its sampling window under 3 percent over a beat, re-sampled per frame. Shimmer noise is refused (MOTION.md, "Texture").
MOTION.md allows four scene transitions in films, and on the web a dithered picture changes by the first of them.
Refused in films: the whole-frame cross dissolve, push, slide, zoom blur, spin, glitch and light leak. Refused for any dithered field: alpha fades, wipes, masks that move, content entrance animation, and a change of cell size inside a transition (the build log's "The dither transitions, and the grid they run on"). The dashboard's 240 ms first paint of an empty field region (section 2) is a load, and it is the only alpha change a field takes.
Viewer shell chrome moves transform or opacity only, on the --pt-dur-* tokens; the sidebar column is the one exception. The shared pt-fade-in and pt-fade-out keyframes live in tokens.css.
The sign-in to onboarding judges (2026-09-28) rejected a mask tween (a wipe), departure tweens (OAuth leaves in about 200 ms), entrance animation and view transitions; Kevin then dropped the arrival morph (2026-09-29). On gt-cloud main a step change is the 150 ms tone mix.
redirect() thrown in a Next page render paints an intermediate shell first.disableAnimation sheet stops first-paint transitions, so a first-paint fade is a keyframe.references/traps.md has the cause and the fix for each.
The material itself (the screens, the engines, the presets and the artifact picture standard) is in gt-dither. A dithered field changes state by the five rules of the build log (src/app/craft/libraries.ts, TRANSITION_RULES), each held in code as follows.
ditherToCanvas takes phase: a canvas n cells below another's needs phase.y = n % 8.mixFields(a, b, k), k = smoothstep(elapsed / duration)), and the ink is interpolated on the same curve (lerpInk in TransitionDemo.tsx).TransitionDemo solves the globe's gain on the resolve's first tick so its mean tone over the disc on screen equals the Blue Marble's.The shipped field adds two rules of its own.
from: current, so quick steps never return to a picture that has gone (gt-cloud apps/dashboard/src/components/brand/FieldStack.tsx, startMix; the contract is written on fieldController.ts).GLOBE_SCALE = 2, and a film picks 2 or 3 CSS px at 1920 by 1080 and keeps it for the whole film.scale (CSS px per cell). A full-bleed field at scale 1 is 1.3M field calls a frame at 1440 by 900, so it is never animated; scale 3 is the full-bleed default, and radialBurst animates at scale 4 or more or renders once (the cost table is in src/lib/dither.ts).GTDither.mix(a, b, p) applies the smoothstep itself, so drive p with ease: 'none'.DitheredMark.tsx) sweeps nested Bayer tiers across a mark by pure horizontal translate with ease none. Its windows are pre-rotated, because a rotate() window proved fragile under GSAP's transform origin. gt-isometric section 6 has its props and its driver.ditherMask.ts, on the Lottie branch) steps a layer through 65 SVG mask levels of the 8 by 8 screen at 2 px cells. The level only rises, and the layer is untransformed.DESIGN.md section 8 states the law, and src/components/shared/EverySentence.tsx builds it.
lang and dir; the container sets unicode-bidi: isolate. Per-character spans break Arabic joining and Devanagari matras, so no page or film splits a non-Latin sentence.lang and dir: the whole roster in one batched pass, cached, snapped to device pixels, re-measured on a debounced resize and on document.fonts.ready. Nothing is measured in a frame loop.power2.inOut). Under 720 px the em pins to the column, so a sentence that folds to two lines never tweens layout (founder: "slow and laggy on mobile").setLocale(loc) is the only intake. Requests debounce 0.25 s, leading and trailing; one mid-dissolve retargets the form, one mid-form kills the timeline and re-dissolves, a locale with the same text retags lang and dir only, and calls before boot are buffered. hops (1 to 5, default 2) sets the arrangements the swarm takes on its way to the print; the dossier hero runs 1.lang and dir with no tween.LocaleTag: a fixed 15 by 11 SVG flag, then the code in the host's mono on the baseline.resample in src/lib/glyph-field.ts and in gt-cloud's shared packages/ui/src/lib/glyph-field.ts).Every canvas or GL engine and every GSAP band follows one contract (DESIGN.md sections 9 and 11, docs/LIBRARIES.md).
rootMargin: '100% 0px').ScrollTrigger.create({ trigger, start: 'top bottom', end: 'bottom top', onToggle }) that syncs every ambient loop.gsap.matchMedia() reduced motion is its own branch that sets a designed static pose (the stack: all four slabs, the first beat lit, the shimmer band parked mid-glyph, the scan beam hidden); cleanup is mm.revert().visibilitychange.destroy(). Shared GL contexts persist for the session by design.MutationObserver on the root with attributeFilter: ['data-theme'], colors read from computed tokens.createDitherLoop holds 4, 5 and 6 for the CPU engine: one synchronous first frame, one frame and no rAF under reduced motion, a pause offscreen and on hidden tabs, and a destroy() that disconnects every observer. The host holds 1: the /docs plates create their engines when they first scroll near.
gsap.registerPlugin(useGSAP, ScrollTrigger)) and set up inside useGSAP(() => ..., { scope: root }). A canvas engine with no GSAP in gt-cloud mounts through useMountEffect (@generaltranslation/ui/hooks/use-mount-effect, as FieldStack.tsx does). Prototemplate's practices ratchet (pnpm lint:practices) fails on any bare useEffect missing from its baseline, and gt-cloud's gt-ui/no-use-effect bans it.gsap.ticker (TransitionDemo.tsx) and removes it in the cleanup.strokeDashoffset, transform, opacity and visibility it wrote. The landing reveal sets willChange only for the tween and clears it with the rest on complete: a standing will-change or a leftover 0 px transform makes a stacking context that paints over the bento rows' 1 px seams.is-live to the root once the timeline is seeded and painting, so a visitor's first frame is the story's own.Three web patterns have one reference each, and references/web.md holds them: the landing's quiet reveal (useQuietReveal, one batch from y: 16 and autoAlpha: 0, once, nothing under reduced motion), the stack story (a CSS-sticky figure on one scrubbed dial, so scrolling back plays it backward), and the seam's driven dial (--seam-cut, written by drags, tweens and keys with zero React renders).
expo.out or power3.out for a draw-on.d in el.dataset.traceD before an animation blanks it per tick (src/app/d/production/sections/Developer.tsx).pathLength to 1000 where offsets animate finely; GSAP rounds offsets to integers.beamAt(t), so its aperture pivots under the capstone while its land line runs the plate.Five dash traps were each found in a rejected round, and references/traps.md ("SVG dashes and lines", "GSAP") holds each with its proof: non-scaling-stroke makes Chromium ignore pathLength; dashes clip at a closed subpath's end; the sign of the parked offset picks the end a draw starts from, so read the first point of d before touching the timing (Kevin rejected a backwards draw three rounds running in August 2026); pad the gap past the path ('100 200', parked at 101 or -101); and GSAP cannot be frozen from outside the page.
The blog's Lottie translation figure (gt-cloud branch k/blog-lottie-translation, PR #5068: LottieTranslationWindow.tsx, lottieDocument.ts, lottieLoader.ts and ditherMask.ts in apps/landing/src/components/blog/) runs lottie-web 5.13 on canvas. It fills the glyph list, fits the track-matte buffers after a resize, sets setSubframe(false), hides a layer that keeps its box with visibility, drives followers from the leader's currentRawFrame, makes the loop a cut dithered through a still, decodes every mask level before the first step, and holds a poster frame under reduced motion. Each rule was proven by a render or a measurement, and references/traps.md has the proof.
Films use the HyperFrames lottie adapter: unzip each .lottie at build time with its images inlined, and seek every locale's player to one frame.
@gsap/react's useGSAP, ScrollTrigger and gsap.matchMedia(); the dither engines in Prototemplate src/lib/ (dither.ts on the CPU, studio-field.ts on the GPU, glyph-field.ts for glyph rain), indexed in docs/LIBRARIES.md with live plates on /docs; lottie-web with fflate for .lottie files.GTDither, GTSheet, GTGem and lottie-web 5.13, all loaded from motion/kit/ so a render never touches the network. gt-films owns the process.hyperframes-animation (atomic rules, blueprints, runtime adapters, scripts/animation-map.mjs for auditing choreography) and hyperframes-keyframes (punch-ins, camera moves, SVG draw and morph, seek-safe keyframes).Code shapes for a web band, the interrupted tone mix, the dashed ring, the Lottie follower and a film move, with the one film determinism rule this skill adds, are in references/recipes.md.
playwright-core is in Prototemplate's node_modules) or the wiki's agent-browser CLI. The in-app Browser pane pauses requestAnimationFrame, so canvases there come back blank.page.emulateMedia({ reducedMotion: 'reduce' })), in both themes, at 1440 and 390 wide; run pnpm check:pages --pages <id,id> on changed routes for layout shifts.requestAnimationFrame in the capture only to see a dither step's cells.npx -y hyperframes@0.8.106 check . with 0 errors, then animation-map.mjs, a contact sheet and the render frame by frame.power2.inOut, processes none; no bounce, elastic or back overshoot.lang and dir; width is the only animated layout property.destroy() or mm.revert() frees them.--pt-dur-* tokens and moves transform or opacity only; product pages have no entrance animation; the landing uses only useQuietReveal; no smooth scroll and no gif marks.check passes.Prototemplate: gt-films (the film process and the composition contract), gt-dither (the Bayer material, its engines and the artifact picture standard), gt-isometric (the shimmer, the tower's build and the scan beam), gt-diagrams (the doubled line and connector drawing), gt-components (the instruments' props: RevealSeam, EverySentence, LocaleTag), gt-graphics (stills), gt-landing-pages (landing bands, the read lines and the svh and dvh law). Wiki: hyperframes, hyperframes-core, hyperframes-animation, hyperframes-keyframes, agent-browser, design-engineering-polish, animated-component-libraries, plus gsap-scrolltrigger and lottie-animations for library API detail. Where a wiki skill suggests smooth scrolling, overshoot eases or loops that autoplay, the GT rules above win.
TRANSITION_RULES), src/app/craft/TransitionDemo.tsx.resample); src/components/shared/EverySentence.tsx; src/app/d/toolchain/sections/RevealSeam.tsx; src/app/d/toolchain/diagrams/DitheredMark.tsx.beamAt).gt-theme key)..plate-row-in, brand-field-picture-in); apps/dashboard/src/app/[locale]/signin/device/_components/DeviceCodeForm.tsx.#nd-sidebar[data-sb-ready]).no-smooth-scroll, no-gif-mark, no-use-effect); packages/ui/src/lib/dither.ts (the engine copy with simBase and the held stop()); packages/ui/src/hooks/use-mount-effect.ts.The folder skills/gt-motion as agents fetch it. Each file opens raw.
SKILL.mdreferences/recipes.mdreferences/traps.mdreferences/web.md