# Nurblings Avatars: full documentation # Getting started # Getting started Nurblings turns any string into a small, curious creature. The same string always hatches the same Nurbling, so you can give every user, team or repository an avatar without storing anything. ## Install ```sh npm install @nurblings/react # Next.js, React npm install @nurblings/vue # Nuxt, Vue npm install @nurblings/astro # Astro npm install @nurblings/element # any HTML, Svelte, Angular npm install nurblings # plain JavaScript ``` Using a coding agent? `npx skills add albertesparragoza/nurblings` teaches Claude Code, Cursor, Codex and others how to use the library. ## What each package supports Every package draws the same avatar from the same seed. They differ in where the drawing happens and how you pass options. | Feature | React | Vue | Astro | HTML element | JavaScript | | --- | --- | --- | --- | --- | --- | | Server-side rendering | Yes | Yes | Yes | No | Yes | | Client JavaScript for a still avatar | None | Your Vue app | None | Element and core | Optional | | Built-in themes | Prop | Prop | Prop | Attribute | `themed()` | | Your own colours | Prop | Prop | Prop | Property | `createNurblings()` | | Light, dark and auto mode | Yes | Yes | Yes | Yes | Yes | | Motion | Yes | Yes | Yes | Yes | Yes | | Motion speed | Yes | Yes | Yes | No | Yes | | Stops for reduced motion | Yes | Yes | Yes | Yes | Yes | | Glides between views | Yes | Yes | Yes | Yes | Yes | | App-wide configuration | Prop or provider | Prop or plugin | Prop | Property | `createNurblings()` | | Pinned traits | Yes | Yes | Yes | Yes | Yes | - **Server-side rendering:** React renders as a Server Component, Vue renders on the server and hydrates with your app (Nuxt included), and Astro ships static SVG. The HTML element draws in the browser. - **Themes:** `theme="lagoon"` on React, Vue, Astro and the element; `themed('lagoon').nurbling(seed)` in plain JavaScript. - **Your own colours:** `theme={palette([...])}` (Vue: `:theme`), or `el.nurblings = themed(palette([...]))` on the element. `palette()` and `themed()` come from `nurblings/themes`. - **App-wide configuration:** a `nurblings` prop everywhere, plus `NurblingsProvider` in React client components and `NurblingsPlugin` in Vue. - **Glides between views:** `morph()` from `nurblings/transition` runs in the browser: call it from client code in React and from a ` ``` Server rendering works out of the box in Nuxt. ## Astro ```astro --- import { Nurbling } from '@nurblings/astro' --- ``` The component renders static SVG and adds no client JavaScript. ## Any other framework: a custom element `@nurblings/element` works in plain HTML, Svelte, Solid, Angular, Lit or a CMS template: ```ts import '@nurblings/element/define' ``` ```html ``` Attributes mirror the options; see the [package README](https://github.com/albertesparragoza/nurblings/tree/develop/packages/element). ## Alive by default Every Nurbling breathes, blinks and sways its antennae, with CSS only. Pass `animate: false` for a still avatar; [Motion and transitions](/docs/motion.md) has the rest. ## Plain HTML, or an image URL Use a data URI anywhere an image goes: ```ts import { nurbling, nurblingSrc } from 'nurblings' img.src = nurblingSrc('ada@example.com', { size: 64 }) ``` ## Next steps - Tune the motion, or glide avatars between places: [Motion and transitions](/docs/motion.md) - Your palette, designs and parts: [Customising](/docs/customising.md) - Every option, and how pinning works: [API reference](/docs/api.md) - Why an avatar never changes: [Generations and stability](/docs/api.md#generations-and-stability) --- # Motion and transitions # Motion and transitions Every Nurbling breathes, blinks and sways its antennae. The motion is CSS inside the SVG: no script and no ids, so it works in server components, in an `` and in a `data:` URI. Each seed gets its own timing, so a grid of avatars never moves in unison. ## Turn motion off, or pick layers ```ts nurbling(seed) // every layer nurbling(seed, { animate: false }) // a still drawing nurbling(seed, { animate: { blink: false } }) // everything but blinking nurbling(seed, { animate: { hover: false, speed: 0.5 } }) // calmer, no hover ``` Framework components take the same value: `animate={false}` in React and Astro, `:animate="false"` in Vue. On ``, `animate="false"` draws a still avatar and a list keeps only some layers: `animate="blink hover"`. | Layer | What moves | | --- | --- | | `breath` | The body squashes and stretches slowly from its base. | | `blink` | The eyes blink every few seconds. | | `antennae` | Each antenna drifts on its own clock. | | `hover` | The antennae wiggle faster while the pointer is over the avatar. | `speed` multiplies every clock (2 is twice as fast) and must be a positive number. Sleepy Nurblings move a little slower. ## When nothing moves - When the viewer asks for reduced motion. Every rule sits inside `prefers-reduced-motion: no-preference`, so a paused avatar is simply its still drawing. - At 32 px and below, where motion reads as noise. For long lists, `animate: { breath: false, antennae: false }` keeps just the blink and the hover. Each animated avatar carries its own small style block, under 1 KB before compression and the same text in every avatar, because styles cannot be shared between SVGs without ids. For hundreds of avatars on one page, `animate: false` is the lighter choice. ## Move an avatar between places: `morph(update, options?)` A Nurbling can glide from a dialog into a list, or from a hero into a header, instead of jumping. Give the avatar the same `transition` key in both places, then make the change inside `morph`: ```ts import { morph } from 'nurblings/transition' // the same key in the list and in the dialog links the two nurbling(user.id, { size: 48, transition: user.id }) nurbling(user.id, { size: 320, transition: user.id }) await morph(() => dialog.close()) ``` `update` must change the page before it returns, or return a promise that resolves once it has: ```tsx // React morph(() => flushSync(() => setOpen(false))) ``` ```ts // Vue morph(async () => { open.value = false await nextTick() }) ``` `duration` (milliseconds, default 450) and `easing` (any CSS easing) tune the move. Under reduced motion the change simply happens. ## How it works - Browsers with View Transitions use them when each key is on screen once. - Otherwise, and in every other browser, the avatar is animated from its old box to its new one with a transform, which keeps the vector sharp. A new avatar flies out of a twin that stays on screen, so a dialog opening over a list animates too. - A name the page set itself on a tagged element is restored afterwards, and overlapping calls never undo each other. - The helper is its own entry, under 1 KB: pages that never import it pay nothing. ## Across page navigations For moves between pages, use your framework's view transitions and set `view-transition-name` to `transitionName(key)`, also exported by `nurblings/transition`, so the names match the ones `morph` uses. --- # Customising # Customising `createNurblings(config)` makes an instance with your palette, body designs, drawn parts and defaults. Configure it once and use it everywhere: ```ts import { createNurblings, SILHOUETTES } from 'nurblings' export const avatars = createNurblings({ // brand colours replace the built-in palette shells: { mist: '#e4ebf2', sand: '#f1e4cf' }, accents: { ink: '#2c5fd9', coral: '#c94f38', forest: '#2e7d4f' }, // designs are added to the built-in ones; false drops one silhouettes: { pear: false, robot: { hw: 1.1, width: 0.9, belly: 0.3, tip: 0.9, rows: 4, cols: 2, plates: 'side', weight: 2 }, }, slots: { mouth: false, // drop a part extra: () => '', // replace it brow: (ctx, base) => `${base()}`, // wrap it }, defaults: { size: 48, background: 'circle' }, }) avatars.nurbling('user-42') avatars.traits('user-42', { silhouette: 'robot' }) // custom names are typed ``` ## The rules - **Colours are paired by contrast.** An accent is used only if it reads on light and dark pages (2.5:1) and on a shell (3:1); every shell needs readable eyes and at least two such accents. Setup throws a `RangeError` naming the colour when that is not possible, so a palette that would give unreadable faces fails before a single avatar renders. - **Slots and parts** change or add drawn parts: see [Extending parts](#extending-parts). Their markup goes into the SVG as it is, so build it from your own code, the anchors and the colours, never from user input, and put any text from `props` through `ctx.esc`. Keep it free of `id` attributes, so many avatars can share a page. Treat what `ctx` holds as read-only, and return a string. - **Custom body designs** need finite numbers, a positive `hw` and `width`, a known `plates` zone, and at most 12 plate rows and 12 columns. Setup throws a `RangeError` naming the design and the field otherwise. - The same config and seed always render the same avatar. No seed draws Nurbi: he is `nurbi()` from `nurblings/nurbi`. ## Extending parts Every drawn part is a named layer, painted in this order: `backdrop`, `antennae`, `body`, `plates`, `extra`, `eyes`, `brow`, `mouth`, and any part a config adds. Three tools work on them, and they combine: - **`slots`** change a part, built in or added: return markup to replace it, call `base()` to wrap it, or pass `false` to drop it. - **`parts`** add a new part, painted right after the part it names (`after`, `mouth` by default). Added parts step aside at 32 px and below unless they set `small: true`. - **`use`** stacks presets. A preset is an ordinary config, so a recipe, a brand kit and a plugin are all the same thing. ### What every part reads Slots and parts all receive the same `ctx`. Built-in parts are drawn from the same anchors and colours, so a part placed on them fits every body, follows every theme and switches with the page. | Field | Is | | --- | --- | | `anchors` | Named places, in drawing units: `centre`, `base`, `crown`, `eyes` (each with `x`, `y`, `rx`, `ry`), `brow`, `mouth`, `chest`, `box`, and `band(f)`, the body's `left` and `right` edges at a height from `0` (base) to `1` (crown) | | `colours` | Each colour role as drawn for this avatar: `shell`, `eye`, `accent` (brow), `mark` (antennae), `wear` (extras), `ground` (container). A theme sets them all | | `paint(role, 'fill' \| 'stroke')` | A role as an attribute. In `mode: 'auto'`, `ground` and `mark` switch with the page, as the built-in parts do | | `random(name)` | A random stream of its own, the same for an avatar every time | | `theme`, `options`, `props` | The theme in use, the call's options, and data from the config and the call (the call wins) | | `small`, `size`, `mode` | 32 px and below, the pixel size, and the page mode | | `n(value)`, `esc(text)` | A number as the renderer writes it, and text made safe for SVG | | `traits`, `geometry` | The raw traits and body shape, for anything else | ### Recipes Each recipe is a preset. The home page draws every one of them. **Body.** Wrap it and add a belly patch in a lighter shade of its own shell: ```ts import { createNurblings } from 'nurblings' import { setFill, shade } from 'nurblings/themes' const belly = { slots: { body: ({ anchors: a, colours, n }, base) => { const { left, right, y } = a.band(0.2) return `${base()}` }, }, } ``` **Eyes.** Keep them and put round glasses over them: ```ts const glasses = { slots: { eyes: ({ anchors: { eyes: [l, r] }, n }, base) => { const size = l.rx * 1.7 return `${base()} ` }, }, } ``` **Mouth.** Replace it with a smile in the eye colour, as wide as the eyes are apart. The built-in mouth steps aside at 32 px; this one does too: ```ts const smile = { slots: { mouth: ({ anchors: a, small, paint, n }) => { if (small) return '' const w = (a.eyes[1].x - a.eyes[0].x) * 0.35 return `` }, }, } ``` **Extras.** Swap the scarf, collar or hat for a heart pin on the chest: ```ts const pin = { slots: { extra: ({ anchors: a, small, n }) => { if (small) return '' const x = a.chest.x + (a.band(0.2).right - a.chest.x) * 0.25 const y = a.band(0.2).y return `` }, }, } ``` **A new part.** A status dot that reads `props` on each call, ringed in the container colour so it switches with the page: ```ts const status = { parts: { status: { after: 'mouth', small: true, draw: ({ anchors: a, props, paint, n }) => { const { right, y } = a.band(0.14) const fill = props.status === 'online' ? '#2e9e5b' : '#9a9a9a' return `` }, }, }, } ``` **Backdrop.** Keep the container and give it your brand colour: ```ts const brand = { slots: { backdrop: (ctx, base) => setFill(base(), '#ffd166') }, } ``` ### Combining Put presets in `use`, or combine them with `compose()`. They apply in order: - Slots chain: each later slot's `base()` is what the earlier ones drew, so two presets can both wrap the eyes, and a preset can restyle a part another one added. - A later slot that does not call `base()` replaces everything before it; a later `false` drops the part, and a slot after that starts from nothing. - Parts, body designs, colours and props merge by name, later names winning. The last theme wins, and defaults merge. ```ts export const avatars = createNurblings({ use: [belly, glasses, smile, pin, status, brand], theme: lagoon, }) avatars.nurbling(user.id, { props: { status: user.online ? 'online' : 'away' } }) ``` A slot for a name that is no part, or a part placed after one that does not exist, throws a `RangeError` at setup. Antennae and brows can be slotted too, but they carry the family look, so wrap them rather than replace them. ### Collections: new eyes, mouths and extras The seed picks each avatar's eye shape, mouth and extra from a list. `eyes`, `mouths` and `extras` add to those lists, the same way `silhouettes` adds body designs, so a season's accessories are one preset: - A new name joins the list and draws itself with `draw(ctx)`. `weight` sets how often it comes up: 1 by default, where each built-in extra weighs 1 and `none` 6. - `false` drops a built-in choice. Drop them all to wear only your own. - A built-in name with `draw` gets a new look; with `weight`, a new share. - New mouths and extras step aside at 32 px and below; eyes always show. - Only that trait changes. Every other trait of a seed stays as it was. ```ts const winter = { extras: { beanie: { draw: ({ anchors: a, colours, n }) => { const rows = [0.62, 0.68, 0.74, 0.8, 0.85].map((f) => a.band(f)) const right = rows.map((r) => `${n(r.right)},${n(r.y)}`) const left = rows.map((r) => `${n(r.left)},${n(r.y)}`).reverse() return `` }, }, bowtie: { weight: 2, draw: ({ anchors: { chest: c }, colours, n }) => ``, }, }, } // add them to the extras createNurblings({ use: [winter] }) // or wear only the new season createNurblings({ use: [winter], extras: { none: false, scarf: false, hat: false, collar: false } }) avatars.nurbling(user.id, { extra: 'beanie' }) // new names can be pinned, and are typed ``` Body designs follow the same rule: `silhouettes: { robot: {...}, pear: false }` adds one and drops another. ## Use it everywhere ```tsx // React server components: pass it as a prop (wrap once in your own component) // React client components: a provider import { NurblingsProvider } from '@nurblings/react/client' {children} ``` ```ts // Vue and Nuxt: install once import { NurblingsPlugin } from '@nurblings/vue' app.use(NurblingsPlugin(avatars)) ``` ```ts // Astro: // Custom element: el.nurblings = avatars ``` ## Themes Ten themes ship in `nurblings/themes`. Pass one by name to any component, or hand it to `createNurblings`: ```tsx ``` ```ts import { createNurblings } from 'nurblings' import { lagoon } from 'nurblings/themes' export const avatars = createNurblings({ theme: lagoon }) ``` | Theme | Feel | | --- | --- | | `lagoon`, `punch`, `candy`, `picnic`, `sorbet`, `terracotta` | Soft bodies on a strong container colour | | `marble`, `riso`, `lime`, `bauhaus` | Vivid bodies with one dark ink for every mark | Importing a theme by name keeps only that one in your bundle. The `theme="..."` prop on the components looks names up in `THEMES`, which adds about 1 KB. ### Your own colours `palette()` turns 2 to 5 colours into a theme, in any order. The lightest become bodies, the darkest the brows and antennae, and the rest the containers. ```ts import { palette } from 'nurblings/themes' const brand = palette(['#264653', '#e9c46a', '#f4a261']) createNurblings({ theme: brand }) ``` | Colours | Bodies | Brows and antennae | Containers | | --- | --- | --- | --- | | 2 | 1 | 1 | a tint of the body | | 3 | 1 | 1 | 1 | | 4 | 2 | 1 | 1 | | 5 | 2 | 2 | 1 | Two colours give a two-tone family. Themes are repaired, never refused: brows and antennae are darkened or lightened until they read on every body, and eyes turn light on a dark body. For full control, write the theme object yourself: ```ts const night = { name: 'night', shells: { moon: '#e8e4f7', dusk: '#b9b3e6' }, accents: { ink: '#2b2350' }, backdrops: ['#2b2350', '#6a5acd'], // containers on light pages darkBackdrops: ['#15122b'], // containers on dark pages } satisfies Theme ``` ## Light and dark pages `mode` tells an avatar what page it sits on. The creature never changes; its container colour does, and so do the antennae where they would disappear into it. ```tsx ``` `auto` needs no JavaScript. The nearest `data-theme="dark"` or `.dark` ancestor turns it dark, the nearest `data-theme="light"` or `.light` one keeps it light, at any depth, and with neither the OS setting decides. ## Colour helpers for slots Most slot code is no longer needed once a theme exists. When a slot still recolours, `nurblings/themes` has the pieces: ```ts import { contrast, ensureContrast, pickBy, readableOn, recolour, setFill, shade } from 'nurblings/themes' createNurblings({ theme: brand, slots: { backdrop: (ctx, base) => setFill(base(), pickBy(['#264653', '#2a9d8f'], ctx.traits.silhouette.grain)), brow: (ctx, base) => recolour(base(), ctx.traits.palette.accent, '#1b1b1b'), }, }) ``` | Helper | Does | | --- | --- | | `contrast(a, b)` | The WCAG contrast ratio of two colours | | `ensureContrast(fg, bg, min)` | `fg`, moved until it reads on `bg` at `min`:1 | | `readableOn(ground, candidates, min?)` | The first candidate that reads on `ground` | | `shade(hex, k)` | Toward white for `k > 0`, toward black for `k < 0` | | `pickBy(list, n)` | One item per whole number, such as the seed's grain | | `recolour(markup, from, to)` | Swaps one colour in a slot's markup | | `setFill(markup, fill)` | Sets the first fill, such as the container's | --- # API reference # API reference Everything below is exported from the `nurblings` package. It has no runtime dependencies and runs anywhere JavaScript does: browsers, Node, Deno, Bun, edge runtimes and build-time rendering. ```ts import { nurbling, traits, toDataUri } from 'nurblings' ``` ## `nurbling(seed, options?)` Returns one Nurbling as an SVG string. ```ts nurbling('user-42') nurbling('user-42', { size: 48, background: 'circle', title: 'Ada Lovelace' }) ``` Seeds are normalised first (see [`normaliseSeed`](#normaliseseedseed)), so `Ada Lovelace`, `ada.lovelace` and `ADA_LOVELACE` hatch the same Nurbling. Prefer a stable user id over an email: ids never change, and normalisation means two different strings can hatch the same creature. ### Options | Option | Type | Default | What it does | | --- | --- | --- | --- | | `size` | `number` | `128` | Width and height in pixels, a positive number. At 32 and below, small-size mode drops the mouth and extras so the face still reads, and motion stops. | | `background` | `'none' \| 'circle' \| 'squircle' \| 'square'` | `'none'` | The container behind the Nurbling, in a tint of its own shell colour. | | `title` | `string` | `'Nurbling'` | The accessible name, used for `aria-label` and ``. Escaped for you. | | `decorative` | `boolean` | `false` | Hides the avatar from assistive technology (`aria-hidden`, no name), for an avatar beside the person's name in text. | | `animate` | `boolean \| Motion` | `true` | Ambient life: breath, blink, antenna drift and a hover wiggle. `false` draws a still avatar; an object picks layers. See [Motion and transitions](/docs/motion.md). | | `frame` | `'auto' \| 'full' \| 'portrait'` | `'auto'` | How the Nurbling sits in its square. `portrait` crops closer on the face, which reads better in small round avatars; `full` shows the whole figure. `auto` uses a portrait at 48 px and below. | | `mode` | `'light' \| 'dark' \| 'auto'` | `'light'` | The page the avatar sits on. The container colour and, where needed, the antennae change; the creature does not. `auto` follows the OS or a `data-theme="dark"` or `.dark` ancestor with CSS only. | | `transition` | `string` | none | A key for `morph()`: the same key in two places links them, so the avatar glides between them. Adds a `data-nurbling-transition` attribute. | | `gen` | `1` | `1` | The trait generation. Pin it to keep an avatar identical across future releases. | | `mood` | `'neutral' \| 'curious' \| 'pleased' \| 'thinking' \| 'sleepy'` | from the seed | Pins the resting mood. | | `mouth` | `'none' \| 'line' \| 'smile'` | from the seed | Pins the mouth. | | `extra` | `'none' \| 'scarf' \| 'hat' \| 'collar'` | from the seed | Pins the extra. | | `silhouette` | `'classic' \| 'basketball' \| 'squat' \| 'firm' \| 'round' \| 'bell' \| 'wide' \| 'smooth' \| 'pear' \| 'tall'` | from the seed | Pins the body design. Each seed still varies it slightly and gets its own plate pattern. | | `shell` | `'mint' \| 'sky' \| 'butter' \| 'peach' \| 'lilac' \| 'sage' \| 'cloud' \| 'blush' \| 'lemon'` | from the seed | Pins the body colour. The accent is still chosen from the colours that read well on it. | Pinning one trait never changes any other: every trait is drawn from its own random stream. The one exception is `shell`: pinning it re-picks the accent from the colours that read well on that shell, so the brow and antennae keep their contrast. An unknown value for `gen`, `mood`, `mouth`, `extra`, `silhouette` or `shell` throws a `RangeError`, and so do a `size` or an `animate.speed` that is not a positive number. ## `traits(seed, options?)` Returns the resolved traits a seed hatches, without rendering: silhouette, antennae, eyes, brow, mouth, extra, mood and palette. Useful for tests, for theming around an avatar (the palette's `shell` and `accent` are good UI colours), or for rendering with `renderTraits`. It returns a fresh object every time: changing it never affects any other avatar. ## `nurblingSrc(seed, options?)` The avatar as a data URI in one call, for an `<img src>`, a CSS `url()` or an email. Takes the same options as `nurbling()`. A `createNurblings` instance has the same thing as `.src(seed, options?)`. ```ts img.src = nurblingSrc(user.id, { size: 64, background: 'circle' }) ``` ## `toDataUri(svg)` Wraps an SVG string as a `data:` URI for an `<img src>` or a CSS `background-image`. ```ts const src = toDataUri(nurbling('user-42', { size: 64 })) ``` ## `renderTraits(traits, options?)` Renders a traits object you built or edited yourself. Takes the rendering options: `size`, `background`, `title`, `decorative`, `animate`, `frame`, `mode` and `transition`. Every value in the traits is checked first: a colour that is not 6-digit hex, a name outside the known traits or a number that is not finite throws a `RangeError`, so traits from a database or a form can never inject markup. ## `normaliseSeed(seed)` The normalisation every seed goes through before hashing: Unicode NFKC, lower case, accents and other combining marks stripped, whitespace and punctuation removed. Emoji are kept, so an emoji seed still hatches its own Nurbling. ## `nurbi(options?)` From `nurblings/nurbi`: Nurbi, the family's flagship character, as the stored drawing. It takes the rendering options: `size`, `background`, `title`, `decorative`, `animate`, `frame` and `transition`. He has an entry of his own so no other import carries his drawing. The seed `nurbi` is an ordinary Nurbling. ## `createNurblings(config)` Makes an instance with your palette, body designs, drawn parts and defaults, returning `{ nurbling, traits, src }` with the same signatures as above. Its calls also take `theme` and `props`. Colours are checked for contrast, and part positions and slot names are checked, at setup. See [Customising](/docs/customising.md). | Config | What it does | | --- | --- | | `theme` | A colour set for every avatar | | `shells`, `accents` | Body and mark colours, replacing the built-in ones | | `silhouettes`, `eyes`, `mouths`, `extras` | Lists the seed picks from. New names are added to the built-in ones, `false` drops one, and `weight` sets how often each comes up. New eyes, mouths and extras draw themselves with `draw(ctx)` | | `slots` | Replace, wrap or drop any part by name, built in or added | | `parts` | New parts, each `{ after?, small?, draw(ctx) }` | | `props` | Data every slot and part reads as `ctx.props`; a call's `props` win | | `use` | Presets applied first, in order: each is a config like this one | | `defaults` | Options for every call | Every slot and part receives the same context: `anchors`, `colours`, `paint()`, `random()`, `theme`, `options`, `props`, `n()`, `esc()`, `small`, `size`, `mode`, `traits` and `geometry`. See [Extending parts](/docs/customising.md#extending-parts). ## `compose(...configs)` Combines configs into one, the same way `use` does: slots chain, each later one wrapping what the earlier ones drew; parts, designs, colours and props merge by name, later names winning; the last theme wins; defaults merge. ```ts const avatars = createNurblings(compose(brand, glasses, status)) ``` ## `nurblings/themes` Named themes, `palette()` and colour helpers, in their own entry. Components take `theme="lagoon"`; `createNurblings({ theme })` takes a theme object. See [Customising](/docs/customising.md#themes). | Export | Does | | --- | --- | | `lagoon`, `punch`, `candy`, `picnic`, `sorbet`, `terracotta`, `marble`, `riso`, `lime`, `bauhaus` | The built-in themes | | `THEMES`, `ThemeName` | All of them by name | | `palette(colours, name?)` | A theme from 2 to 5 hex colours | | `themed(theme)` | A ready instance: `themed('lagoon').nurbling(seed)` | | `contrast`, `ensureContrast`, `readableOn`, `shade`, `pickBy`, `recolour`, `setFill` | Colour helpers for slots | | `themeOf(theme)` | A theme from its name, or the theme object itself; an unknown name throws a `RangeError` | | `renderNurbling(renderer, seed, options, theme?)` | What the framework components call: an app-wide instance when there is one, a named theme when given, otherwise the default family. For writing a component of your own | ## `morph(update, options?)` and `transitionName(key)` From `nurblings/transition`, a separate entry of under 1 KB. `morph` applies a page change and moves every avatar tagged with a `transition` key from where it was to where it lands. `options` is `MorphOptions`: `duration` in milliseconds (450 by default) and `easing`, any CSS easing. `transitionName(key)` returns the `view-transition-name` a key uses, for cross-page transitions in your framework. See [Motion and transitions](/docs/motion.md). ## Constants and types | Export | What it is | | --- | --- | | `SILHOUETTES`, `SHELLS`, `ACCENTS` | Generation 1's body designs, body colours and accent colours by name: the values the matching options accept | | `SLOTS`, `SlotName` | The drawn parts in paint order, the names `slots` and `parts.after` take | | `NurblingOptions`, `RenderOptions` | The options of `nurbling()` and of `renderTraits()` | | `Traits` and its parts (`Silhouette`, `Antennae`, `Eyes`, `Brow`, `Palette`, …) | What `traits()` returns and `renderTraits()` takes | | `NurblingsConfig`, `ConfiguredOptions`, `Nurblings` | A `createNurblings` config, its call options, and the instance it returns | | `Slot`, `Slots`, `Part`, `SlotContext`, `CallOptions`, `Variant`, `Variants`, `Anchors`, `Point`, `EyeAnchor`, `ColourRole`, `Props`, `SilhouetteShape` | The pieces of an extension; see [Customising](/docs/customising.md) | | `NurblingRenderer` | Anything with a `nurbling(seed, options)` method: the default package or an instance. Every component's `nurblings` prop takes one | | `Rng` | The seeded random source behind `ctx.random()` | | `Theme`, `Hex`, `Motion`, `Mode`, `Frame`, `Background`, `Generation` | The value types the options use | ## Generations and stability Traits ship in numbered generations. Within a generation, a seed always hatches the same Nurbling: releases never change existing avatars. The exact SVG for a seed is stable for the same options and configuration; motion markup is a layer on top of the drawing and can be turned off with `animate: false`. New shapes, colours or extras arrive as a new generation, and you opt in by passing `gen`. ## Accessibility - Every SVG has `role="img"`, an `aria-label` and a `<title>`. Pass `title` with the person's name, for example `title: 'Ada Lovelace'`. - When the name is already written next to the avatar, pass `decorative: true` instead, so screen readers do not read it twice. - Colours are chosen for contrast: the accent clears 3:1 on the body, and the antennae read on both light and dark pages. - Motion is on by default, stops completely under `prefers-reduced-motion`, and turns off with `animate: false`. ## Licence of generated avatars The code is MIT. Every avatar it generates is dedicated to the public domain under CC0: use them anywhere, commercially or not, without attribution. Nurbi's exact body design and colours are not among the generator's choices. The one exception is Nurbi himself, the stored drawing that `nurbi()` from `nurblings/nurbi` returns: the names Nurbi and Nurblings and that artwork are reserved, see [TRADEMARKS.md](https://github.com/albertesparragoza/nurblings/blob/develop/TRADEMARKS.md).