npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

timotion-kit

v0.2.0

Published

8-bit Monokai component kit for Remotion concept-explainer videos

Readme

timotion-kit

A Remotion 4 + React 19 component library, published on npm, for building "concept explainer" short-form videos: dark, 8-bit game styled vertical videos (Monokai palette, pixel fonts, hard-edged blocky panels, chiptune sound effects) that visually explain one concept per episode.

Install it once per project and update it like any other dependency — see Installing — instead of copy-pasting source between projects and letting each copy drift out of sync.

For AI-agent-facing conventions (episode-authoring workflow, non-goals, etc.) see the consuming project's own CLAUDE.md (in timotion-template or wherever you're building an episode) — that's project-specific and lives outside this package. This README is the human-facing reference for the kit itself.

Folder structure

timotion-kit/
├── src/
│   ├── theme.ts                # colors, safeArea, radius, typeScale, pixelShadow, statusColor/Glow, video config
│   ├── fonts.ts                # fontFamily / fontFamilyTitle / fontFamilyLabel (Google Fonts, loaded once)
│   ├── sfx.ts                  # sound name -> public/sfx/*.wav path map
│   ├── index.ts                # barrel: re-exports theme, fonts, sfx, components
│   ├── scripts/
│   │   └── generate-sfx.mjs    # synthesizes the chiptune WAVs — see "Installing" below
│   └── components/
│       ├── index.ts            # barrel: one export pair (component + prop type) per component
│       ├── common/
│       │   └── PixelSound.tsx  # shared leaf component — see "Why common/" below
│       ├── TerminalHeader.tsx
│       ├── EntityCard.tsx
│       ├── MessageTravel.tsx
│       ├── LogFeed.tsx
│       ├── ResultBadge.tsx
│       ├── Watermark.tsx
│       ├── PixelBackground.tsx
│       ├── DiagramBox.tsx
│       ├── FadeLine.tsx
│       ├── PulseStream.tsx
│       ├── Typewriter.tsx
│       ├── ChatBubble.tsx
│       └── TypingIndicatorBubble.tsx
└── dist/                        # built output (published to npm; not committed)

Standing rule: nothing inside src/ imports from outside src/. That invariant is what keeps the package self-contained and its build simple.

Why common/?

components/common/ holds only code that's genuinely shared between sibling components — as opposed to code a scene calls directly. Today that's exactly one file: PixelSound, which 6 of the other 13 components (TerminalHeader, EntityCard, MessageTravel, LogFeed, ResultBadge, Typewriter) import internally to trigger their sound effects. Everything else in components/ is used by scene code, not by another component, so it stays flat at the top level. If a second genuinely-shared piece ever emerges, it belongs in common/ too; a component only one other component happens to use (e.g. ChatBubble importing Typewriter) doesn't qualify — it's a normal dependency, not shared infrastructure.

The public import path is unaffected by this: import { PixelSound } from "timotion-kit" keeps working exactly the same, since the barrel re-exports it regardless of which file it lives in.

Installing

npm install timotion-kit
npx timotion-generate-sfx
  • npm install timotion-kit adds the package as a normal dependency — remotion, react, react-dom, @remotion/google-fonts, and @remotion/media are peer dependencies, so make sure your project already has those installed (any project scaffolded from timotion-template already does).
  • npx timotion-generate-sfx is a one-time step per project (run it again after any npm update timotion-kit that adds a new sound). It synthesizes the chiptune WAVs into your project's own public/sfx/ — required because Remotion's staticFile() always resolves against the current project's public/ folder, not into node_modules, so the actual audio binaries have to live in your project regardless of where the code that references them comes from.
  • Import everything from "timotion-kit" (the package root) in your own compositions: import { EntityCard, colors, safeArea } from "timotion-kit".
  • To pick up kit updates later: npm update timotion-kit.

Design tokens (theme.ts)

Palette is Monokai (the classic code-editor color scheme).

| Token | Value | Usage | |---|---|---| | colors.bg | #272822 | page background | | colors.bgTop | #3E3D32 | PixelBackground gradient top | | colors.star | #F8F8F2 | PixelBackground twinkling stars | | colors.cardFill | #2D2E27 | entity card fill | | colors.cardBorder | #49483E | default card border | | colors.divider | rgba(248,248,242,0.15) | thin header divider | | colors.textPrimary | #F8F8F2 | titles, card values | | colors.textDim | #75715E | dim meta text | | colors.textLabel | #CFCFC2 | card labels, log body text | | colors.green / greenGlow | #A6E22E / rgba | prompt, ok state, success | | colors.red / redGlow | #F92672 / rgba | warn/error state | | colors.amber | #E6DB74 | secondary accent |

statusColor / statusGlow map a Status ("idle" | "active" | "ok" | "warn" | "error") to the right color/glow — always source status colors from these maps, never hardcode green/red per component.

safeArea (marginX: 64, marginTop: 96, marginBottom: 140) is the standard inset from the frame edges, so content doesn't collide with platform UI on Reels/Shorts/TikTok.

radius is 0 everywhere — sharp pixel corners, no rounding, is the core of the 8-bit look. typeScale holds the font-size scale — extend it rather than hardcoding new sizes inline.

pixelShadow(color, offset?) produces a hard, unblurred offset shadow (Npx Npx 0 0 color) — the 8-bit equivalent of a soft glow. Always use this helper instead of writing a boxShadow string by hand.

video holds the target composition config: { width: 1080, height: 1920, fps: 30 }.

Typography (fonts.ts)

| Export | Font | Usage | |---|---|---| | fontFamilyTitle | Space Grotesk (500/700) | big titles | | fontFamilyLabel | JetBrains Mono (600/700) | short HUD labels/tags | | fontFamily | JetBrains Mono (400/500) | body/log copy, long-form text |

fontFamilyLabel and fontFamily are both JetBrains Mono at different weights — a deliberate two-family system (one display font, one mono). Text that might wrap to multiple lines should use lineHeight of at least 1.4.

Sound effects (sfx.ts, components/common/PixelSound.tsx)

Short 8-bit/chiptune WAVs, synthesized (not sourced/licensed) via scripts/generate-sfx.mjs, living in public/sfx/.

| Sound | File | Used by | |---|---|---| | blip | sfx/blip.wav | EntityCard pop-in, MessageTravel on arrival | | type | sfx/type.wav | LogFeed / Typewriter per-character reveal | | coin | sfx/coin.wav | ResultBadge positive (ok/active) | | error | sfx/error.wav | ResultBadge negative (warn/error) | | powerup | sfx/powerup.wav | reserved for bigger positive story-beats | | start | sfx/start.wav | TerminalHeader title entrance |

Play sounds through <PixelSound name="..." from={...} volume={...} enabled={...} /> (wraps <Audio> from @remotion/media) — don't import <Audio> directly in scene/component code. Every sound-playing component exposes a sound?: boolean prop (default true) so a caller can mute one instance without silencing the whole video.

Component reference

  • TerminalHeader{ conceptTag?, promptUser?, command, title, showCursor?, showDivider?, from?, durationInFrames?, sound?, typeCommand?, charsPerFrame? }. Concept tag + green prompt line + big title with a blinking pixel cursor block + divider. By default the command line fades in as a whole (durationInFrames splits into quarters for prompt/title/divider); pass typeCommand: true to have it type out character-by-character via Typewriter instead — title/divider timing then derives from the command's own length. Use terminalHeaderTypedDuration(command, charsPerFrame?) to size a scene's timeline around a typed header instead of guessing.
  • EntityCard{ label, value?, sublabel?, status?, glow?, width?, height?, from?, durationInFrames?, sound? }. Dark sharp-cornered card with a square status dot. Self-contained, no self-positioning — the parent places it via flex/grid or absolute coordinates.
  • MessageTravel{ start, end, bend?, status?, icon?, from?, durationInFrames?, sound? }. Animates an icon traveling between two points along a straight or quadratic-bezier path (via bend). No line is drawn, only the icon moves.
  • LogFeed{ lines, from?, staggerFrames?, lineDurationInFrames?, align?, sound? }. A stack of [ok]/[warn] log lines that reveal progressively with a stagger.
  • ResultBadge{ title, subtitle?, status?, from?, durationInFrames?, sound? }. Bordered callout with a big colored headline + dim subtitle, for emphasis moments.
  • Watermark{ text?, from?, durationInFrames?, fontSize? }. Bottom-right credit text, always positioned via safeArea.
  • PixelBackground — no required props. Full-frame Monokai gradient plus a sparse field of slowly twinkling pixel stars, deliberately low-contrast. Always the first child of a scene's AbsoluteFill.
  • DiagramBox{ x, y, width, height, label, from?, durationInFrames? }. Bordered, unfilled region with a top-left uppercase label — the basic node in a node-and-connector diagram.
  • FadeLine{ x1, y1, x2, y2, from, to?, durationInFrames?, color?, strokeWidth? }. SVG connector line between two diagram nodes; fades in at from, optionally hard-cuts out at to. Render inside an <svg> that overlays the scene.
  • PulseStream{ x1, y1, x2, y2, activeFrom, activeTo, period?, travelFrames?, color?, glowColor? }. A repeating stream of glow-ring + solid-core dots fired along a line for [activeFrom, activeTo] — represents continuous data flow, not a one-off trip. Pairs with FadeLine, same <svg> overlay.
  • Typewriter{ text, from, charsPerFrame?, cursor?, cursorColor?, sound?, soundVolume?, style? }. Character-by-character text reveal with a blinking currentColor cursor and a per-char type tick — the reusable core behind every typing beat. Build scene-specific typed text on top of this rather than re-deriving typed-character/cursor logic.
  • ChatBubble{ text, from, align, accent?, typeDelay?, fontSize? }. A chat message bubble, left- or right-aligned, with Typewriter-driven text reveal. Use align="left" for the user's message, "right" for the AI's response.
  • TypingIndicatorBubble{ from, to, align? }. A "thinking" bubble (three dots pulsing in a wave) that hides itself once to is reached — chain a ChatBubble right after it with from={to}.
  • PixelSound (components/common/) — { name, from?, volume?, enabled?, durationInFrames? }. Sound-trigger wrapper; the shared leaf every sound-playing component above depends on.
  • Recap{ sections, x, y, width, from, bulletGap?, sectionStart?, sectionGap?, sound? }. A takeaways/best-practices/pitfalls style recap: named sections ({ title, accent, bullets }[]) stacked full-width (not squeezed into side-by-side columns, which cuts long bullets off), each bullet typing out in turn with a blinking cursor and its row space reserved up front (no layout jump as bullets start typing). Pair with recapDuration(sections, opts?) to size how long it needs to stay mounted (typing time + a readHold, default 3s) instead of guessing a fixed hold. Every episode so far uses the same 3-section shape (green TAKEAWAYS, green BEST PRACTICES, red PITFALLS) — use buildRecapSections({ takeaways, bestPractices, pitfalls }) to get that standard RecapSection[] from just the bullet lists, or construct RecapSection[] directly for a different set of names/colors.

Timing contract

Every animated component accepts Remotion-native from?: number (start frame, default 0) and durationInFrames?: number (entrance/travel duration, each component has its own sensible default) — the same semantics as native Remotion layer props. Internally each component computes localFrame = frame - from and drives interpolate() off that, with extrapolateLeft/Right: "clamp" always set.

Animation implementation rules

  • Drive all animation from useCurrentFrame() + interpolate(). No CSS transition/animation, no Tailwind animation classes — they don't render correctly in Remotion.
  • Use Easing.bezier(...) (or Easing.spring(...)) for eased motion.
  • Use the translate / scale / rotate CSS properties directly in style, not a transform string. For scale, pass output: "perceptual-scale" to interpolate().
  • Wrap the animated root element in <Interactive.Div name="..."> so it shows up correctly in Remotion Studio's timeline/trim UI.