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

@elucim/dsl

v0.22.0

Published

JSON/YAML DSL for declarative Elucim animations — define visualizations without writing React code

Readme

@elucim/dsl

Normalized JSON/YAML documents for animated Elucim scenes — designed for agents, editors, and content pipelines.

npm version license

@elucim/dsl lets you describe animated diagrams as data. A public ElucimDocument is the normalized single-scene shape: version: '2.0', scene, an ID-keyed elements registry, optional timelines, optional stateMachines, and optional metadata. The <DslRenderer> component renders these documents as interactive Elucim visuals — no React authoring required.

Install

npm install @elucim/dsl @elucim/core react react-dom
# or
pnpm add @elucim/dsl @elucim/core react react-dom

Quick Start

From JSON

import { DslRenderer } from '@elucim/dsl';
import type { ElucimDocument } from '@elucim/dsl';

const myDiagram: ElucimDocument = {
  version: '2.0',
  scene: {
    type: 'player',
    width: 800,
    height: 600,
    fps: 30,
    background: '#0d0d1a',
    children: ['orbit'],
  },
  elements: {
    orbit: {
      id: 'orbit',
      type: 'circle',
      props: {
        type: 'circle',
        cx: 400,
        cy: 300,
        r: 100,
        stroke: '#3b82f6',
        strokeWidth: 3,
        fill: 'none',
        opacity: 0,
      },
    },
  },
  timelines: {
    intro: {
      id: 'intro',
      duration: 60,
      tracks: [
        {
          target: 'orbit',
          property: 'opacity',
          keyframes: [
            { frame: 0, value: 0 },
            { frame: 60, value: 1, easing: 'easeOutCubic' },
          ],
        },
      ],
    },
  },
  defaultStateMachine: 'main',
  stateMachines: {
    main: {
      id: 'main',
      entry: 'intro',
      states: { intro: { timeline: 'intro' } },
      transitions: [{ id: 'entry-start', from: 'entry', to: 'intro', trigger: 'onStart' }],
    },
  },
};

function App() {
  return <DslRenderer dsl={myDiagram} />;
}

From YAML

import { DslRenderer, fromYaml } from '@elucim/dsl';

const yaml = `
version: "2.0"
scene:
  type: player
  width: 800
  height: 600
  fps: 30
  background: "#0d0d1a"
  children: [orbit]
elements:
  orbit:
    id: orbit
    type: circle
    props:
      type: circle
      cx: 400
      cy: 300
      r: 100
      stroke: "#3b82f6"
      strokeWidth: 3
      fill: none
      opacity: 0
timelines:
  intro:
    id: intro
    duration: 60
    tracks:
      - target: orbit
        property: opacity
        keyframes:
          - { frame: 0, value: 0 }
          - { frame: 60, value: 1, easing: easeOutCubic }
defaultStateMachine: main
stateMachines:
  main:
    id: main
    entry: intro
    states:
      intro: { timeline: intro }
    transitions:
      - { id: entry-start, from: entry, to: intro, trigger: onStart }
`;

const myDiagram = fromYaml(yaml);

function App() {
  return <DslRenderer dsl={myDiagram} />;
}

API

<DslRenderer dsl={doc} />

Validates a normalized document and renders it as React components. If validation fails, it displays error messages instead of crashing.

Props:

  • dsl: ElucimDocument — The normalized document to render
  • className?: string — CSS class for the wrapper div
  • style?: CSSProperties — Inline styles for the wrapper div
  • theme?: ElucimTheme — Custom color tokens as CSS custom properties
  • colorScheme?: 'light' | 'dark' | 'auto' — Inject light/dark theme variables automatically
  • poster?: 'first' | 'last' | number — Render a static frame instead of interactive playback
  • onError?: (errors: Array<{ path: string; message: string }>) => void — Callback for validation errors
  • ref?: React.Ref<DslRendererRef> — Imperative handle for programmatic control

validate(doc: unknown): ValidationResult

Validates a document without rendering it.

import { validate } from '@elucim/dsl';

const result = validate(myDoc);
if (!result.valid) {
  console.log(result.errors);
  // [{ path: 'elements.orbit.props.cx', message: 'Required numeric field "cx"...', severity: 'error' }]
}

fromYaml(input: string): ElucimDocument

Parses a YAML string into a validated ElucimDocument. It uses a JSON-compatible schema so YAML values such as on, yes, and NO stay as strings instead of being coerced to booleans.

import { fromYaml, ElucimYamlError } from '@elucim/dsl';

try {
  const doc = fromYaml(yamlString);
  // doc is a validated ElucimDocument, ready for <DslRenderer>
} catch (e) {
  if (e instanceof ElucimYamlError) {
    console.error(e.message);
    console.error(e.validationErrors);
  }
}

renderToSvgString(doc, frame, options?)

Renders a document to an SVG string without a browser DOM — useful for server-side rendering, thumbnails, and static export.

import { renderToSvgString } from '@elucim/dsl';
const svg = renderToSvgString(myDoc, 0);

Agent authoring helpers

@elucim/dsl/agent provides a small, deterministic toolkit for LLM and host workflows. It creates normalized documents, applies higher-level commands, generates explicit timeline/state-machine structures, and returns agent-readable quality reports.

import {
  applyAgentCommands,
  createDocument,
  evaluateSceneForAgent,
  inspectSceneForAgent,
  repairDocumentForAgent,
  sampleAnimationForAgent,
} from '@elucim/dsl/agent';

const doc = applyAgentCommands(createDocument({
  preset: 'slide',
  metadata: { title: 'Slope intuition' },
}), [
  {
    op: 'addElement',
    element: {
      id: 'title',
      type: 'text',
      role: 'title',
      intent: { purpose: 'Introduce the core concept' },
      layout: { x: 96, y: 96 },
      props: { content: 'Slope as local change', fill: '$title' },
    },
  },
  { op: 'addRevealTimeline', timeline: { id: 'intro', targets: ['title'], preset: 'fadeIn' } },
  { op: 'createStateMachine', stateMachine: { id: 'main', timelineId: 'intro', start: 'onStart' } },
]).document;

const report = evaluateSceneForAgent(doc);
const repaired = repairDocumentForAgent(doc);
const animation = sampleAnimationForAgent(repaired.document, 'intro');
const inspection = inspectSceneForAgent(repaired.document, { timelineId: 'intro' });

The agent helpers intentionally produce timelines and state machines rather than wrapper animation props. Use them when you want an LLM to make targeted scene edits without memorizing the full document schema. Diagnostic helpers such as getTimelineBounds(), repairDocumentForAgent(), sampleAnimationForAgent(), inspectSceneForAgent(), and createLoopingStateMachine() help agents detect timeline mistakes, auto-extend too-short timeline durations, prove that properties change over sampled frames, catch tiny/off-canvas/low-contrast scenes, and wire a generated timeline into live playback.

Diagram polish for agents

Generated diagrams should be checked with the deterministic polish APIs before handing them to a user. evaluateSceneForAgent() includes report.polish, and the same analysis is available directly as analyzePolish(doc). The report returns category scores plus diagnostics for layout, hierarchy, readability, contrast, graph readability, explanatory structure, and motion.

import {
  analyzePolish,
  applyNudge,
  createBadgePreset,
  createBoundaryPreset,
  createCalloutCardPreset,
  createComparisonTablePreset,
  createConnectorPreset,
  createDecisionNodePreset,
  createQueueStackPreset,
  createStepCardPreset,
  createTextBlockPreset,
  createTimelineRoadmapPreset,
  inspectPolishHeuristics,
  suggestDocumentNudges,
} from '@elucim/dsl';

const polish = analyzePolish(doc);
const heuristics = inspectPolishHeuristics(doc);
const nudges = suggestDocumentNudges(doc);
const safe = nudges.filter(nudge => nudge.confidence === 'safe');
const polished = safe.reduce((current, nudge) => applyNudge(current, nudge).document, doc);

const calloutElements = createCalloutCardPreset({
  id: 'key-insight',
  x: 80,
  y: 420,
  title: 'Key insight',
  body: 'A semantic, token-based callout gives agents a polished starting point.',
});

const stepElements = createStepCardPreset({
  id: 'draft',
  x: 80,
  y: 120,
  title: 'Draft',
  body: 'Cards, text blocks, and connectors are normal grouped elements.',
  index: 1,
});

const connectorElements = createConnectorPreset({
  id: 'draft-to-review',
  from: 'draft',
  to: 'review',
  fromBounds: { id: 'draft', x: 80, y: 120, width: 300, height: 132 },
  toBounds: { id: 'review', x: 440, y: 120, width: 300, height: 132 },
  label: 'then',
});

const textBlockElements = createTextBlockPreset({
  id: 'takeaway',
  x: 80,
  y: 320,
  width: 360,
  text: 'Wrapped text emits editable text lines inside a group.',
});

const supportingElements = [
  ...createBadgePreset({ id: 'status', x: 80, y: 80, label: 'review' }),
  ...createBoundaryPreset({ id: 'system', x: 60, y: 110, width: 420, height: 260, label: 'System boundary' }),
  ...createDecisionNodePreset({ id: 'cache-hit', x: 560, y: 120, text: 'Cache hit?' }),
  ...createQueueStackPreset({ id: 'queue', x: 80, y: 420, items: [{ label: 'Request' }, { label: 'Render' }] }),
  ...createTimelineRoadmapPreset({ id: 'roadmap', x: 420, y: 420, milestones: [{ label: 'Draft' }, { label: 'Polish' }] }),
  ...createComparisonTablePreset({
    id: 'tradeoffs',
    x: 80,
    y: 560,
    columns: ['Agent', 'Human'],
    rows: [{ label: 'Best at', cells: ['Fast structure', 'Final taste'] }],
  }),
];

Semantic motion for agents

Use semantic motion helpers when an agent knows the intent of the animation but should not hand-author every keyframe. These helpers compile to ordinary Elucim timelines/state machines, so the result remains editable and renderer-independent.

import {
  createAutoStaggerTimeline,
  createReducedMotionDocument,
  createSemanticMotionTimeline,
  createStateSnapshotMotion,
  holdFinalFrame,
  lintMotion,
  planMotionBeats,
  previewBeatDiffs,
} from '@elucim/dsl';

const beats = planMotionBeats({ seconds: 12, fps: 30, beatCount: 4 });

const intro = createSemanticMotionTimeline(doc, {
  id: 'intro-flow',
  preset: 'revealFlow',
  group: 'steps',
  duration: beats[0].duration,
});

const handoff = createSemanticMotionTimeline(doc, {
  id: 'draft-to-review-motion',
  preset: 'handoff',
  from: 'draft',
  to: 'review',
  connectorId: 'draft-to-review',
});

const rankedReveal = createAutoStaggerTimeline(doc, {
  id: 'ranked-reveal',
  group: 'steps',
  orderBy: 'rank',
});

const lint = lintMotion({ ...doc, timelines: { intro, handoff, rankedReveal } });
const preview = previewBeatDiffs({ ...doc, timelines: { intro } }, { timelineId: 'intro-flow', beats });
const reduced = createReducedMotionDocument({ ...doc, timelines: { intro } }, { mode: 'minimal' });
const poster = holdFinalFrame({ ...doc, timelines: { intro } }, { timelineId: 'intro-flow' });

Supported semantic presets are revealFlow, emphasizeDecision, tracePath, loopOnce, handoff, drainQueue, and compareBeforeAfter. createStateSnapshotMotion() turns named visual states into timelines plus a state machine, while holdFinalFrame() creates static poster/final-state documents. lintMotion() checks blank first frames, too-fast transitions, simultaneous overload, hidden labels, flashing, excessive motion, and reduced-motion readiness. previewBeatDiffs() returns agent-readable beat summaries describing what appears, disappears, moves, or changes.

Agent guidance:

  • Prefer semantic roles and intent (role: 'title', role: 'callout', intent.importance: 'primary' | 'secondary' | 'supporting') so polish can preserve explanatory meaning.
  • Add explicit relationship intent when the layout matters: intent.target, intent.flowFrom, intent.flowTo, intent.relationship, intent.group, plus layout.rank and layout.locked.
  • Use composite helpers such as createStepCardPreset(), createTextBlockPreset(), createCardGridPreset(), createConnectorPreset(), createDecisionNodePreset(), createBoundaryPreset(), createBadgePreset(), createQueueStackPreset(), createTimelineRoadmapPreset(), createComparisonTablePreset(), createAutoLayoutGroupPreset(), and createProgressiveRevealGroupPreset() for designed-slide structure. They emit ordinary editable groups, primitives, and timelines, not a separate persisted layout format.
  • Prefer createConnectorPreset() for reading order and layout relationships. Its generated connector intent is consumed as a virtual ELK edge during semantic layout.
  • Use theme tokens such as $title, $surface, $primary, and $muted instead of one-off literal colors unless a specific color is necessary.
  • Run suggestDocumentNudges() after drafting. Apply safe nudges automatically; present review nudges, especially graph layout changes, for review.
  • Use inspectPolishHeuristics() when an agent needs the raw evidence behind the aggregate score: element bounds, intersections, off-canvas overflow, text sizing, literal colors, graph crossings/overlaps, connector continuations, and semantic relationships.
  • Present smooth-connector-continuations as a review nudge when straight line/arrow connectors should become editable Bezier curves with rounded caps and smoother directionality.
  • Prefer semantic motion helpers over raw keyframes for common beats: reveal a flow, emphasize a decision, trace a connector, hand off between steps, drain a queue, compare before/after, and create reduced-motion fallbacks.
  • For graph elements, provide stable node IDs and edges; the layered graph nudge rewrites node coordinates while keeping the graph editable.
  • Use getAgentOperationCatalog() when a host or CLI needs to pass the available authoring, validation, inspection, polish, and layout operations to an agent.

For broader explanatory scenes, agents can request an ELK-backed semantic layout review nudge. ELK is used as a layout solver only; the result is written back as ordinary editable element coordinates.

import { applyNudge, suggestSemanticLayoutNudges } from '@elucim/dsl';

const [layoutNudge] = await suggestSemanticLayoutNudges(doc);
const next = layoutNudge ? applyNudge(doc, layoutNudge).document : doc;

Semantic layout nudges are always review confidence because they may move multiple elements.

DslRendererRef

Imperative handle exposed via ref on <DslRenderer>:

  • getSvgElement() — Returns the underlying SVGSVGElement
  • seekToFrame(frame) — Jump to a specific frame
  • getTotalFrames() — Total frame count for the current playback surface
  • play() / pause() — Control playback
  • isPlaying() — Whether playback is active

Math expressions

compileExpression(expr: string) and compileVectorExpression(expr: string) compile safe math strings for function plots and vector fields. The evaluator supports arithmetic, common trig/log functions, constants (PI, E, TAU), and variables (x, or x/y for vector fields). It does not use arbitrary JavaScript evaluation.

Document Schema

Every public document uses this normalized structure:

interface ElucimDocument {
  $schema?: string;
  version: '2.0';
  scene: ElucimScene;
  elements: Record<string, ElucimElement>;
  timelines?: Record<string, ElucimTimeline>;
  stateMachines?: Record<string, ElucimStateMachine>;
  defaultStateMachine?: string;
  metadata?: ElucimMetadata;
}

scene

The scene defines the render surface and top-level element order.

| Field | Description | |-------|-------------| | type | Usually player for interactive playback or scene for host-controlled rendering | | width / height | Scene dimensions in pixels | | preset | Optional shorthand: card (640×360), slide (1280×720), or square (600×600) | | fps | Frames per second for timelines | | background | Background color or semantic token | | children | Ordered array of top-level element IDs |

Presentation and slide-deck composition is host-level React composition with @elucim/core components such as <Presentation> and <Slide>, not a DSL root shape.

elements

Elements are keyed by stable ID. Each element includes its id, type, optional layout/metadata, and a props object for render properties.

Supported primitives include circle, line, arrow, rect, polygon, bezierCurve, text, image, group, and barChart.

Supported math/data elements include axes, functionPlot, vector, vectorField, matrix, graph, and latex.

timelines

Timelines contain explicit property tracks and keyframes. Use timelines instead of wrapper animation nodes.

{
  "intro": {
    "id": "intro",
    "duration": 45,
    "tracks": [
      {
        "target": "title",
        "property": "opacity",
        "keyframes": [
          { "frame": 0, "value": 0 },
          { "frame": 45, "value": 1 }
        ]
      }
    ]
  }
}

Common animated properties include opacity, translate, scale, rotate, fill, and stroke.

stateMachines

State machines connect timelines into interactive flows. Use them for entry animation, click/key-driven transitions, reset behavior, or auto-advancing states.

{
  "main": {
    "id": "main",
    "entry": "intro",
    "states": {
      "intro": { "timeline": "intro" },
      "focus": { "timeline": "focus" }
    },
    "transitions": [
      { "id": "entry-start", "from": "entry", "to": "intro", "trigger": "onStart" },
      { "id": "intro-next", "from": "intro", "to": "focus", "exitTime": 1 }
    ]
  }
}

Semantic Color Tokens

Use $token syntax in color fields to create theme-adaptive visualizations. Tokens resolve to CSS custom properties at render time.

{
  "version": "2.0",
  "scene": { "type": "player", "preset": "card", "background": "$background", "children": ["label"] },
  "elements": {
    "label": {
      "id": "label",
      "type": "text",
      "props": { "type": "text", "x": 320, "y": 180, "content": "Hello", "fill": "$foreground", "textAnchor": "middle" }
    }
  }
}

Available tokens include $foreground, $background, $title, $subtitle, $muted, $primary, $secondary, $tertiary, $success, $warning, $error, $surface, $border, and $accent.

Pair tokens with colorScheme or theme:

<DslRenderer dsl={doc} colorScheme="auto" theme={{ accent: '#ff6600' }} />

For AI Agents

When instructing an LLM to create Elucim diagrams:

  1. Ask it to produce JSON matching the normalized ElucimDocument schema.
  2. Set version: "2.0" and include exactly one scene plus an ID-keyed elements object.
  3. Use stable, semantic element IDs (title, axis-x, curve-sine) so later edits can target specific objects.
  4. Put motion in timelines with explicit tracks/keyframes; do not generate wrapper animation nodes.
  5. Use stateMachines for entry, click/key, reset, and auto-advance behavior.
  6. Use math expression strings for function plots and vector fields.
  7. Use semantic color tokens ($accent, $foreground, $background) when the host theme should control colors.

Useful APIs for agent and host workflows include createDocument, applyAgentCommands, addRevealTimeline, createStateMachine, evaluateSceneForAgent, validateForAgent, summarizeDocument, diffDocuments, and suggestDocumentNudges from @elucim/dsl/agent, plus low-level preview helpers such as applyTimelineFrame and transitionStateMachine from @elucim/dsl.

Tips:

  • Use poster on <DslRenderer> to show a static preview frame before playback starts.
  • Use renderToSvgString(doc, frame) to generate SVG previews server-side for thumbnails and social cards.

Example prompt:

"Create an Elucim DSL JSON document with version: \"2.0\", a single player scene, stable element IDs for axes and two function plots, an intro timeline that draws the sine curve before the cosine curve, and a state machine that starts the intro on load."

Related

  • @elucim/core — React rendering engine and component APIs
  • @elucim/editor — Visual canvas editor for normalized Elucim documents
  • Elucim Docs — Full docs with live interactive examples

License

MIT