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

visual-toolkit

v2.2.0

Published

Visual effects library encoding premium deep-sea aesthetics

Readme

Visual Toolkit

A library encoding premium visual aesthetics with theme support. Extracted from working code that "feels right."

Built-in Theme: Deep-sea marine biology (themes.deepSea)
Extensible: Register custom themes for jungle, space, or any aesthetic.

Installation

With a build system (TypeScript/ES modules)

npm install visual-toolkit
# or copy the src/ folder into your project

Without a build system (plain JS)

Include the browser bundle directly:

<script src="dist/visual-toolkit.min.js"></script>
<script>
  // Everything is available on window.VisualToolkit
  const { drawROV, drawJellyfish, timing, deepSea } = VisualToolkit;
</script>

Architecture: Core + Themes

The library is organized into core API (theme-agnostic) and themes (presets):

// Core API - works for any theme
import { timing, drift, material3D } from 'visual-toolkit';

// Theme presets - deep-sea specific
import { themes } from 'visual-toolkit';
themes.deepSea.colors.background
themes.deepSea.particles.marineSnow(100)
themes.deepSea.recipes.drawROV(ctx, x, y)

Backward Compatibility: The old API still works:

import { deepSea, drawROV } from 'visual-toolkit'; // Still works!

See THEME_ARCHITECTURE.md for details.


Why This Exists

LLMs generate generic visuals. They default to:

  • Pure primaries (#ff0000, #00ff00)
  • 1-2 second animations
  • Flat colors
  • Symmetric shapes
  • Obvious effects

This toolkit encodes patterns that make visuals feel premium - not photorealistic, but crafted. Like a Pixar movie, not a stock image.


Core Principles

1. Slowness Creates Premium

Generic animations use 1-2 second durations. Deep-sea creatures move glacially.

import { timing } from 'visual-toolkit';

// DON'T: Math.sin(time * 0.1) — too fast, looks mechanical
// DO: Math.sin(time * timing.glacial) — barely perceptible, feels organic

// Timing multipliers (applied to frame counter ~16/frame):
timing.glacial  // 0.0008 - ROV rotation, leviathan approach
timing.verySlow // 0.001  - gentle drift
timing.slow     // 0.002  - light pulse, creature movement
timing.medium   // 0.003  - bio-pulse
timing.fast     // 0.005  - flee behavior

2. Flat Colors Look Cheap

Real materials have gradients suggesting 3D form. The key: 3-stop gradients with highlight → mid → shadow.

import { materials, material3D } from 'visual-toolkit';

// DON'T: ctx.fillStyle = '#ffff00'; — pure yellow looks fake
// DO: Use the ROV yellow palette
const grad = material3D(ctx, x, y, width, height, materials.rovYellow);
// Creates: #e8a832 (highlight) → #d4942a (mid) → #b87820 (shadow)

3. Depth Through Layers

The secret to premium visuals: offset shadows drawn BEFORE the main shape.

import { drawOffsetShadow, drawWithDepth, depthPresets } from 'visual-toolkit';

// Draw shadow first, then fill
drawOffsetShadow(ctx, () => ctx.fillRect(x, y, w, h), 4, 4);
ctx.fillRect(x, y, w, h);

// Or use the preset system
drawWithDepth(ctx, 'equipment', () => ctx.fillRect(x, y, w, h), gradient);

4. Organic Shapes Are Asymmetric

Creatures aren't perfectly round. Asymmetric border-radius creates life.

import { organicRadii, organicRadius } from 'visual-toolkit';

// DON'T: border-radius: 50%;
// DO:
element.style.borderRadius = organicRadii.blob;  // '60% 50% 45% 55%'
element.style.borderRadius = organicRadius();    // random organic values

5. Glow Has Layers

Single-layer glows look flat. Stack multiple shadows with different blur radii.

import { cssGlow, cssJellyfishGlow } from 'visual-toolkit';

// Multi-layer glow
element.style.boxShadow = cssGlow('rgba(100, 200, 255, 0.6)', 'medium');
// Creates: 0 0 20px rgba(..., 0.6), 0 0 40px rgba(..., 0.3)

// Jellyfish: outer glow + inner shadow for depth
element.style.boxShadow = cssJellyfishGlow();

Themes

The library supports multiple themes. The deep-sea theme is built-in and auto-registered.

Using Built-in Themes

import { themes } from 'visual-toolkit';

// Deep-sea theme (built-in)
themes.deepSea.colors.background
themes.deepSea.colors.bioluminescence
themes.deepSea.particles.marineSnow(100)
themes.deepSea.recipes.drawROV(ctx, x, y)
themes.deepSea.gradients.deepWaterBackground(ctx, height)

Registering Custom Themes

import { registerTheme } from 'visual-toolkit';

registerTheme('jungle', {
  colors: {
    canopy: { light: '#2d5016', mid: '#1a3009', dark: '#0f1a05' },
  },
  particles: {
    rain: (count) => createRainParticles(count),
  },
  recipes: {
    drawTree: (ctx, x, y) => { /* ... */ },
  },
});

// Now available
themes.jungle.colors.canopy
themes.jungle.particles.rain(50)

See THEME_ARCHITECTURE.md for complete theme system documentation.


Color Palettes

Deep Sea Theme

Not "dark blue" - specific colors that feel like depth:

import { deepSea } from 'visual-toolkit';

// Background layers (darkest at bottom)
deepSea.background.surface  // '#020810'
deepSea.background.mid      // '#051018'
deepSea.background.deep     // '#020a12'
deepSea.background.abyss    // '#010508'

Bioluminescence

Cyan-ish hues (180-220°) with proper glow falloff:

import { bioluminescence, bioGlow, randomBioHue } from 'visual-toolkit';

// Preset colors
bioluminescence.cyan.core   // 'rgba(100, 200, 255, 0.9)'
bioluminescence.warm.core   // 'rgba(255, 100, 80, 0.8)'

// Generate creature colors
const hue = randomBioHue();  // 180-220
const color = bioGlow(hue, 70, 60, 0.6);  // hsla string

Materials

Real equipment colors, not primaries:

import { materials } from 'visual-toolkit';

// ROV body (not #ffff00)
materials.rovYellow.highlight  // '#e8a832'
materials.rovYellow.mid        // '#d4942a'
materials.rovYellow.shadow     // '#b87820'

// Metal (not #000000)
materials.metal.light   // '#666666'
materials.metal.dark    // '#333333'

Motion

Drift & Bob

Organic movement combines multiple frequencies:

import { drift, bob, pulse } from 'visual-toolkit';

function animate(time) {
  // ROV-style floating
  const movement = drift(time);
  rov.x += movement.x;
  rov.y += movement.y;
  rov.rotation = movement.rotation;
  
  // Jellyfish bob
  jellyfish.y += bob(time);
  
  // Glow pulse
  const scale = pulse(time, timing.medium, 0.8, 1.2);
}

Tendril Physics

Follow-the-leader with organic waviness:

import { createTendril, updateTendril } from 'visual-toolkit';

// Create
const tendril = createTendril(0, screenHeight / 2, 12);

// Each frame
updateTendril(tendril, mouseX, mouseY, time, recoilFactor);

// Draw
ctx.beginPath();
ctx.moveTo(tendril.startX, tendril.startY);
tendril.segments.forEach(seg => ctx.lineTo(seg.x, seg.y));
ctx.stroke();

Seeker Behavior

Creatures that approach light, flee when it moves fast:

import { createSeeker, updateSeeker } from 'visual-toolkit';

const seekers = Array.from({ length: 40 }, () => 
  createSeeker(canvas.width, canvas.height)
);

function animate() {
  for (const seeker of seekers) {
    updateSeeker(seeker, lightX, lightY, mouseSpeed, width, height);
    // Seeker now has updated x, y, glow, hue
  }
}

Particles

Marine Snow

import { createMarineSnow, updateMarineSnow, drawMarineSnow } from 'visual-toolkit';

const particles = createMarineSnow(80);

function animate() {
  updateMarineSnow(particles, 'down');  // or 'up' for descending ROV
  drawMarineSnow(ctx, particles, width, height);
}

CSS Version

import { htmlMarineSnow, cssMarineSnowStyles } from 'visual-toolkit';

// Add to your page
document.head.innerHTML += `<style>${cssMarineSnowStyles}</style>`;
container.innerHTML += htmlMarineSnow(20);

Shapes

Mechanical Details

import { drawPanelLines, drawGrille, drawLight, drawLens } from 'visual-toolkit';

// ROV panel lines
drawPanelLines(ctx, x, y, width, height, [0.25, 0.75]);

// Thruster grilles
drawGrille(ctx, x, y, width, height, 4);

// Lights with glow
drawLight(ctx, x, y, 8, true, time);

// Camera lens with glint
drawLens(ctx, x, y, 12, 8);

Creature Parts

import { drawTrackingEye, drawTentacle } from 'visual-toolkit';

// Eye that follows player, reacts to light
drawTrackingEye(ctx, eyeX, eyeY, 8, playerX, playerY, illumination);

// Tentacle from segment array
drawTentacle(ctx, tendril.segments, tendril.thickness);

CSS Helpers

Gradients

import { cssDeepWater, cssBioGlow, cssCreatureBody } from 'visual-toolkit';

element.style.background = cssDeepWater();
glow.style.background = cssBioGlow('cyan');
creature.style.background = cssCreatureBody(0.8);

Timing

import { cssTimings, staggerDelay } from 'visual-toolkit';

element.style.animationDuration = cssTimings.drift;  // '8s'
element.style.animationDelay = staggerDelay(index);  // '0.9s' for index 3

Full Example: Simple Deep Sea Scene

import {
  deepSea,
  timing,
  deepWaterBackground,
  createMarineSnow,
  updateMarineSnow,
  drawMarineSnow,
  createBioParticles,
  drawBioParticles,
  vignette,
} from 'visual-toolkit';

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');

const snow = createMarineSnow(60);
const bio = createBioParticles(12);
let time = 0;

function render() {
  // Background
  ctx.fillStyle = deepWaterBackground(ctx, canvas.height);
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  
  // Particles
  updateMarineSnow(snow, 'down');
  drawMarineSnow(ctx, snow, canvas.width, canvas.height);
  drawBioParticles(ctx, bio, canvas.width, canvas.height, time);
  
  // Vignette
  ctx.fillStyle = vignette(ctx, canvas.width/2, canvas.height/2, 
    canvas.height * 0.3, canvas.height * 0.8);
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  
  time += 16;
  requestAnimationFrame(render);
}

render();

Recipes (Complete Objects)

High-level functions that draw complete objects, combining all the primitives.

ROV (Underwater Vehicle)

import { drawROV } from 'visual-toolkit';

function render() {
  drawROV(ctx, canvas.width / 2, canvas.height * 0.4, 1.2, {
    lightsOn: true,
    showTether: true,
    showArm: true,
    time: frameCount,
    rotation: Math.sin(frameCount * 0.0008) * 0.03,
  });
}

Light Cone

import { drawLightCone } from 'visual-toolkit';

// Draw cone of light from ROV pointing down
drawLightCone(ctx, rovX, rovY + 50, Math.PI / 2, 300, {
  spread: 0.6,        // radians
  startOpacity: 0.2,
  color: { r: 255, g: 250, b: 230 },
});

Jellyfish

import { drawJellyfish } from 'visual-toolkit';

drawJellyfish(ctx, 200, 150, {
  bellWidth: 60,
  tentacleCount: 4,
  time: frameCount,
  glowIntensity: 0.5,
});

Specimen (Mysterious Creature)

import { drawSpecimen } from 'visual-toolkit';

// Reveals more details when illuminated
drawSpecimen(ctx, 300, 200, {
  width: 70,
  time: frameCount,
  illumination: 0.5,  // 0-1, how much light is on it
  lookAt: { x: mouseX, y: mouseY },  // eye tracking
});

Browser Bundle (No Build System)

For projects without TypeScript/bundlers, use the pre-built browser bundle:

<script src="path/to/visual-toolkit.min.js"></script>
<script>
  const canvas = document.getElementById('canvas');
  const ctx = canvas.getContext('2d');
  let time = 0;

  function render() {
    // Clear with deep water gradient
    ctx.fillStyle = VisualToolkit.deepWaterBackground(ctx, canvas.height);
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // Draw ROV
    VisualToolkit.drawROV(ctx, canvas.width / 2, 150, 1, {
      lightsOn: true,
      time: time,
    });

    // Draw light beams
    VisualToolkit.drawLightCone(ctx, canvas.width / 2, 200, Math.PI / 2, 250);

    // Draw creatures
    VisualToolkit.drawJellyfish(ctx, 100, 300, { time: time });
    VisualToolkit.drawSpecimen(ctx, 400, 280, { time: time, illumination: 0.3 });

    // Vignette
    ctx.fillStyle = VisualToolkit.vignette(ctx, canvas.width/2, canvas.height/2, 
      canvas.height * 0.3, canvas.height * 0.8);
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    time += 16;
    requestAnimationFrame(render);
  }

  render();
</script>

Lighting Helpers

Calculate Illumination

import { calculateIllumination } from 'visual-toolkit';

// How lit is this creature by the player's light?
const illumination = calculateIllumination(mouseX, mouseY, creatureX, creatureY, 250);

if (illumination > 0.4) {
  // Reveal hidden details
}

Uses squared falloff for natural light decay.

Draw Player Light

import { drawPlayerLight } from 'visual-toolkit';

// Complete multi-layer light effect in one call
drawPlayerLight(ctx, mouseX, mouseY, 120, {
  intensity: 1,
  pulseTime: frameCount,  // optional subtle pulse
});

Organic Surface Texture

import { drawOrganicTexture } from 'visual-toolkit';

// Fill background first
ctx.fillStyle = '#0a0a12';
ctx.fillRect(0, 0, width, height);

// Add living texture
drawOrganicTexture(ctx, width, height, {
  density: 25,
  color: { r: 20, g: 30, b: 40 },
  intensity: 0.15,
  time: frameCount,  // optional slow movement
});

Caustics

import { drawCaustics } from 'visual-toolkit';

// Light filtering through water at top of scene
drawCaustics(ctx, width, height, frameCount, {
  count: 5,
  heightRatio: 0.4,
  intensity: 0.03,
});

Eye System

For scenes with multiple reactive eyes that track, blink, and dilate.

Create and Update Eyes

import { createEye, updateEye, drawEye } from 'visual-toolkit';

// Create eyes
const eyes = [
  createEye({ x: 100, y: 150, size: 8 }),
  createEye({ x: 400, y: 200, size: 6, hue: 30 }),  // orange eye
  createEye({ x: 250, y: 300, size: 10, awarenessRadius: 300 }),
];

function render() {
  // Update all eyes (handles blinking, tracking, dilation)
  for (const eye of eyes) {
    updateEye(eye, mouseX, mouseY, 16);
  }
  
  // Draw all eyes
  for (const eye of eyes) {
    drawEye(ctx, eye);
  }
}

Batch Operations

import { createEyes, updateEyes, drawEyes } from 'visual-toolkit';

const eyes = createEyes([
  { x: 100, y: 150, size: 8 },
  { x: 400, y: 200, size: 6 },
]);

function render() {
  updateEyes(eyes, mouseX, mouseY, 16);
  drawEyes(ctx, eyes);
}

Eye Properties

Eyes automatically:

  • Track the light source (pupil follows)
  • Contract pupils in bright light (slit at illumination > 0.4)
  • Dilate pupils in darkness
  • Blink at random intervals
  • Glow brighter when illuminated

Note: Tendrils Removed

Tendril support (createTendril, updateTendril, drawTendril, etc.) was removed from the library due to fundamental challenges with creating convincing appendage behavior:

  • Disembodiment problem: Even with reach limits and base mass rendering, tendrils often looked like floating worms rather than attached appendages
  • Synchronization issues: Multiple tendrils reacting to the same light source moved identically, lacking independent behavior
  • Visual quality: Despite bezier curves and multi-layer rendering, results often looked artificial compared to simpler approaches

Alternative approach: For creature appendages, consider custom creature shapes (like jellyfish bells with tentacles) rather than generic tendril physics.


Seeker Swarms

For scenes with many bioluminescent creatures.

Create with Center Bias

import { createSeekerSwarm, updateSeekerSwarm, drawSeekerSwarm } from 'visual-toolkit';

// Center-biased swarm that won't cluster at edges
const seekers = createSeekerSwarm(40, canvas.width, canvas.height, {
  spawnBias: 'center',  // 'uniform' | 'center' | 'edges'
  sizeRange: [2, 8],
  speedRange: [1, 4],
  hueRange: [180, 220],
});

Update with Center Drift

// centerDrift prevents edge clustering when wandering
updateSeekerSwarm(seekers, mouseX, mouseY, mouseSpeed, width, height, 0.02);

Draw with Proper Layering

// Dim seekers render behind, bright ones in front
drawSeekerSwarm(ctx, seekers, {
  lightX: mouseX,
  lightY: mouseY,
  lightRadius: 200,
  time: frameCount,
});

Reusable Bioluminescent Glow

import { drawBiolumGlow } from 'visual-toolkit';

// For any glowing point - creatures, particles, etc.
drawBiolumGlow(ctx, x, y, size, hue, intensity, time);

Organic Surfaces

Draw surfaces that read as SOLID (flesh, rock, membrane) - not void or holes.

The Problem

Radial gradients look like holes/sockets. That's bad when you're drawing a wall with eyes on it.

The Solution

drawOrganicSurface() uses directional patterns (veins, grain, ridges) instead of radial gradients.

import { drawOrganicSurface, drawEyes } from 'visual-toolkit';

// Draw the wall FIRST
drawOrganicSurface(ctx, canvas.width, canvas.height, {
  preset: 'visible',     // 'visible' | 'subtle' | 'dramatic'
  type: 'fleshy',        // 'fleshy' | 'abyssal' | 'rocky' | 'membranous'
  lightX: mouseX,        // Surface responds to light
  lightY: mouseY,
  lightRadius: 200,
  time: frameCount,      // Optional subtle animation
});

// THEN draw eyes on top
drawEyes(ctx, eyes);

Visibility Presets

| Preset | Use Case | |--------|----------| | visible | Main features (The Wall) - clear, high-contrast | | subtle | Background texture - muted, ambient depth | | dramatic | Horror scenes - high vein density, strong light response |

Performance: Caching the Base Layer

The per-pixel noise is expensive. For animated scenes, cache to offscreen canvas:

// Cache once
const offscreen = document.createElement('canvas');
offscreen.width = width; offscreen.height = height;
drawOrganicSurface(offscreen.getContext('2d'), width, height, { 
  preset: 'visible',
  type: 'fleshy',
  time: 0,  // static base
});

// In render loop - reuse cached base
ctx.drawImage(offscreen, 0, 0);

// Draw dynamic elements on top
drawFleshLightResponse(ctx, ...);  // light
drawEyes(ctx, eyes);               // eyes

Surface Types

| Type | Description | |------|-------------| | fleshy | Whale/squid flesh, reddish veins, subsurface scattering | | abyssal | Deep-sea creature, blue-gray with bioluminescent hints | | rocky | Cave wall, gray-blue tones | | membranous | Thin tissue, purple tones, visible veins |

Additional Surface Elements

import { drawBarnacles, drawScarring } from 'visual-toolkit';

// Add barnacle clusters (raised bumps, NOT holes)
drawBarnacles(ctx, 200, 150, 40, { count: 10 });

// Add scarring (linear marks that read as scratches)
drawScarring(ctx, 100, 200, 80, { angle: 0.3, width: 4 });

Surface Light Response

When you provide lightX and lightY, the surface:

  • Shows highlights where light hits
  • Has subsurface scattering for fleshy/membranous types (warm glow)
  • Reveals texture without creating "hole" artifacts

Performance Considerations

Shadow Blur

ctx.shadowBlur is expensive. Recommended limits:

  • 6-12px for most shadows
  • Avoid blur > 20px unless necessary
  • Consider using offset shadows without blur for better performance

Segment Counts

Fewer segments perform better:

  • 8-12 segments for organic shapes (tendrils, creatures)
  • Avoid 20+ segments unless detail is critical
  • Each segment adds draw calls

Light Layers

Multiple light layers can be costly:

  • 1-2 layers recommended for most scenes
  • Each additional layer adds full-screen gradient operations
  • Consider caching static light layers to offscreen canvas

Organic Surface Caching

The per-pixel noise in drawOrganicSurface is expensive. For animated scenes:

// Cache once
const offscreen = document.createElement('canvas');
offscreen.width = width; offscreen.height = height;
drawOrganicSurface(offscreen.getContext('2d'), width, height, { 
  preset: 'visible',
  type: 'fleshy',
  time: 0,  // static base
});

// In render loop - reuse cached base
ctx.drawImage(offscreen, 0, 0);
// Then draw dynamic elements (light response, eyes, seekers)

General Tips

  • Batch operations: Draw similar elements together (e.g., all seekers, then all particles)
  • Cull off-screen: Skip rendering elements outside viewport
  • Reduce gradients: Prefer solid colors where possible
  • Limit particles: 50-100 particles perform well; 500+ may cause issues

License

ISC