import type { Animate } from "./animate";
import { type Palette } from "./color";
import type { Expression } from "./expression";
import { type TraitOverrides, type Traits } from "./traits";
export interface BlobatarOptions {
/** Emits width/height attributes. Omit to let CSS size it (the viewBox always scales). */
size?: number;
/** Overrides the default backdrop. `false` renders transparent. */
background?: boolean | "square" | "circle" | "squircle";
/** Overrides specific palette entries. Overridden colors bypass the contrast guarantee. */
palette?: Palette;
/** Locks the hue in degrees, so the name drives shape only. */
hue?: number;
/**
* Locks the tone as a 0–1 position in the swatch set, pale to ink.
*
* The swatches are banded with half-open edges, so an exact `1` sits on the
* top edge rather than under it and falls back to the first swatch: `1`
* renders what `0` renders. Reach for ink with `0.999`.
*/
tone?: number;
/**
* Pins individual traits, so the name drives only what you leave out.
*
* Each value is the 0–1 position the hash would have produced for that key —
* the same units the layout reads, so `{ "eye.gap": 1 }` is the top of
* whatever range `eye.gap` is declared over rather than a measurement in
* viewBox units. Values outside [0, 1) are clamped.
*
* ```ts
* // Always a sun, always wide eyes — colour and everything else per name.
* blobatar(user.email, { traits: { shape: 0.95, "eye.ratio": 0 } })
* ```
*
* Pin every trait and the name stops mattering, which is how you build one
* fixed blobatar: pass any constant string alongside a full map.
*
* The layout still runs in full, so the containment guarantees hold under any
* combination — an eye cluster that would not fit is scaled down by `fit`
* exactly as a hashed one is. That also means an extreme value can land short
* of where you asked; `_layout` reports what it resolved to.
*
* Overlaps with `hue` and `tone`, which state the same two traits in friendlier
* units. Those win: `hue` is degrees, `traits.hue` is a 0–1 position.
*/
traits?: TraitOverrides;
/** Applies NFC + trim + lowercase to the name. Default true. */
normalize?: boolean;
/** Enforces the minimum contrast ratios. Default true. */
contrast?: boolean;
/** Adds a
for screen readers. */
title?: string;
/**
* Idle animation. Off by default.
*
* Requires `import "blobatar/motion.css"`, and requires the blobatar to be
* inline SVG — content inside an `` is an isolated document that hover
* cannot reach. `@blobatar/react` and `@blobatar/vue` switch rendering mode
* for you; the string API is already inline.
*
* **Honored by the framework adapters (`@blobatar/react`, `@blobatar/vue`)
* only.** `blobatar()` returns static markup regardless: a branch on
* `animate` inside it keeps the motion module alive for every caller,
* animating or not, which measured at ~190 B. An animated string API wants
* its own entry point, not a branch here.
*/
animate?: Animate;
/**
* Which pose the blobatar holds. Import one from `blobatar/expression`.
*
* ```ts
* import { happy } from "blobatar/expression";
* blobatar(name, { expression: happy });
* ```
*
* Passed as a value rather than named as a string so that the expressions you
* do not import cost nothing — and so that the core carries no pose code at
* all. Omitting it is `idle`; `idle` is also exported, for when writing it
* reads better than `undefined`.
*
* Set by you and held until you change it — nothing here returns to idle on
* its own, and there are no timers. A burst is `setExpression(happy)` followed
* by your own `setTimeout`, which is four lines in your code and zero bytes in
* this bundle.
*
* Independent of `animate` in both directions. Without `animate` the blobatar
* renders the pose statically, which is what makes this work in the string API
* and under `prefers-reduced-motion`; **the morph between poses requires
* `animate`**, because that is what puts the blobatar in inline SVG where CSS
* can reach it. Setting `expression` never turns `animate` on for you: that
* would silently flip a 400-blobatar grid from 400 ``s to 400 SVG trees.
*
* `idle` emits byte-identical markup to omitting the option.
*/
expression?: Expression;
}
export interface Style {
layout(t: Traits): L;
/**
* `mo` is set when animating, absent otherwise. It is a flag rather than the
* root class it used to be: the root `` is the caller's now, because a
* class inside this string is a class inside `dangerouslySetInnerHTML`. See
* `makeParts`.
*/
render(l: L, p: Palette, mo?: boolean): string;
background: boolean | "square" | "circle" | "squircle";
}
/**
* Applies a static pose, if an expression was asked for.
*
* The animated path deliberately does not come through here: there, the pose is
* eight custom properties and the CSS composes it, so baking it into geometry as
* well would apply it twice.
*/
export declare function posed(l: L, opts: BlobatarOptions, animate?: unknown): {
l: L;
wrap: string;
};
/**
* Applies a pose's tint, if it has one.
*
* Called on the static path only, for the mirror-image of the reason `posed`
* skips the animated one: when animating, the fills have to stay off the markup
* entirely — `parts.inner` may not vary with the expression — so the tinted
* colors go out as `--mo-head`/`--mo-eye` in `vars` instead and the stylesheet
* puts them on. Same two colors, resolved once, serialized into whichever half
* of the split can carry them.
*/
export declare const tinted: (p: Palette, e?: Expression) => Partial>;
export declare function resolve(seed: string, opts: BlobatarOptions): {
t: Traits;
palette: Palette;
};
/** The plate behind the figure, as geometry rather than as markup. */
export interface Backdrop {
d: string;
fill: string;
}
/**
* The backdrop is the style's concern to default, not the renderer's.
*
* Returns the path rather than a serialized `` because the framework
* adapters (React, Vue) have to draw it as a real element: it sits *outside*
* the motion root, so it cannot ride along in the innerHTML string that the
* root `` now owns.
*/
export declare function backdrop(style: Style, opts: BlobatarOptions, p: Palette): Backdrop | undefined;
/**
* What a motion factory hands back: the root class, and the seeded timing to
* put on the outer element.
*
* Passed *in* rather than imported, so `src/animate.ts` never enters a bundle
* that does not animate. That indirection is the entire reason the static path
* still costs what it did before the motion layer existed — a plain
* `if (opts.animate)` here would pull the motion module into every consumer,
* animating or not.
*/
export interface Motion {
cls: string;
vars: Record;
}
/**
* The palette is handed to the factory because a tinting expression needs it:
* the hot pair it mixes toward is derived from the colors the blobatar is actually
* wearing, overrides included. It arrives as an argument rather than being
* looked up so that `src/color.ts`'s `tinted()` stays reachable only from an
* expression value — the same indirection that keeps `animate.ts` out of static
* bundles.
*/
export type MotionFactory = (t: Traits, p: Palette) => Motion;
/** Binds one package major's frozen style into `blobatar(name, opts)`. */
export declare function makeBlobatar(style: Style): (name: string, opts?: BlobatarOptions) => string;
/**
* The blobatar in the pieces a renderer that owns the outer element needs.
*
* Split out from `makeBlobatar` because the framework adapters (React, Vue)
* have to own the `