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

@micheeeeel/yinyangapp

v2.3.0

Published

A program to visualize human activity balance to reach Sustainable Development Objectives

Readme

@micheeeeel/yinyangapp

Visualisation « Sphères » (diagramme yin/yang + satellites) de l'équilibre des activités humaines vis-à-vis des Objectifs de Développement Durable.

Deux usages :

  • Page standalone (npm start, déployée sur GitHub Pages) : l'application complète (panneaux, pile d'actions, recherche…) bootstrapée par src/main.ts.
  • Librairie npm : une app hôte (ex. React) importe les fonctions de src/api.ts pour obtenir un diagramme.

Installation dans une app hôte

npm install @micheeeeel/yinyangapp d3

d3 (v7) est un peer dependency : il n'est pas embarqué dans le bundle, c'est l'app hôte qui le fournit — soit via son propre bundler (import "d3", résolu automatiquement), soit en global window.d3 si le bundle est chargé en <script> (voir webpack.config.jsexternals).

Importer le CSS une fois :

import "@micheeeeel/yinyangapp/dist/bundle.css";

API

import {
  createYinYangSvg,   // rendu hors écran → SVGSVGElement détaché (ou null)
  createYinYangSvgs,  // idem, en lot
  mountYinYang,       // diagramme vivant dans un conteneur de l'hôte → { controller, destroy }
  mapCauseToInternalModel,
  createRandomCause,  // Cause aléatoire (2–7 composantes, ODD distincts) — graine optionnelle
  getYasuniCause,     // le Yasuní complet (7 composantes, 20 ODD) au format Cause
  type Cause, type YinYangSvgConfig, type YinYangMountConfig,
} from "@micheeeeel/yinyangapp";

data accepte une Cause (format métier : composantes → ODD → facettes avec score/weight) ou un YinYangInternalModel déjà adapté.

Toutes ces fonctions touchent le DOM : à appeler côté client uniquement (useEffect en React, pas de SSR).

Exemple React

import { useEffect, useRef } from "react";
import { createYinYangSvg, type Cause } from "@micheeeeel/yinyangapp";

export function YinYang({ data, size = 400 }: { data: Cause; size?: number }) {
  const ref = useRef<HTMLDivElement>(null);
  useEffect(() => {
    const host = ref.current;
    if (!host) return;
    const svg = createYinYangSvg({ data, viewData: { minEdge: size } });
    if (svg) host.appendChild(svg);
    return () => { host.innerHTML = ""; };
  }, [data, size]);
  return <div ref={ref} />;
}

Pour un diagramme interactif, préférer mountYinYang({ data, container: ref.current }) et appeler destroy() dans le cleanup du useEffect.

Film léger pour markers de carte

createYinYangFilmSvg « cuit » un tour complet de l'étincelle périmètre (sans satellites, sans glow) en un SVG autonome sans JavaScript : rotation CSS de l'étincelle + flip-book de calques pré-rendus pour les bascules de composante. Coût runtime par instance ≈ un GIF ; prévu pour des dizaines de markers Mapbox.

import { createYinYangFilmSvg } from "@micheeeeel/yinyangapp";

const film = createYinYangFilmSvg({
  data,                    // Cause ou YinYangInternalModel
  size: 120,               // px
  rotationDurationMs: 6000,
  transitionFrames: 4,     // images par bascule (0 = switch immédiat)
});
new mapboxgl.Marker({ element: film }).setLngLat([lng, lat]).addTo(map);
  • highlightActiveSegment: false : bordure neutre unique (pas de mise en avant du segment de la composante courante), film un peu plus léger.
  • stripSpikes (défaut true) : les pointes (une <line> + un dégradé par découpe d'ODD, dans chaque calque) sont retirées — invisibles à la taille d'un marker et lourdes (Yasuní : 492 → 254 Ko, dégradés 484 → 25). Le film ne garde que l'essentiel : lobes yin/yang avec leurs arcs d'ODD, yeux, anneau des facettes. stripSpikes: false pour les conserver.
  • yinYangRotationMs (défaut : pas de rotation) : rotation des lobes yin/yang cuite en CSS pur — même cinématique que la rotation de la lib (orbite des deux lobes autour du centre, rotation propre de chacun, demi-disque entraîné), continue et uniforme, sans rampe ni retour à la position de base. Durée d'un tour en ms, surchargeable sans recuire par la variable CSS --yy-yinyang-rotation sur le <svg> (ex. film.style.setProperty("--yy-yinyang-rotation", "12s")), comme --yy-rotation pour l'étincelle. Coût : un groupe orbital animé + un groupe de rotation propre par lobe et par calque (film Yasuní ≈ +3 %). Pour rejouer la vitesse du diagramme lui-même, lire la constante exportée YINYANG_ROTATION_DURATION_MS plutôt que d'écrire un nombre en dur : elle suit animationConfig.rotationDurationMs.
  • Cuire une fois par lieu (≈ 50 ms à transitionFrames: 0, ≈ 150 ms à 4) et cloner : film.cloneNode(true).
  • Sur une page qui anime en continu (carte), préférer createYinYangFilmSvgAsync (même sortie, même config) : la cuisson rend la main au navigateur entre chaque calque capturé (pas ≤ 12 ms), donc pas de micro-gel. Options : { yieldFn, signal } (AbortSignal pour annuler proprement).
const film = await createYinYangFilmSvgAsync({ data, size: 100, transitionFrames: 0 });
  • Vitesse par instance sans recuire : clone.style.setProperty("--yy-rotation", "5200ms").
  • Ordres de grandeur (4 composantes, 120 px) : transitionFrames: 4 → ~170 Ko brut / 16 Ko gzip / 1 700 nœuds ; transitionFrames: 0 → ~40 Ko / 350 nœuds.
  • Survol / sélection : remplacer le film par mountYinYang (diagramme vivant), puis revenir au film.

Données : Yasuní complet ou Cause aléatoire

import { createRandomCause, getYasuniCause } from "@micheeeeel/yinyangapp";

const yasuni = getYasuniCause();                       // identique à la page standalone
const pin = createRandomCause({ seed: mission.id, id: mission.id }); // stable par graine

createRandomCause tire 2 à 7 composantes (orientations alternées), 1 à 3 ODD par composante — distincts dans toute la Cause, donc couleurs officielles jamais répétées — et 1 à 4 facettes par ODD (score ∈ [0.15, 0.85], poids 1–3). Toutes les plages sont surchargeables (components, oddsPerComponent, cuttingsPerOdd, score, weight). Même seed ⇒ même Cause ; sans graine, Math.random. Ne touche pas au DOM (utilisable en SSR).

Guide d'intégration détaillé (React + Mapbox, cache, survol) : docs/integration-yvy-frontend.md.

Développement

npm start        # dev server
npm test         # vitest
npm run build    # dist/bundle.js + dist/bundle.css + dist/types/
npm publish --access public