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

sakana-card

v1.0.0

Published

🐟 High-performance WhatsApp bot event card image generator by Sakanaa. Supports Welcome, Leave, Level Up, Rank, Promote, Demote, and Banned cards with dynamic avatars, smart typography, auto-color picking, and blazing fast rendering powered by @napi-rs/c

Readme


✨ Features at a Glance

| Feature | Description | |---------|-------------| | 🎨 8 Event Templates | Welcome, Leave, Level Up, Rank, Promote, Demote, Banned, Premium Tier | | πŸ–ΌοΈ Dynamic Avatar | Circle, Rounded Rectangle, Hexagon clipping with gradient borders | | πŸ“ Smart Auto-Shrink | Long usernames automatically resize β€” never overflow or clip | | 🎯 Auto-Color Picker | Extracts dominant color from avatar for accent theming | | 🌫️ Background Filters | Dark overlay + blur effects for readability | | 🎭 Overlay/Ornaments | Add PNG overlay layers for seasonal themes | | ⚑ In-Memory LRU Cache | Cached images & fonts β€” no redundant downloads | | πŸš€ Ultra Fast | ~50ms per card on average (native Skia engine) | | πŸ“¦ Zero Config | Works out of the box with sensible defaults | | πŸ”’ Edge-Case Safe | Dead URLs, empty names, emojis, huge numbers β€” all handled | | πŸ’Ž Premium Tiers | Free β†’ Standard β†’ Ultra β†’ Super Enterprise (Sultan mode!) | | 🎭 Dual Render Modes | avatar mode (with photo) or typography mode (text-only) |


πŸ“¦ Installation

npm install sakana-card

Requirement: Node.js >= 16. The @napi-rs/canvas dependency ships prebuilt native binaries for Linux, macOS, and Windows β€” no build tools needed.


πŸš€ Quick Start

Welcome Card

const { generateWelcome } = require('sakana-card');
const { writeFileSync } = require('fs');

async function main() {
  const buffer = await generateWelcome({
    username: 'Sakanaa',
    groupName: 'Developers Hub',
    message: 'Welcome to the team!',
    memberCount: 142,
    avatarShape: 'circle',
    accentColor: ['#43e97b', '#38f9d7'],
    // avatar: 'https://example.com/avatar.png', // URL, file path, or Buffer
  });

  writeFileSync('welcome.png', buffer);
  console.log('βœ… Welcome card generated!');
}

main();

Leave Card

const { generateLeave } = require('sakana-card');

const buffer = await generateLeave({
  username: 'GoodbyeFriend',
  groupName: 'Coding Masters',
  message: 'Left the group. We will miss you!',
  memberCount: 89,
  accentColor: ['#e74c3c', '#c0392b'],
});

Level Up Card

const { generateLevelUp } = require('sakana-card');

const buffer = await generateLevelUp({
  username: 'ProGamer',
  level: 50,
  previousLevel: 49,       // Shows "Level 49 β†’ Level 50"
  xp: 24500,
  requiredXp: 25000,
  accentColor: ['#ffd700', '#ff8c00'],
});

Premium Tier Card (NEW!)

const { generatePremium } = require('sakana-card');

// Super Enterprise β€” Sultan Mode (Avatar)
const seCard = await generatePremium({
  tier: 'super_enterprise',
  mode: 'avatar',
  username: 'Sakanaa',
  title: 'SUPER ENTERPRISE',
  description: 'Exclusive premium tier for elite members',
  avatar: 'https://example.com/avatar.png',
  avatarShape: 'circle',
});

// Ultra β€” Typography Mode (Text-Only, no avatar)
const ultraCard = await generatePremium({
  tier: 'ultra',
  mode: 'typography',
  username: 'UltraUser',
  title: 'ULTRA',
  description: 'Next level visual experience',
});

πŸ“– Full API Reference

Template Generators

All generators are async functions that return Promise<Buffer> β€” a PNG image buffer ready to send via bot or save to file.


generateWelcome(options)

Generates a modern welcome card for new members.

| Option | Type | Default | Description | |--------|------|---------|-------------| | username | string | 'User' | Display name (auto-shrinks if long) | | groupName | string | '' | Group name text | | avatar | string \| Buffer | placeholder | Avatar URL, file path, or Buffer | | background | string \| Buffer | gradient | Custom background image | | message | string | 'Welcome to the group!' | Custom welcome message | | memberCount | number | β€” | Member count badge (e.g. #142) | | avatarShape | 'circle' \| 'rounded' \| 'hexagon' | 'circle' | Avatar clipping shape | | accentColor | string \| string[] | auto-detected | Accent color or gradient array | | autoColor | boolean | true | Auto-detect accent from avatar | | overlay | string \| Buffer | β€” | PNG overlay for seasonal themes | | overlayOpacity | number | 0.3 | Overlay opacity (0-1) | | backgroundOpts | object | {} | { blur?: boolean, overlayOpacity?: number, blurRadius?: number } |


generateLeave(options)

Generates a farewell/goodbye card. Same options as generateWelcome.

| Key Differences | | |---|---| | Default message | 'has left the group. Goodbye!' | | Default accent | Red tones | | Visual | Dashed ring around avatar, muted palette |


generateLevelUp(options)

Generates a level-up celebration card with progress bar and XP display.

| Option | Type | Default | Description | |--------|------|---------|-------------| | username | string | 'User' | Display name | | level | number | 1 | Current level | | previousLevel | number | β€” | Previous level (shows 49 β†’ 50 transition) | | xp | number | 0 | Current XP amount | | requiredXp | number | 100 | XP needed for next level | | avatar | string \| Buffer | placeholder | Avatar image | | background | string \| Buffer | gradient | Background image | | avatarShape | AvatarShape | 'circle' | Avatar shape | | accentColor | string \| string[] | ['#ffd700', '#ff8c00'] | Gold accent colors | | autoColor | boolean | true | Auto-detect from avatar | | overlay | string \| Buffer | β€” | Overlay PNG | | backgroundOpts | object | {} | Background filter options |


generateRank(options)

Generates a compact rank/profile card showing level, XP, and ranking position.

| Option | Type | Default | Description | |--------|------|---------|-------------| | username | string | 'User' | Display name | | discriminator | string | '' | Tag like '#0001' | | level | number | 1 | Current level | | rank | number | 1 | Global rank position | | xp | number | 0 | Current XP | | requiredXp | number | 100 | XP for next level | | status | 'online' \| 'idle' \| 'dnd' \| 'offline' | β€” | Status indicator dot | | avatar | string \| Buffer | placeholder | Avatar image | | background | string \| Buffer | gradient | Background image | | accentColor | string \| string[] | auto | Accent colors |


generatePromote(options)

Generates a promotion notice card.

| Option | Type | Default | Description | |--------|------|---------|-------------| | username | string | 'User' | Display name | | groupName | string | '' | Group name | | newRole | string | 'Admin' | New role title | | avatarShape | AvatarShape | 'hexagon' | Default hexagon for promote |

Plus all common options: avatar, background, accentColor, autoColor, overlay, overlayOpacity, backgroundOpts.


generateDemote(options)

Generates a demotion notice card.

| Option | Type | Default | Description | |--------|------|---------|-------------| | username | string | 'User' | Display name | | groupName | string | '' | Group name | | previousRole | string | 'Admin' | Old role | | newRole | string | 'Member' | New role |

Plus all common options.


generateBanned(options)

Generates a ban notice card with warning stripes and a slash across the avatar.

| Option | Type | Default | Description | |--------|------|---------|-------------| | username | string | 'User' | Display name | | groupName | string | '' | Group name | | reason | string | '' | Ban reason (auto-shrinks) | | bannedBy | string | '' | Who issued the ban |

Plus all common options.


generatePremium(options) πŸ’Ž

Generates a premium-tier user card with luxury visual effects.

| Option | Type | Default | Description | |--------|------|---------|-------------| | tier | 'free' \| 'standard' \| 'ultra' \| 'super_enterprise' | 'super_enterprise' | Premium tier level | | mode | 'avatar' \| 'typography' | 'avatar' | Render with photo or text-only | | title | string | Auto from tier | Title text (e.g. 'SUPER ENTERPRISE') | | username | string | 'User' | User display name | | description | string | '' | Small faded description text | | avatar | string \| Buffer | placeholder | Avatar image (ignored in typography mode) | | background | string \| Buffer | gradient | Custom background image | | avatarShape | AvatarShape | 'circle' | Avatar clipping shape | | accentColor | string \| string[] | tier default | Override accent color(s) | | autoColor | boolean | true | Auto-detect accent from avatar | | overlay | string \| Buffer | β€” | Overlay PNG | | overlayOpacity | number | 0.3 | Overlay opacity | | backgroundOpts | object | {} | Background filter options |

Premium Tier Hierarchy

| Tier | Visual Style | Background | |------|-------------|------------| | free | Clean, minimal with accent bars | Simple gradient | | standard | Enhanced with richer gradients | Purple gradient | | ultra | Neon glow, gradient text, particles | Nebula spots + vignette | | super_enterprise | Sultan mode β€” Extreme neon, 3D emboss, multi-color particles, metallic text | Deep purple/pink with dramatic light effects |

Mode Comparison

| Mode | What's Rendered | Best For | |------|----------------|----------| | avatar | Avatar + Title + Username + Description | Profile cards, membership badges | | typography | Title + Username + Description (no avatar) | Announcements, tier displays |

Super Enterprise is the Sultan/Hardcore version of Ultra β€” same purple/pink/neon-blue palette but with dramatically thicker strokes, heavier glow, denser particles, and 3D emboss text effects.


🎨 Visual Customization Guide

Avatar Shapes

// Circle (default) β€” clean, universal
await generateWelcome({ username: 'User', avatarShape: 'circle' });

// Rounded Rectangle β€” modern, sleek
await generateWelcome({ username: 'User', avatarShape: 'rounded' });

// Hexagon β€” great for gaming bots
await generateWelcome({ username: 'User', avatarShape: 'hexagon' });

Color Theming

// Single solid accent color
await generateWelcome({ username: 'User', accentColor: '#e74c3c' });

// Gradient accent (applied to borders, labels, bars)
await generateWelcome({ username: 'User', accentColor: ['#667eea', '#764ba2'] });

// Auto-detect from avatar (enabled by default)
await generateWelcome({
  username: 'User',
  avatar: 'https://example.com/avatar.png',
  autoColor: true,
});

// Disable auto-color, use default palette
await generateWelcome({ username: 'User', autoColor: false });

Background Effects

await generateWelcome({
  username: 'User',
  background: 'https://example.com/bg.jpg',
  backgroundOpts: {
    blur: true,           // Enable background blur
    blurRadius: 12,       // Blur intensity
    overlayOpacity: 0.7,  // Darker overlay for better readability
  },
});

Overlay / Ornament Layer

await generateWelcome({
  username: 'User',
  overlay: './assets/cyberpunk-frame.png',
  overlayOpacity: 0.4,
});

Auto-Shrink Text

No configuration needed! If a username like "This_Is_An_Extremely_Long_Username_That_Should_Auto_Shrink_Properly" is provided, the text automatically shrinks to fit within the card boundaries. This works for all text elements across all templates.


πŸ”§ Cache Management

const { clearCache, getCacheStats } = require('sakana-card');

// Check cache performance
console.log(getCacheStats());
// Output:
// {
//   image: { size: 5, hits: 12, misses: 3, hitRate: '80.0%' },
//   font: { size: 0, hits: 0, misses: 0, hitRate: '0%' }
// }

// Clear all caches (useful for long-running bots to free memory)
clearCache();

πŸ› οΈ Advanced: Build Custom Cards

Use the exposed core engine to create completely custom card layouts:

const { engine, loadImageSafe, extractDominantColor } = require('sakana-card');

async function myCustomCard() {
  const { canvas, ctx } = engine.createCard(800, 400);

  // Gradient background
  engine.fillBackground(ctx, 800, 400, ['#1a1a2e', '#16213e'], 'diagonal');

  // Load and draw avatar
  const avatar = await loadImageSafe('https://example.com/avatar.png');
  engine.drawAvatar(ctx, avatar, 100, 200, 120, {
    shape: 'hexagon',
    borderWidth: 4,
    borderColor: ['#43e97b', '#38f9d7'],
  });

  // Styled text with gradient, shadow, and stroke
  engine.drawStyledText(ctx, 'Custom Card!', 400, 50, {
    font: 'bold 40px "DejaVu Sans", sans-serif',
    color: ['#43e97b', '#38f9d7'],
    align: 'center',
    shadow: { color: 'rgba(0,0,0,0.5)', blur: 8, offsetX: 2, offsetY: 2 },
    strokes: [{ color: '#000', width: 3 }],
  });

  // Progress bar
  engine.drawProgressBar(ctx, 200, 300, 400, 30, 0.75, {
    fillColor: ['#667eea', '#764ba2'],
  });

  return canvas.toBuffer('image/png');
}

Engine API

| Method | Description | |--------|-------------| | engine.createCard(w, h) | Create a canvas context | | engine.fillBackground(ctx, w, h, color, direction) | Fill with solid/gradient | | engine.drawBackgroundImage(ctx, w, h, img, opts) | Draw cover-fit background | | engine.drawAvatar(ctx, img, x, y, size, opts) | Draw clipped avatar | | engine.autoShrinkFont(ctx, text, max, min, maxWidth) | Auto-fit text | | engine.drawStyledText(ctx, text, x, y, opts) | Styled text rendering | | engine.drawProgressBar(ctx, x, y, w, h, progress, opts) | Gradient progress bar | | engine.drawStatusDot(ctx, x, y, r, color) | Status indicator | | engine.roundedRect(ctx, x, y, w, h, r) | Rounded rectangle path | | engine.drawOverlay(ctx, img, w, h, opts) | Overlay image layer | | engine.stripEmoji(text) | Remove emoji from text |


⚑ Performance

Benchmarked on a standard VPS (2 vCPU, 4GB RAM):

| Metric | Value | |--------|-------| | Average per card | ~50ms | | Batch of 10 (parallel) | ~560ms | | 7 card types (sequential) | ~340ms | | Memory per 20 cards | < 1 MB heap increase | | Cache hit rate | Up to 80%+ |


πŸ§ͺ Testing

# Run full test suite (86+ tests)
npm test

# Check generated output images
ls output/

Output images are saved to the output/ directory for visual inspection:

output/
β”œβ”€β”€ welcome-default.png
β”œβ”€β”€ welcome-full.png
β”œβ”€β”€ welcome-long-name.png
β”œβ”€β”€ welcome-emoji.png
β”œβ”€β”€ welcome-hexagon.png
β”œβ”€β”€ leave-default.png
β”œβ”€β”€ levelup-default.png
β”œβ”€β”€ rank-default.png
β”œβ”€β”€ rank-status-online.png
β”œβ”€β”€ promote-default.png
β”œβ”€β”€ demote-default.png
β”œβ”€β”€ banned-default.png
β”œβ”€β”€ banned-full.png
β”œβ”€β”€ premium-se-avatar.png
β”œβ”€β”€ premium-se-typography.png
β”œβ”€β”€ premium-ultra-avatar.png
β”œβ”€β”€ premium-ultra-typography.png
β”œβ”€β”€ premium-free-avatar.png
β”œβ”€β”€ premium-standard-typography.png
└── ... (and many more)

πŸ“ Project Structure

sakana-card/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ index.js           # Main entry point & exports
β”‚   β”œβ”€β”€ index.d.ts         # TypeScript type definitions
β”‚   β”œβ”€β”€ core/
β”‚   β”‚   └── engine.js      # Core canvas rendering engine
β”‚   β”œβ”€β”€ templates/
β”‚   β”‚   β”œβ”€β”€ welcome.js     # Welcome card
β”‚   β”‚   β”œβ”€β”€ leave.js       # Leave/goodbye card
β”‚   β”‚   β”œβ”€β”€ levelup.js     # Level up card (with progress bar)
β”‚   β”‚   β”œβ”€β”€ rank.js        # Rank/profile card
β”‚   β”‚   β”œβ”€β”€ promote.js     # Promotion card
β”‚   β”‚   β”œβ”€β”€ demote.js      # Demotion card
β”‚   β”‚   β”œβ”€β”€ banned.js      # Ban notice card
β”‚   β”‚   └── premium.js     # Premium tier card (4 tiers Γ— 2 modes)
β”‚   └── utils/
β”‚       β”œβ”€β”€ cache.js       # LRU in-memory cache
β”‚       β”œβ”€β”€ loader.js      # Image/resource loader with timeout
β”‚       └── color.js       # Color utilities & auto-picker
β”œβ”€β”€ test/
β”‚   └── run-all-tests.js   # Comprehensive test suite
β”œβ”€β”€ output/                # Generated test images
β”œβ”€β”€ .github/
β”‚   └── workflows/
β”‚       └── npm-publish.yml  # Auto-publish to npm on release
β”œβ”€β”€ package.json
β”œβ”€β”€ .gitignore
β”œβ”€β”€ .npmignore
β”œβ”€β”€ LICENSE
└── README.md

πŸ”„ WhatsApp Bot Integration Example

const { generateWelcome, generateLeave, generateLevelUp } = require('sakana-card');

// With Baileys (WhatsApp Web API)
async function onMemberJoin(sock, groupJid, participant) {
  const card = await generateWelcome({
    username: participant.name || participant.id.split('@')[0],
    groupName: groupMetadata.subject,
    avatar: await getProfilePicUrl(participant.id),
    memberCount: groupMetadata.participants.length,
  });

  await sock.sendMessage(groupJid, {
    image: card,
    caption: `Welcome @${participant.id.split('@')[0]}!`,
    mentions: [participant.id],
  });
}

async function onMemberLeave(sock, groupJid, participant) {
  const card = await generateLeave({
    username: participant.name || participant.id.split('@')[0],
    groupName: groupMetadata.subject,
    avatar: await getProfilePicUrl(participant.id),
    memberCount: groupMetadata.participants.length,
  });

  await sock.sendMessage(groupJid, { image: card });
}

async function onLevelUp(sock, chatJid, user, newLevel, prevLevel) {
  const card = await generateLevelUp({
    username: user.name,
    level: newLevel,
    previousLevel: prevLevel,
    xp: user.xp,
    requiredXp: user.requiredXp,
    avatar: user.avatarUrl,
  });

  await sock.sendMessage(chatJid, {
    image: card,
    caption: `πŸŽ‰ ${user.name} leveled up to Level ${newLevel}!`,
  });
}

πŸ”„ Discord Bot Integration Example

const { generateRank, generatePremium } = require('sakana-card');
const { AttachmentBuilder } = require('discord.js');

// Rank command
client.on('interactionCreate', async (interaction) => {
  if (interaction.commandName === 'rank') {
    const user = interaction.user;
    const userData = await getUserData(user.id);

    const card = await generateRank({
      username: user.displayName,
      discriminator: `#${user.discriminator}`,
      level: userData.level,
      rank: userData.rank,
      xp: userData.xp,
      requiredXp: userData.requiredXp,
      avatar: user.displayAvatarURL({ format: 'png', size: 256 }),
      status: interaction.member?.presence?.status || 'offline',
    });

    const attachment = new AttachmentBuilder(card, { name: 'rank.png' });
    await interaction.reply({ files: [attachment] });
  }
});

πŸ“„ License

MIT License β€” Created with 🐟 by Sakanaa