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

@strike48/picjs

v0.2.11

Published

A language for creating animated, constraint-based SVG diagrams

Readme

Picjs: A language for creating animated web graphics.

Marvel at the examples below, then wander over to the documentation and playground.

Hello World!

A simple flow chart with three boxes, connected by arrows

Palette.current = "shuksan"
box "Input" -> box "Process" fill ~b2 -> box "Output"

picjs supports themes, controlled by the Palette object. Here we select the shuksan theme which defines light and dark versions of eight foreground and eight background colors. The outer two boxes use the theme-default background, while the middle box uses the second background color, ~b2.

Simple Turtle Graphics

petals = 17

start_color = oklch(70%, .3, 0)

petal = (color) => {
  4.times(=> {
    Arc stroke color
    Arc ccw stroke color.spin(10)
    Arc stroke color.spin(20)
  })
}

petals.times(n => {
  Face 360/petals*n     
  petal(start_color.spin(n*30))
})

Image drawn using arcs and 17-fold rotational symmetry

The petal function draws a petal shape by repeating a set of three arcs four times. There's no need for positioning: by default shapes follow on from each other.

We then call the petal function 17 times, rotating the face of the turtle by 360/17 degrees each time, and spinning the (hue of the) color by 30 degrees each time.

Run Towers of Hanoi, animating each move:

Screenshot of the animation in action

NumDisks = 5
Box.pole.fill = ~brown.lighten(5%)  // ~brown is a named color
DiskColor = rgb(220,180,140)


// This is just a regular function, but we're using it to define a mixin

canHaveDisks = (aPole) => {
  disks = []
  aPole.push = (disk) => {
    disks.push(disk)
    // return the position of the bottom of the disk
    aPole.s - (0, disks.length * (disk.ht + 2))
  }
  aPole.pop = () => {
    disks.pop()
  }
}

// draw a pole with a base.

drawPole = (number) => {
  pole = Box 20x150 rad 4 .pole  at (100 + number*230, 300)
  Box .pole 160x20 rx 7.5 at pole.s - (0,10) // the base
  pole.number = number
  canHaveDisks(pole)
  pole
}

poles = [0..2].map(drawPole)

// create the disks and add them to pole #0
[NumDisks..1].each(d => {
  @ += 0.3
  disk =  Box ht 20 wid 40 + d*15 rx 10 ry 5 fill DiskColor.spin(d*40)
  disk.s = poles[0].push(disk)
})

@ += 0.3

moveDisk = (pFrom, pTo) => {
   distance = (pFrom.number - pTo.number).abs()  // will be 1 or 2
   disk = pFrom.pop()
   move disk.s      to pFrom.n - (0, 10) ease "cubicIn"
   then move disk.s to pTo.n - (0, 10)   ease "linear" take 0.3 + 0.3*distance
   then move disk.s to pTo.push(disk)    ease "cubicOut"
   @@
}

hanoi = (n, pFrom, pTo, pVia) => {
 if (n > 0)  {
   hanoi(n-1, pFrom, pVia, pTo)
   moveDisk(pFrom, pTo)
   hanoi(n-1, pVia, pTo, pFrom)
 }
}

hanoi(NumDisks, poles[0], poles[2], poles[1])

There's a lot going on here; so I wrote a separate breakdown.

Integration

Browser:

<script type="module">
  import { renderAll } from 'picjs'
  renderAll('.picjs')  // renders all elements with class "picjs"
</script>
<div class="picjs">box "Hello"</div>

Server-side (Node.js):

import { renderToStringAsync } from '@strike48/picjs'
const { svg, width, height } = await renderToStringAsync('box "Hello"')

CLI (for markdown files):

npx picjs process README.md   # renders ```picjs blocks to SVG
npx picjs watch README.md     # watch mode

The CLI preserves code blocks and caches rendered SVGs—unchanged blocks are skipped on reprocessing.

Features

  • Integrates a JavaScript-like language with the drawing and animation DSL

  • All values can be extended with attributes, allowing you to implement mixins and to tag shapes with extra information

  • Functions with closures

  • Timeline handling

  • Built-in types include boolean, color, font, function, list, number, position, range, and string.

  • Ranges allow interpolation (45% * [~red..~blue] is a color almost halfway between red and blue).

  • Shapes may be positioned absolutely or relative to each other. Relative positioning can be one-off, or can act as a constraint (if the target shape moves, the dependent shape follows it to maintain the constraint).

  • Shapes can be grouped together, and groups can be nested. A group becomes shape-like, and so can be positioned and animated like any other shape.

  • Shapes can be created and destroyed on the timeline

  • Attributes can be animated. Where possible, the animation will interpolate the start and end values. Where not possible, the animation will do a cross fade (WIP).

  • Comes with a browser-based environment to let you experiment and debug your code.

  • The skills/ directory contains basic skills and a separate animation skills file.

License

See LICENSE.md.