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

@pepperui/illustrations

v0.10.0

Published

The Mobupps design system's illustration library (DL-26): isometric scenes built from one library of objects, each placed on a page by name with <pepper-illustration scene="…">. A small element paints the scene's still (drawn at build time in every palett

Readme

@pepperui/illustrations

The illustration library (DL-26, milestone M3.7): isometric 3D objects built once from shared building blocks and composed into scenes. Every scene is one definition drawn two ways:

  • live: three.js, for the web. A slow idle drift, the pointer turns it, and a signal runs along its arrows;
  • still: an SVG for pages, slides and PDFs, with text kept as text and Poppins inside the file, plus a PNG at 2×.

A page places a scene by its name, the way it places an icon: one element, one attribute. The library is a growing bank — a new scene is its description dropped into scenes/, built, and released as a minor.

Install

npm install @pepperui/illustrations

Serve the package's dist/ folder as it is — the element finds its engine and its stills beside its own script — and write the scene where it goes:

<pepper-illustration scene="cross-screen" palette="red" label="One user across three screens"></pepper-illustration>
<script src="node_modules/@pepperui/illustrations/dist/pepper-illustration.js"></script>

A page that serves the files from elsewhere says where on the script tag, data-base="/assets/illustrations/". dist/catalogue.json lists every scene and object, each one's own palette and direction, the files, and the attributes below.

The element

| Attribute | What it takes | |---|---| | scene | a library scene, by its name — cross-screen, cross-screen-close (the MAFO page's), intelligence, flow, infra, release, screens, and the Travel page's eight: demand-board, one-event, city-map, booking-window, deep-link, creative-rotation, source-pricing, geo-holdout; and the solution pages' twelve (3.7.30): igaming-1-deposit-stream, igaming-2-holdout-gap, igaming-3-deposit-cost, gaming-5-organic-baseline, gaming-7-reddit-partner, ecommerce-1-purchase-ledger, ecommerce-5-supply-sources, ecommerce-6-holdout-lift, finance-1-value-event-ledger, finance-5-overlap-suppressed, finance-7-channels-one-price, finance-alt-5-incrementality-read | | object | one library object alone, by its name, in place of a scene — it takes palette and direction as a scene does | | palette | red · yellow · blue · three — one system colour or the three; the scene's own without it, the three for an object alone | | direction | left · right — which way the objects face: right when the picture sits to the left of its words. The camera stands at the other corner; nothing is mirrored, so the words still read. The scene's own without it, left for an object alone | | still | the still, drawn at build time in this palette and direction — no engine, no motion | | idle | on · off — the slow drift on its own; on without it | | interactive | on · off — the pointer turns the picture; on without it | | label | what the picture shows, for a screen reader — the element is an image | | loading | lazy · eager — lazy without it: the engine starts when the picture comes into view | | src | the address of a description (JSON), for a scene that is not in the library |

Width is the element's, and its height follows the picture's proportions. Reduced motion gives the still. data-state says where the element is: poster, live, paused or still.

What a page pays

A live picture is a WebGL render loop, and a browser allows about sixteen WebGL contexts before it drops one. So:

  • The still is painted first. Every scene is drawn at build time in every palette and facing both ways (dist/stills/<scene>--<palette>--<direction>.svg), and so is every object alone (dist/objects/<object>--<palette>--<direction>.svg); the element shows that file at once — the page's first paint never waits for the engine. A live picture takes over from it without a jump: the still is scaled to the live picture's first frame. A still's outline never draws thinner than one device pixel, as the live picture's never does: each file draws a one-pixel floor, and on a denser screen the element asks for the file's #dpr2 (or #dpr3), where the floor is one device pixel. A page placing a still itself on a Retina screen adds #dpr2 to the address the same way.
  • The engine loads only when a picture has to move. pepper-illustration.js is the element alone; the engine — three.js, the library and every scene, pepper-illustration-engine.js — is fetched after the page has loaded, the first time a live picture comes into view. A page of stills downloads no engine at all.
  • Lazy, and paused. A live picture starts when it comes near the viewport and pauses when it leaves; with no live picture in view, no frame is drawn.
  • One renderer. Every live picture on a page shares the engine's one WebGL renderer, so a page opens one context however many pictures it holds.

The page pattern: one live scene per page, the rest stills — the hero moves, the others are the same scenes with still. At most one live scene in view at a time. The stills are renders of the same descriptions, so a mixed page matches exactly, in the same palette and direction.

The files

| In dist/ | What it is | |---|---| | pepper-illustration.js | the element — a small classic script; it defines <pepper-illustration> and paints stills | | pepper-illustration-engine.js | the engine: three.js 0.184.0, the library, every scene and its motion, the design system's Poppins | | stills/<scene>--<palette>--<direction>.svg | every scene in every palette, facing both ways, Poppins inside, its outline never under a device pixel (#dpr2 / #dpr3 on a dense screen) | | scenes/<scene>.svg · <scene>@2x.png | each scene in its own palette, as an SVG and a PNG at 2× — for slides and PDFs | | objects/<object>.svg · <object>@2x.png | each object alone, in the three, facing left | | objects/<object>--<palette>--<direction>.svg | each object alone in every palette, facing both ways — what the element paints for object | | descriptions/<scene>.json | each scene's description — a scene is data | | catalogue.json | what the package holds: the scenes, the objects, the element's attributes | | manifest.json | every file's sha1, and what it was made from |

Adding a scene

A new scene is a description — plain JSON on the library's objects (below, A scene is data); it draws nothing.

  1. Author it: in Claude Design with Pepper.Scene (the system's README there carries the format), or by hand. An object the library lacks is added to the library first, never drawn on a page.
  2. Try it: python3 scripts/illustrations.py --scene FILE draws it live and still and runs its checks.
  3. Drop it into scenes/ as scenes/<name>.json, once it is approved. The file's name is the scene's name.
  4. Build: npm run illustrations. The run draws it in every palette and direction, runs every check, adds its still to the baseline (a new picture moves no approved one), and the element and dist/catalogue.json list it.
  5. Release a minor: python3 scripts/release.py (a scene added is a name added — RELEASING.md §2).
  6. On the page: install the new version and write <pepper-illustration scene="<name>">.

Versions

The package follows RELEASING.md like the others. Its public API is dist/catalogue.json: the scene and object names and the element's attributes. A scene added is a minor; a scene redrawn is a minor listed under Changed; a name removed is a major. It stays under 1.0 while the catalogue grows — the first releases are 0.x.

For the library itself

What follows is how the library is built and checked, in this repository. The rules an illustration follows are decisions D116–D123 in docs/03_DECISIONS.md: the style, one colour or the three, line widths as tokens, the screen colour, rounded ends, arrows, and camera and motion; how the package loads on a page is D164. The whole brief, with the loop that makes a new illustration, is Design_system/illustration-test/CHAPTER.md.

What is here

| Path | What it is | |---|---| | library/tokens.js | GENERATED by npm run build from tokens/*.json: the illustration/… tokens (D124) and the primitives beside them — edit tokens/, never this file | | library/style.js | names those token values for the objects: colours, outline, connector, arrow, badge, the screen fills; plus the lights and the one camera direction, which are rendering constants | | library/content.js | what is drawn on surfaces (screens, labels, badges, icon tiles, cards), as drawing steps replayed to a canvas (live) or written as SVG (still) | | library/batch2.js | batch 2's eight build functions, word for word as Claude Design wrote them on its canvas — a module, not a string run through new Function, so the library also runs where a content security policy forbids eval (3.7.9). The kit hands them the starter's names as one scope, helpers(colour) | | library/objects.js | the objects, the shared stage and camera, the outlines (thick lines, sized before every render — below), and the helpers that keep arrows clear of their targets and outlines on round shapes | | library/scene.js · library/catalogue.json | a scene is data (3.7.12, D134 — below): compose(description) builds any description on the library's stage; problems(description) says what is wrong with one in plain words; the catalogue is what a description may say about each object | | library/checks.js | the two arrow measures the runner takes — crossings and the tightest bend — read from a still in the browser, so whoever composes a scene is told at once | | library/icons.js | GENERATED by node build/illustration-icons.js (inside npm run build) from lucide-react, the design system's icon set: 106 icons, each as one path, beside the fourteen glyphs content.js writes out, which the approved pictures carry | | scenes/*.json | the scenes, each a description, named by its file — cross-screen, intelligence (the slide), flow, infra, release, screens, cross-screen-close, the Travel page's eight (D170) and the solution pages' twelve (D175). A new scene is one more file here | | scenes/index.js · scenes/meta.js | GENERATED by the runner from scenes/*.json and the baseline: every description by its name (the engine's), and each scene's size, palette and direction (the element's, before any engine) | | scenes/<name>.js · scenes/sheet.js | the first six scenes' thin modules, which hand their JSON on to the animated pages; sheet.js (one object alone) is still code | | component.js · engine.js · engine-global.js · library/motion.js | the element, the engine it loads, the engine's classic-script entry, and how a live picture moves (read by both) | | svg.js | the still: surfaces back to front under the same lighting, outlines split where hidden, connectors, flat arrows, icons as text-keeping decals | | render3d.html · export.html · view-svg.html | load a scene (?scene=, or any description by its address, ?src=) live, export it to SVG, or re-render a stored SVG | | *-live.js · *-live.html | the first six scenes' animated pages; the runner bundles each into out/*.bundle.js so a page opens straight from disk. scene-live.html?scene=<name> is any scene's, through dist/ | | vendor/ | three.js 0.184.0 and its line add-ons, vendored (imports point at this copy); the runner fails on any other revision | | baseline/ | the committed record: every scene's and object's SVG, and how far each still differs from its live version on each platform (parity/darwin.json; linux's is recorded from CI) |

Running it

npm run illustrations                       # = python3 scripts/illustrations.py: renders everything into out/, runs the checks, writes catalogue.html
python3 scripts/illustrations.py --bless    # after a change that is MEANT to move a picture: the run becomes the baseline
python3 scripts/illustrations.py --static   # no browser: the committed record whole and its arrows sound — npm test runs this
python3 scripts/illustrations.py --quick        # a library change NOT meant to move a picture: every scene still against the baseline, byte for byte, in seconds (D135)
python3 scripts/illustrations.py --stills       # …every scene and object in every palette and direction drawn again into dist/ (two minutes)
python3 scripts/illustrations.py --component    # …then bundle the element and its engine into dist/ again, without the full run
python3 scripts/illustrations.py --scene FILE   # any scene description: drawn live and still, its checks run, its files in out/composed/<name>/

Where the checks run (3.7.6). npm test runs the static half: every still the runner draws has its committed SVG and its recorded figure, nothing else is in baseline/, and the committed scenes' arrows cross no outline and bend no tighter than 16 px. So a picture with a broken arrow cannot be blessed and committed while npm test passes. The render half runs in the pipeline's Illustrations step, and locally with npm run illustrations.

Per platform. The SVGs are held byte for byte everywhere. How far a still differs from its live version is anti-aliasing, which depends on the platform, so each platform has its own figures in baseline/parity/. linux's are blessed from a CI run's out/parity-linux.json, after a look. Until then the step runs with --missing=warn, which reports that one check instead of failing it. A --bless on linux writes linux's figures only; the SVGs are blessed on the platform where the pictures are approved.

It needs Python Playwright with Chromium, fontTools and Pillow; esbuild comes from the repository's node_modules. out/ and catalogue.html are rebuilt on every run and are not committed. Open catalogue.html to see every scene and object, still first, with each scene's animated version one click away.

The checks, and exit 1 if one fails:

  • three: the vendored three.js is revision 184.
  • widths: the outline's width is the token's in both versions. One object is drawn again at twice the width (?outline=2 on the runner's two pages) and its outline, measured across an edge, must come out about twice as wide live and still. At today's width a live outline is one device pixel, which is also what WebGL's own lines were, so no other check can tell whether the live version follows the token.
  • baseline: every SVG is the committed one, byte for byte, or the same pieces in another order with an identical render. Hairlines vary slightly between renders, so the proof is the file, not a screenshot.
  • still: each still differs from its live version no more than the baseline recorded, plus 0.05 points. The difference is anti-aliasing on lines and text: 0.44–0.69 % of pixels for a scene, up to 1.52 % for an object. A missing or shifted piece makes it grow.
  • palette: every scene, and each scene drawn in another palette (scene 1 in red; the slide in yellow and in blue), shows exactly the colours it was built in. The check reads the still: every coloured value in it, however it is shaded, is sorted to the nearest of red, yellow and blue, and neutrals are left out — a value nearest blue needs a little more colour, because the ground light gives a neutral face a faint blue cast. Every object is also drawn in blue alone, and must show blue and nothing else.
  • arrows: no arrow crosses another object's outline, and the tightest bend is at least 16 px (D122).
  • files: every still's file, as dist/ carries it (Poppins inside), renders placed as an image as it does inline. Both renders are softened by 1 px first: the 0.5 px hairlines anti-alias a little differently each time, which counted 621 px on a scene, and the blur makes that 0. A word in another face still differs by 30 403 px, and at another weight by 17 099. The fonts inside each file are read to check they cover every character the file uses. This Mac has Poppins installed, so a missing font would fall back to it without a trace. The slide's file is byte for byte the approved slide file (pinned by its sha1: three-layers-of-intelligence-v4, the slide with the library's one signal and round corners however near it is held, approved 2026-09-23 — D156; v3 was approved 2026-09-21 — D136; v2 is the file frozen in the illustration test's report/).
  • stills: every scene in every palette and facing both ways shows only its palette's colours, and its arrows cross nothing and bend no tighter than 16 px. In the three, a scene with fewer coloured objects shows fewer colours, and the check says which.
  • objects: every object alone in every palette and facing both ways shows only its palette's colours (none, if it carries none), and every object with a front turns it: its right-facing still is not its left. Only the arc, a bare line, draws the same picture both ways.
  • component: <pepper-illustration> on a bare page, with no Poppins and no other script. A still is its file, drawn at build time — the same picture as the file placed as an image — and no engine loads. What has no file (a description by its address) the engine draws as the live render does, with its own three Poppins faces. Live, it moves. An object alone told to face right is its right-facing file as a still, and live the engine draws it facing right — nearer its right still than its left (five objects with an unmistakable front: card, shield, phone, TV, user).
  • page: what a page pays, on a page shaped like the site's — one live scene, six stills, two live scenes far below. A page of stills downloads no engine and opens no WebGL context; the live scene's still is painted before the engine arrives; a live scene below the fold is not built until it is scrolled to; the three share one WebGL context; out of view a picture pauses, and with none in view no frame is asked for.
  • bundles and browser: the animated pages bundle, and no page logs an error.

npm test holds the tokens: every value in tokens.js equals its token in tokens.css, and no colour is typed in the drawing code (the library, the scenes, the exporter, the animated pages).

A scene is data (3.7.12, D134)

A scene is a description: plain JSON that says which library objects stand where, what they carry and which arrows run between them. library/scene.js composes it on the library's own stage — the one camera, the lights, the outlines, the palette, the arrow rules — so whoever writes one composes a picture and draws nothing. The format is documented at the top of that file; this is the shape:

{ "format": 1, "title": "…", "size": [1000, 470], "palette": "three",
  "objects": [
    { "id": "platform", "object": "disc", "at": [0, 0, 0], "radius": 1.05, "t": 0.24, "colour": 1 },
    { "id": "engine", "object": "block", "on": "platform", "icon": "cpu", "colour": 1 },
    { "id": "results", "object": "bars", "at": [5, 0, 0], "tinted": true, "colour": 2 } ],
  "arrows": [ { "id": "out", "from": "engine", "to": "results", "signal": true } ],
  "motion": { "signal": ["out"] } }
  • Places are along the screen's own axes: x to the right, z toward the viewer (down the screen, at four tenths of x's scale), y up. An object always stands square to the world. The library's first two scenes keep the world axes they were laid out in ("axes": "world"). on stands an object on another; colour is which of the palette's colours it takes. "mirror": true is for a cluster placed by hand round one object, where the side a part sticks out of is the picture: facing right, what stands on another object stands on its other side, so the picture is the mirror image of its left-facing self (3.7.28, D172). Without it, nothing is mirrored.
  • What an object takes is library/catalogue.json: its options, each with its type. A description that names an object, an option or an icon the library does not have is refused, with a sentence that says what it may say instead.
  • Arrows name the two objects they join. The library picks the face an arrow leaves through — of the two sides the camera sees, the one turned toward the target, at the point nearest it; the top when the target stands behind — aims it at the target's middle and ends it where that line meets the target, then stops it 16 px clear of every outline. Its start is walked forward the same way (D176, 3.7.31), so an arrow stands in the middle of the space between the two objects it joins; the face route of two devices standing close keeps its start on the source's face. Several arrows into one object arrive side by side. Between two thin objects that face the viewer standing close side by side — two phones — an arrow through the gap would be a stub, so it leaves the source's face low, bows out toward the viewer and comes to the target from in front of its near edge (3.7.17, D157 c). An end may also be { "of": id, "at": [x, y, z], "viewer": r } — a point measured from an object along its own axes — which is how the approved scenes keep their arrows exactly.
  • The shape comes first, and the objects lead (D155, D157, D158). size is "square", "portrait" or "landscape" (1000 × 1000, 1000 × 1250, 1000 × 560) or [w, h]. A description that gives no object an at is laid out by the library (D158), in the order its objects are listed: a row across a landscape (or a picture with no size), a zig-zag down a portrait or a square, neighbours half their mean size apart on screen (LAYOUT in style.js); an object with on goes with what it stands on, and an arrow that runs down the picture leaves its source at the foot. Place objects by hand only for a picture that is not one chain. The result's warnings say when what the picture holds fills under six tenths of its frame one way — a composition made for another shape — and when the space an arrow crosses between the two masses it joins is more than ARROW.lead (0.85) times the larger of them: the arrow leads.
  • The frame fits what is drawn, and a landscape lies on one line (D177, 3.7.31). The frame is fitted on every vertex of every part, through the camera — never a part's box — so the margins are even by themselves. In a landscape (at least 1.4 times as wide as tall), what stands one above the other is a column, and every column is centred on the picture's middle line: a single object on it, a branch split evenly above and below it. A row the library lays out lifts each mass to the middle of the tallest; a picture placed by hand gets a warning naming the column, how far off the line it stands, and how far to move it in y. A stack whose front card carries the words is a screenCard with sheets (nought to three plain cards behind, the code stack's staircase); a shield carries an icon on its inner shield. When the still's draw order has no answer, what is left is drawn far to near, never in the order the scene lists it.
  • Four rules of composition (D135, D139). Data on an arrow is the signal: "signal": true rests the three-sphere trail on the arrow, sized and spaced by what shows on screen; an arrow carries no packets and packets is refused. A signal is the colour of its source (D139): it takes the colour of the object the arrow comes from — a blue source sends a blue signal — without anything being said, and an arrow's colour is only for saying something its source cannot; an arrow that starts at a bare point takes the palette's first colour. What stands on a platform stands in its centre: on centres it, and nothing is given beside it. A name stands upright: a plate that is only a name — a source, a channel, an item of a list — takes "upright": true and stands, its label and badge on its face (plateUpright() in objects.js; the approved plate() is untouched); a plate lies flat only when it is a platform. Standing plates in a column are 3.4 apart.
  • The signal is one component (D136). K.signal() builds it, SIGNAL in style.js is its standard — three spheres in the colour's 300 / 200 / 200 steps; a lead of 8 px in a 1000-px picture, never under 2.2 half-widths of the line it rides; 2.4 lead radii apart along what shows; resting 0.6 of the way in a still; live, 140 px a second on every arrow, 0.4 s between arrows, 1.5 s before the round starts again — and shownPath() / placeSignal() put it on a path. A description says where a signal runs, never its look or its pace, and which colour only to override the source's (D139). No scene draws its own signal: the slide's rod carries the component too (D136 e, approved 2026-09-21).
  • A ring carries the signal too. A ring named in motion is one leg per hop around what it joins — it leaves each object for the next and comes back to the first — so the signal takes each source's own colour in turn (D139) and each target lights up as it arrives, as along an arrow. The still is untouched: a ring rests no signal on itself.
  • What composed but deserves a look is the result's warnings: two objects in one place, an arrow with no room. library/checks.js adds what the still says about its arrows: a crossing, a bend under 16 px.
  • The six library scenes are descriptions, and every still is byte for byte the committed one. --static fails a scene module that builds its scene itself.
  • A description from anywhere — a Claude Design canvas, a page, a hand — is drawn and checked by python3 scripts/illustrations.py --scene FILE: the still with Poppins inside, the PNG at 2×, the live render, the palette it shows, its arrows. It joins the library once approved: its JSON in scenes/, its thin module, a line in the runner's SCENES and in component.js, its baseline.

A device's screen, a close-up, and which way a picture faces (3.7.16, D154)

  • A screen shows the system's page. A phone, laptop or tv takes "screen" in place of label and icon: { "ui": [ … ], "dark": true } — each item ONE of eyebrow, heading, body, button, icon, lines (a count of grey lines that stand for copy, D170), or a card holding those — laid out top to bottom by uiSteps() in content.js, in the device's canvas (a phone web-mobile, a laptop or a TV web-desktop) from the tokens UI in style.js names: the eyebrow is label/medium, a heading the page's headline/medium and a card's title/medium, copy body/medium, the button the large one (size/control/large, radius/8). Light, it is a page; dark, the footer's ground and inks. Every word is <text> in the still, wrapped to the screen; what runs past the foot is cut and said. The face sizes are the phone's 340 × 720, the laptop's 768 × 489, the TV's 768 × 431.
  • A picture is the exception: { "image": an address }, drawn cover-and-top on the screen and carried in the still as the file itself (data-raster="picture"). loadImages(description) loads it before a build — the element, the runner's pages and Pepper.Scene do — and a scene whose picture has not loaded draws the plain screen and says so.
  • A close-up needs no word. lineWeight() in style.js reads how near the picture is held (px to a scene unit in a 1000-px picture: the first five scenes are 101–152, two phones filling the frame 469) and from 160 thickens the outline, the arrows and the signal — its size, its spacing and its speed — until from 240 they are as thick against the objects as the cross-screen scene's (121.43). The result's weight is the factor; the still's outline and the live one both read it through the camera (cam.userData.weight). The arrow's 16 px clearance is a distance on screen and does not scale. No library scene is near enough to move.
  • direction: left or right. Right stands the camera at the other front corner, mirrored across x, with the lights (cameraAt(), lights() in objects.js); every object turns its front the other way and nothing is mirrored. layoutFor() keeps a world-axes scene where it stood on screen; a screen-axes scene keeps it by its own axes. A turn turns the other way too, and an arrow leaves through the two sides this camera sees. The element and Pepper.Scene take it as an attribute, over the description's own.
  • The still draws what is shown: exportSVG() walks visible objects only (a signal ball with no room on a short arrow was drawn at the scene's origin before).

Line widths (D119, D126)

Three tokens set every line: illustration/outline/width, illustration/arrow/width and illustration/connector/radius. Change one, npm run build, and the live version and the still both follow.

  • Outlines are three.js's thick lines (LineSegments2), the type the arrows use. The token is the width in px of a still at its own size. A still exported at another size scales it with the picture; the live version draws the same line at the size it is shown (stage() sizes the outlines before every render), and never under one device pixel, below which WebGL breaks a line into dots.
  • A renderer needs stencil: true. The outline's ink is not opaque, and a thick line's segments overlap at every joint; the stencil lets the ink lie once on a pixel, as the still's single outline path does. Without it the picture still draws, with darker joints on curves.
  • The faces step back (a slope-scaled polygon offset) by the outline's half-width, so the half of a line that lies over its own face is not cut. It grows with the width and is 1 at today's.
  • Connectors stay tubes: a tube's radius is the token's, in scene units, in both versions already.

One colour or three (D118, D127)

A picture is one of the system's colours or the three. A scene takes palette: 'red', 'yellow', 'blue', or 'three' (red, yellow and blue). Anything else stops the build.

render3d.html?scene=intelligence&palette=blue     export.html?scene=cross-screen&palette=red     slide-live.html?palette=yellow
  • Defaults. Scene 1 is in the three: the TV is red, the phone yellow and the laptop blue, as in the first example. Every other scene is in red. The object sheet is in the three, so the catalogue keeps its look.
  • What follows the palette is every icon tile, tint, highlight, rim, light and signal. The kit is made in a palette (kit({ palette })), and K.colour(i) is the steps (100–400) the i-th coloured object takes: always the one colour, or red, yellow and blue in turn. An object takes its colour from its colour option, or from the palette's first colour when it has none.
  • In the three: each scene gives its coloured objects the three in reading order. A target lights up in its own colour's 100 tint, and a signal takes the colour of the object it comes from (D139) — the palette's first colour only when it comes from no object.
  • The Claude Design source (library/batch2.js) names its one colour RED. The name stays word for word, and the kit binds it to the object's colour.

The outputs (3.7.7; D164)

dist/pepper-illustration.js                        the element, a small classic script
dist/pepper-illustration-engine.js                 the engine it loads: three.js, the library, every scene
dist/stills/<scene>--<palette>--<direction>.svg    every scene in every palette and direction
dist/scenes/<scene>.svg · <scene>@2x.png           the scenes in their own palette, and the three held variants (<scene>--<palette>)
dist/objects/<object>.svg · <object>@2x.png        the objects
dist/descriptions/<scene>.json                     the scenes' descriptions
dist/catalogue.json                                the package's public API
dist/manifest.json                                 every file's sha1, and what each was made from
  • A still's file is its SVG with the design system's own Poppins put inside, cut to the characters it uses (a still without words carries none). It renders correctly placed as an image, in a page, a slide or a PDF. The PNG is that image render at twice the size, transparent. A scene's own palette and direction, and each held variant, is the committed baseline SVG; the rest of stills/ are drawn by the same exporter from the same description.
  • The element is the page's half: component.js, bundled alone, knows each scene's size and its own palette and direction (scenes/meta.js) and nothing of three.js. It paints the still, and the first time a picture has to move it adds pepper-illustration-engine.js beside itself — after the page's load event, a classic script, so it also works from disk. The engine carries Poppins 400, 500 and 600 cut to Latin (library/poppins.js, generated), so its words are Poppins on any page. It holds ONE WebGL renderer, off the page, and each live picture is a 2D canvas it copies a frame into, drawn in a corner of the renderer's canvas the picture's size — so any number of live pictures cost one context. One loop serves them all, and a picture takes part only while its element is in view.
  • When dist/ changes. It is written by npm run illustrations on the platform where the pictures are approved, once every check has passed, and only where a source changed; --bless rewrites it all. npm test holds it: every file is the one the manifest pins and was made from the baseline and the library as they are now (the stills in every palette carry a fingerprint of everything they are drawn from, so a token change leaves them stale until the next build draws them); each SVG in scenes/ and objects/ is its baseline with Poppins put in and nothing else changed; each PNG is twice the still's size; the catalogue, the descriptions and the two generated modules are what the sources write; the element's attributes are the catalogue's; and the element and the engine are what their sources bundle to.

In Claude Design (3.7.9, D132)

Pepper UI is a design system in Claude Design, and this library is its bundle. design-system.js is the bundle's entry: scripts/claude_design.py bundles it into one classic script that assigns window.Pepper, with no eval, no import and no network, which a design system's preview frame forbids.

  • Pepper.Illustration — a library scene or one library object: scene or object, palette, label, and still. Live, it is <pepper-illustration>. With still it is the still itself: the exporter's SVG drawn in the page, in a shadow root so two stills' ids never meet. A still holds no WebGL context, so a canvas can carry any number of them.
  • Pepper.Scene (3.7.12, D134) — a NEW scene, composed on the canvas: scene is a description, palette, label and still as above. What the description has wrong with it, and what composed but deserves a look — the scene's warnings and what library/checks.js reads from its still — is written under the picture in plain words. The system's README in Claude Design carries the method, the format, every object with its size and options (from library/catalogue.json) and the icons; index.d.ts types the description option by option.
  • Pepper.Draft — an object that is not in the library yet, built on the canvas: build(h, options) returns a THREE.Group made with the helpers the kit hands batch 2 (helpers(colour): THREE, MAT, C, RED, slab, roundedShape, outlined, round, tinted, dot, iconSlot, TINT), under the library's stage, lights, camera and outlines. The element draws it through its source property. still shows what the exporter makes of it, where pieces that overlap show before the import and not after. The object inside build stays one plain named function, and comes into the library word for word, as batch 2 did.
  • The starter canvas is claude-design/starter/ — Pepper illustration starter in Claude Design: Pepper installed, one artboard composing a new scene from a description, one placing a library scene, one drafting an object live beside its still. The next batch starts from a copy of it, not from an attachment pack.
  • Republishing is npm run claude-design (build), then the publish in a Claude Code session; npm run claude-design:check says whether what is published is what the repository builds.

Known: the working pages here (render3d.html, export.html, the animated pages) and the baseline SVGs load Poppins from Google Fonts, as they did when the scenes were approved. Only the outputs carry the repository's own files, because moving the baseline would change every SVG.