pictoguys 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,141 @@
1
+ /** Artist body colors, e.g. "blue", "pink". */
2
+ declare const COLORS: string[];
3
+ /** Body shape variants, "1".."4". */
4
+ declare const SHAPES: string[];
5
+ /** Background keys, "1".."5". */
6
+ declare const BGS: string[];
7
+ /** Eye kinds that have both eyes and matching eyebrows: "single" | "double" | "triple". */
8
+ declare const PREFIXES: string[];
9
+ interface PalEntry {
10
+ name: string;
11
+ light: string;
12
+ dark: string;
13
+ gen: boolean;
14
+ }
15
+ declare function buildPalette(selColors: string[], extra: number): PalEntry[];
16
+ interface Tuning {
17
+ /** feature (outline) lightness, 10..45 */
18
+ featL: number;
19
+ /** feature tint chroma, 0..12 */
20
+ featC: number;
21
+ /** lit pupil lightness, 60..95 */
22
+ litL: number;
23
+ /** pupil chroma, 4..22 */
24
+ pupC: number;
25
+ }
26
+ declare const DEFAULT_TUNING: Tuning;
27
+ interface ComposeArgs {
28
+ light: string;
29
+ dark: string;
30
+ shape: string;
31
+ bg: string;
32
+ eye: string;
33
+ brow: string;
34
+ mode: string;
35
+ tuning: Tuning;
36
+ uid: string;
37
+ background: boolean;
38
+ }
39
+ declare function compose(a: ComposeArgs): string;
40
+ type Mode = 'mono' | 'hetero' | 'triad';
41
+ type EyeKind = 'single' | 'double' | 'triple';
42
+ /**
43
+ * A constraint value. Three ways to use any field:
44
+ * - omit it -> fully random (picked from the seed)
45
+ * - one value -> locked to that value
46
+ * - an array of values -> random, but only from that set (picked from the seed)
47
+ */
48
+ type OneOrMany<T> = T | readonly T[];
49
+ interface CharConfig {
50
+ /** Explicit seed. Omit to derive a stable seed from the other fields. */
51
+ seed?: number | string;
52
+ /**
53
+ * Body color. An anchor name ("blue"), OR a brand hex ("#19c37d") which gets a
54
+ * matching darker shade auto-generated. Array = pick one at random.
55
+ */
56
+ color?: OneOrMany<string>;
57
+ /** Body hue in degrees. Array = pick one. Used when `color` is absent. */
58
+ hue?: OneOrMany<number>;
59
+ /** Explicit gradient stops. Full manual lock, wins over color/hue. */
60
+ light?: string;
61
+ dark?: string;
62
+ /** Body shape, 1..4. Array = pick one. */
63
+ shape?: OneOrMany<number | string>;
64
+ /** Eye kind. Array = pick one. */
65
+ eyes?: OneOrMany<EyeKind>;
66
+ /** Exact eye file key, e.g. "double_2" (implies `eyes`). Array = pick one. */
67
+ eye?: OneOrMany<string>;
68
+ /** Exact eyebrow file key. Array = pick one. */
69
+ brow?: OneOrMany<string>;
70
+ /** Background, 1..5. Array = pick one. */
71
+ bg?: OneOrMany<number | string>;
72
+ /** Eye coloring mode. Array = pick one. Invalid combos fall back to "mono". */
73
+ mode?: OneOrMany<Mode>;
74
+ /** OKLCH fine-tuning overrides. */
75
+ tuning?: Partial<Tuning>;
76
+ /** Generated-hue variety for seeded picks (default 8). */
77
+ genHues?: number;
78
+ }
79
+ type CharInput = number | string | CharConfig;
80
+ /** A fully-resolved, concrete character spec. */
81
+ interface Resolved {
82
+ color: string;
83
+ light: string;
84
+ dark: string;
85
+ shape: string;
86
+ eyes: EyeKind;
87
+ eye: string;
88
+ brow: string;
89
+ bg: string;
90
+ mode: Mode;
91
+ hue: number;
92
+ tuning: Tuning;
93
+ }
94
+ /**
95
+ * Build a body gradient from a single brand color (hex) or a hue (degrees).
96
+ * Handy for inspecting or reusing what `color: '#hex'` would produce.
97
+ */
98
+ declare function gradient(input: string | number): {
99
+ light: string;
100
+ dark: string;
101
+ };
102
+ declare function resolve(input: CharInput): Resolved;
103
+
104
+ type AnimName = 'blink' | 'jump' | 'breath' | 'dance';
105
+ /** name = animation to play, or null to stop. */
106
+ interface AnimEvent {
107
+ name: AnimName | null;
108
+ }
109
+ type Listener = (e: AnimEvent) => void;
110
+ interface SvgOptions {
111
+ /** Add a background tile (default false). Omit/false -> transparent. */
112
+ background?: boolean;
113
+ /** Id prefix to keep filters/gradients unique across SVGs in one document. */
114
+ uid?: string;
115
+ }
116
+ declare class Character {
117
+ /** The fully-resolved, concrete spec. */
118
+ readonly config: Resolved;
119
+ /** Stable id prefix derived from the spec — keeps .svg() deterministic. */
120
+ private readonly _uid;
121
+ private _listeners;
122
+ constructor(input?: CharInput);
123
+ /** Render this character to a standalone SVG string. */
124
+ svg(opts?: SvgOptions): string;
125
+ toString(): string;
126
+ /** Blink once. */
127
+ blink(): this;
128
+ /** Hop once. */
129
+ jump(): this;
130
+ /** Breathe (loops until stop or another animation). */
131
+ breath(): this;
132
+ /** Dance (loops until stop or another animation). */
133
+ dance(): this;
134
+ /** Stop any running animation. */
135
+ stop(): this;
136
+ /** @internal — used by <Picto> to react to animation calls. */
137
+ _subscribe(fn: Listener): () => void;
138
+ private _emit;
139
+ }
140
+
141
+ export { type AnimEvent as A, BGS as B, COLORS as C, DEFAULT_TUNING as D, type EyeKind as E, type Mode as M, type OneOrMany as O, PREFIXES as P, type Resolved as R, SHAPES as S, type Tuning as T, type AnimName as a, type CharConfig as b, type CharInput as c, Character as d, type ComposeArgs as e, type PalEntry as f, type SvgOptions as g, buildPalette as h, compose as i, gradient as j, resolve as r };
@@ -0,0 +1,21 @@
1
+ // src/rng.ts
2
+ function mulberry32(a) {
3
+ return function() {
4
+ a |= 0;
5
+ a = a + 1831565813 | 0;
6
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
7
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
8
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
9
+ };
10
+ }
11
+ function hashSeed(s) {
12
+ if (typeof s === "number") return s >>> 0;
13
+ let h = 2166136261 >>> 0;
14
+ for (let i = 0; i < s.length; i++) {
15
+ h ^= s.charCodeAt(i);
16
+ h = Math.imul(h, 16777619);
17
+ }
18
+ return h >>> 0;
19
+ }
20
+
21
+ export { hashSeed, mulberry32 };
@@ -0,0 +1,109 @@
1
+ import { Character } from './chunk-MHPZBNYL.js';
2
+ import * as React from 'react';
3
+ import { jsx } from 'react/jsx-runtime';
4
+
5
+ var TAU = Math.PI * 2;
6
+ var ANIMS = {
7
+ blink: { loop: false, dur: 280, target: "eyes", f: (p) => `scaleY(${1 - Math.sin(Math.PI * p) * 0.92})` },
8
+ jump: {
9
+ loop: false,
10
+ dur: 640,
11
+ target: "char",
12
+ f: (p) => {
13
+ const y = Math.sin(Math.PI * p);
14
+ return `translateY(${ -28 * y}%) scaleX(${1 - 0.06 * y}) scaleY(${1 + 0.08 * y})`;
15
+ }
16
+ },
17
+ breath: {
18
+ loop: true,
19
+ dur: 2400,
20
+ target: "char",
21
+ f: (p) => {
22
+ const s = Math.sin(TAU * p);
23
+ return `scaleY(${1 + 0.06 * s}) scaleX(${1 - 0.03 * s})`;
24
+ }
25
+ },
26
+ dance: {
27
+ loop: true,
28
+ dur: 720,
29
+ target: "char",
30
+ f: (p) => {
31
+ const s = Math.sin(TAU * p);
32
+ return `translateX(${6 * s}%) translateY(${ -4 * Math.abs(s)}%) rotate(${9 * s}deg)`;
33
+ }
34
+ }
35
+ };
36
+ var reactUseId = React.useId;
37
+ var fallbackId = 0;
38
+ function usePictoUid() {
39
+ const id = reactUseId ? reactUseId() : React.useMemo(() => `r${++fallbackId}`, []);
40
+ return "p" + id.replace(/[^a-zA-Z0-9_-]/g, "") + "_";
41
+ }
42
+ function runAnim(svg, name) {
43
+ const a = ANIMS[name];
44
+ const target = svg.querySelector("." + a.target);
45
+ if (!target) return () => {
46
+ };
47
+ svg.style.overflow = "visible";
48
+ target.style.transformBox = "fill-box";
49
+ target.style.transformOrigin = a.target === "char" ? "50% 100%" : "center";
50
+ target.style.willChange = "transform";
51
+ let raf = 0;
52
+ let start = 0;
53
+ const step = (ts) => {
54
+ if (!target.isConnected) return;
55
+ if (!start) start = ts;
56
+ const el = (ts - start) / a.dur;
57
+ const p = a.loop ? el % 1 : Math.min(1, el);
58
+ target.style.transform = a.f(p);
59
+ if (!a.loop && el >= 1) {
60
+ target.style.transform = "";
61
+ return;
62
+ }
63
+ raf = requestAnimationFrame(step);
64
+ };
65
+ raf = requestAnimationFrame(step);
66
+ return () => {
67
+ cancelAnimationFrame(raf);
68
+ target.style.transform = "";
69
+ };
70
+ }
71
+ var Picto = React.forwardRef(function Picto2({ char, seed, config, size = 120, background = false, animate, style, ...rest }, ref) {
72
+ const uid = usePictoUid();
73
+ const character = React.useMemo(() => {
74
+ if (char) return char;
75
+ if (config != null) return new Character(config);
76
+ return new Character(seed ?? 0);
77
+ }, [char, config, seed]);
78
+ const html = React.useMemo(() => character.svg({ background, uid }), [character, background, uid]);
79
+ const hostRef = React.useRef(null);
80
+ React.useImperativeHandle(ref, () => hostRef.current, []);
81
+ const stopRef = React.useRef(null);
82
+ const play = React.useCallback((name) => {
83
+ stopRef.current?.();
84
+ stopRef.current = null;
85
+ const svg = hostRef.current?.querySelector("svg");
86
+ if (svg && name) stopRef.current = runAnim(svg, name);
87
+ }, []);
88
+ React.useEffect(() => {
89
+ return character._subscribe((e) => play(e.name));
90
+ }, [character, play]);
91
+ React.useEffect(() => {
92
+ play(animate ?? null);
93
+ return () => {
94
+ stopRef.current?.();
95
+ stopRef.current = null;
96
+ };
97
+ }, [animate, html, play]);
98
+ return /* @__PURE__ */ jsx(
99
+ "span",
100
+ {
101
+ ...rest,
102
+ ref: hostRef,
103
+ style: { display: "inline-block", width: size, height: size, lineHeight: 0, overflow: "visible", ...style },
104
+ dangerouslySetInnerHTML: { __html: html }
105
+ }
106
+ );
107
+ });
108
+
109
+ export { Picto };