Flungies
@flung.dev/flungies draws an avatar for anyone from an id: a plush speech bubble caught mid-throw, with two speed lines behind it, a face and sometimes an accessory. The same id gives the same Flungie everywhere, for good.
usr_8f3k2usr_01hzx3k9qvusr_7d1c0busr_ausr_bflungcreatormem_1mem_2org_demows_northwind42
TL;DR
| Question | Answer |
|---|---|
| How do I install it? | npm install @flung.dev/flungies |
| What do I give it? | A stable id for the person or the thing, such as a user id. An email or a name can change, and the Flungie with it. |
| What does it give back? | An SVG string, from flungie(). A data: URI, a React component, a custom element and an animated WebGL hero are built on it. |
| Where does it run? | Anywhere JavaScript runs: browsers, Node, Cloudflare Workers, Deno and Bun. The SVG needs no DOM. |
| What does it depend on? | Nothing at run time. React is an optional peer, for the React component. |
| How many are there? | 907,200 trait sets, before the sizes, tilts and wobbles that vary on top of them. |
What a Flungie is
Flung's mark (G9) is a speech bubble thrown across the frame. A Flungie is that bubble as a character: a plush, felted body with a tail, caught mid-throw, leaning into the throw.
- The body is the bubble: a soft superellipse with a wedge-shaped tail at its lower back.
- Two speed lines trail behind it, in two of the brand's colours (peach over sky is the brand pair).
- A face sits toward the front of the body, the way it is travelling.
- An accessory is chosen from a creator-themed set: headphones, caps, a beanie, glasses, a party hat, an antenna, a bow, a ring light, a crown, a megaphone, a pencil and a paper plane.
- A disc behind it is a soft tint on light pages and a deep tone on dark ones. A square tile (
variant: 'square') and no ground at all ('bare') are the other variants.
By default the Flungie flies right, with its tail on the left, as the mark does. About a third of Flungies are mirrored. A mirrored Flungie is the same drawing, flipped.
Try it
Type any id to see its Flungie, at every size it simplifies or adds detail at, and the traits it picked. It opens on usr_8f3k2.
Flungie avatar: Ocean wobbly with crown
- 16 px
- 24 px
- 32 px
- 48 px
- 64 px
- 96 px
- Body
- Wobbly
- Colour
- Ocean
- Eyes
- Ovals
- Mouth
- None
- Accessory
- Crown
- Side
- Tail left, flying right
- Speed lines
- Lilac and peach
- Blush
- Off
Install
npm install @flung.dev/flungiesUse
import { flungie, toDataUri, traits, render } from '@flung.dev/flungies'
el.innerHTML = flungie(user.id, { size: 32, theme: 'dark' }) // an SVG stringimg.src = toDataUri(flungie(user.id, { size: 64 })) // or a data: URIrender(traits(user.id, { accessory: 'crown' }), { size: 128 }) // overrides win over the seedSeed with a stable ID, such as a user ID, rather than an email or a name that may change.
On a server
The core needs no DOM, so a Worker can serve Flungies as images. A seed always gives the same drawing, so the response can be cached forever.
import { flungie } from '@flung.dev/flungies'
export default { fetch(request: Request): Response { const url = new URL(request.url) const id = url.searchParams.get('id') ?? '' const size = Math.min(512, Math.max(16, Number(url.searchParams.get('size')) || 64)) return new Response(flungie(id, { size }), { headers: { 'content-type': 'image/svg+xml', 'cache-control': 'public, max-age=31536000, immutable' }, }) },}Each SVG's internal ids come from a counter by default. When a server renders a Flungie into HTML that a browser then hydrates, pass the id option (flungie(id, { size, id: 'avatar-1' })) so both sides write the same markup. The React component does this for you with useId.
React
import { Flungie } from '@flung.dev/flungies/react'
<Flungie seed={user.id} size={32} /><Flungie seed={user.id} size={240} theme="dark" animated />With animated and a size of 160 or more, the component shows the SVG first, then loads @flung.dev/flungies/gl and swaps in the WebGL hero. Below 160 px, it is always the SVG. The component renders on a server and hydrates without a mismatch.
WebGL hero
import { mountFlungieGL } from '@flung.dev/flungies/gl'
const hero = mountFlungieGL(el, user.id, { size: 320, theme: 'light' })hero.setTheme('dark')hero.setSize(240)hero.dispose() // stops the loop, releases the GL context and empties elThe hero animates only while it is on screen and the page is visible. Under prefers-reduced-motion it draws one still frame. Without WebGL2, or if the context is lost, it shows the SVG Flungie, and hero.webgl is false.
Custom element
import { defineFlungie } from '@flung.dev/flungies/element'defineFlungie() // registers <flung-ie>; defineFlungie('my-avatar') uses another name<flung-ie seed="usr_8f3k2" size="40" theme="dark"></flung-ie><flung-ie seed="usr_8f3k2" size="240" animated></flung-ie>The element takes the attributes seed, size, theme, variant, salt and animated.
Options
| Option | For | Default |
|---|---|---|
size | Pixels. The drawing simplifies under 32 px and gets its felt texture from 80 px. | 64 |
theme | 'light' or 'dark'. Picks the disc and checks the contrast with the ground. | 'light' |
variant | 'disc', 'square' (a rounded tile) or 'bare' (no ground). | 'disc' |
texture | false turns the felt filter off. | true |
ground | With 'bare', the colour the Flungie sits on, used for the rim check. | the theme's page colour |
id | The prefix for the SVG's internal ids, so a server and a browser render the same markup. | a counter |
overrides | Traits to set instead of the seed's ({ accessory: 'none' }). | none |
For other products, these options change which Flungie a seed gives. With all of them unset, a seed gives exactly the Flungie it gives on Flung.
| Option | For |
|---|---|
salt | Mixed into the seed, so the same user ID gives a different Flungie in your product. |
palette | Body colours to pick from, instead of COLOURS: { id, label, hex, light: [...discs], dark: [...discs] }, with discs as DISCS keys or #RRGGBB. |
accessories | { include: ['none', 'headphones'] } or { exclude: ['crown'] }. |
flungie(user.id, { salt: 'acme', accessories: { exclude: ['crown', 'halo'] } })traits(user.id, {}, { salt: 'acme' }) // traits(seed, overrides, config)Size tiers
A Flungie is drawn for its size: small ones drop detail that would turn to noise, large ones gain a felt texture.
| Size | What changes |
|---|---|
| Under 32 px | No speed lines, shadow, blush, mouth or eye highlights. Eyes are 1.55 times larger, wide eyes become dots, every stroke is at least 0.95 px, the character is scaled up 1.14 times to fill the disc, and fine accessory detail is dropped. |
| 32 to 63 px | Adds the lines, blush, mouth, sheen and highlights. |
| 64 px and up | Adds every accessory detail. |
| 80 px and up | Adds the felt filter. |
The stability promise
A seed's Flungie never changes. People recognise theirs, so a drawing that moved would be a breaking change for every one of them, however much better it looked. Version 1 of the drawing is frozen, and every release is checked against a fixed set of seeds and the exact SVG each one draws. A change that has to alter an existing Flungie ships behind a new version you opt into, and the default stays where it is.
What does change a Flungie is yours to choose: salt, palette and accessories make a set of your own, and overrides sets a trait outright. With none of them, a seed gives exactly the Flungie it gives on Flung.
Traits
Every trait draws from a stream of its own, seeded by the id, so overriding one trait never changes another. The chance is how often a seed picks the option; overrides picks it outright.
Accessories
Bodies
Colours
Eyes
Mouths
Speed lines
Accessories
The creator set. An accessory's colour is picked from a list of its own, the first that stands clear of the body (and, for one above the head, of both themes' discs).
| Option | Id | Chance |
|---|---|---|
| None | none | 10% |
| Headphones | headphones | 6.4% |
| Beanie | beanie | 6.4% |
| Cap, forward | cap | 6.4% |
| Cap, backward | capBack | 6.4% |
| Sunglasses | sunglasses | 6.4% |
| Round glasses | glasses | 6.4% |
| Party hat | partyHat | 6.4% |
| Antenna | antenna | 6.4% |
| Bow | bow | 6.4% |
| Ring light | halo | 6.4% |
| Crown | crown | 6.4% |
| Megaphone | megaphone | 6.4% |
| Pencil | pencil | 6.4% |
| Paper plane | plane | 6.4% |
Bodies
The bubble, as a soft superellipse with a tail, in six builds.
| Option | Id | Chance |
|---|---|---|
| Classic | classic | 24% |
| Round | round | 15% |
| Squat | squat | 15% |
| Tall | tall | 15% |
| Wobbly | wobbly | 15% |
| Puffy | puffy | 15% |
Colours
The body colour. Each one sits on a disc chosen for it, a pale tint on a light page and a deep tone on a dark one.
| Colour | Id | Hex | Chance |
|---|---|---|---|
| Peach | peach | #FFA584 | 9.6% |
| Sky | sky | #86C0FF | 9.6% |
| Butter | butter | #FFD66E | 7.4% |
| Mint | mint | #8EDDB5 | 7.4% |
| Lilac | lilac | #BFA4FF | 7.4% |
| Coral | coral | #FF7E6E | 7.4% |
| Sand | sand | #E6C9A0 | 5.9% |
| Cloud | cloud | #ECEAF2 | 5.9% |
| Ink | ink | #2B2834 | 5.9% |
| Ocean | ocean | #3F7EDB | 6.7% |
| Moss | moss | #4E9A73 | 6.7% |
| Plum | plum | #7457C9 | 6.7% |
| Berry | berry | #D2457E | 6.7% |
| Rust | rust | #C9643B | 6.7% |
Eyes
| Option | Id | Chance |
|---|---|---|
| Dots | dots | 27% |
| Ovals | ovals | 20% |
| Sleepy | sleepy | 13% |
| Wink | wink | 11% |
| Wide | wide | 16% |
| Happy arcs | happy | 13% |
Mouths
| Option | Id | Chance |
|---|---|---|
| None | none | 30% |
| Smile | smile | 30% |
| Little o | o | 10% |
| Cat | cat | 15% |
| Grin | grin | 15% |
Speed lines
The two lines behind the body, upper and lower. Peach over sky is the brand pair.
| Pair | Id | Upper | Lower | Chance |
|---|---|---|---|---|
| Peach and sky | brand | #FFA584 | #86C0FF | 29% |
| Butter and coral | sunset | #FFD66E | #FF7E6E | 14% |
| Mint and sky | lagoon | #8EDDB5 | #86C0FF | 14% |
| Lilac and peach | dusk | #BFA4FF | #FFA584 | 14% |
| Coral and butter | ember | #FF7E6E | #FFD66E | 14% |
| Sky and lilac | twilight | #86C0FF | #BFA4FF | 14% |
Side
Which way the Flungie flies. By default it flies right with its tail on the left, as the mark does.
| Option | Id | Chance |
|---|---|---|
| Tail left, flying right | left | 69% |
| Tail right, flying left | right | 31% |
Blush
Rosy cheeks, on or off, picked by the seed.
Accessibility
Every Flungie is an image with a name: the SVG carries role="img" and an aria-label that says what it shows, such as "Flungie avatar: Ocean wobbly with crown", and describe() returns the same words.
Next to the person's name, a Flungie repeats what the name says. There, hide it from screen readers: aria-hidden="true" on the element that holds it, or an empty alt on an <img> with a data: URI.
The animated hero breathes and blinks only while it is on screen and the tab is visible, and under prefers-reduced-motion it draws one still frame. Below 160 px the React component always draws the still SVG.
Also exported
TRAITS, every trait with its options, for pickers.combinations(), which returns 907,200 discrete trait sets.- The tables
COLOURS,DISCS,SHAPES,EYES,MOUTHS,ACCESSORIES,ACC_COLOURS,LINE_PAIRSandSIDES. colours()andgeometry(), for renderers of your own.toSvgElement(svg), which parses an SVG string into an element to append, for pages that may not assigninnerHTML.describe(), the accessible name, which every SVG also carries as itsaria-label.- The colour helpers
contrast,distance,luminanceandmix.
See also
| Doc | Why |
|---|---|
| Brand | The mark the Flungies are drawn from, its colours and its type |
| TypeScript SDK | The client for the Flung API |
| Workspace marks | The squared marks the same package draws for workspaces |
| Connect panel | Let people connect their accounts from your page |