angular-avatar-player 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 @@
1
+ {"version":3,"file":"angular-avatar-player.mjs","sources":["../../../src/libs/avatar-player/src/lib/geometry/skin-tones.ts","../../../src/libs/avatar-player/src/lib/geometry/hair-colors.ts","../../../src/libs/avatar-player/src/lib/geometry/eye-colors.ts","../../../src/libs/avatar-player/src/lib/geometry/svg-parts/hair-shapes.ts","../../../src/libs/avatar-player/src/lib/geometry/svg-parts/eye-shapes.ts","../../../src/libs/avatar-player/src/lib/geometry/svg-parts/mustache-shapes.ts","../../../src/libs/avatar-player/src/lib/geometry/svg-parts/beard-shapes.ts","../../../src/libs/avatar-player/src/lib/geometry/svg-parts/glasses-shapes.ts","../../../src/libs/avatar-player/src/lib/geometry/svg-parts/profession-layers.ts","../../../src/libs/avatar-player/src/lib/geometry/trait-previews.ts","../../../src/libs/avatar-player/src/lib/geometry/visemes.ts","../../../src/libs/avatar-player/src/lib/geometry/svg-parts/mouth-shapes.ts","../../../src/libs/avatar-player/src/lib/geometry/svg-parts/beard-path.ts","../../../src/libs/avatar-player/src/lib/geometry/avatar-renderer.ts","../../../src/libs/avatar-player/src/lib/geometry/avatar-animator.ts","../../../src/libs/avatar-player/src/lib/geometry/lip-sync-player.ts","../../../src/libs/avatar-player/src/lib/geometry/index.ts","../../../src/libs/avatar-player/src/lib/services/lip-sync.service.ts","../../../src/libs/avatar-player/src/lib/services/avatar-animation.service.ts","../../../src/libs/avatar-player/src/lib/components/svg-avatar/svg-avatar.component.ts","../../../src/libs/avatar-player/src/lib/components/avatar-player/avatar-player.component.ts","../../../src/libs/avatar-player/src/lib/avatar-player.module.ts","../../../src/libs/avatar-player/src/index.ts","../../../src/libs/avatar-player/src/angular-avatar-player.ts"],"sourcesContent":["// libs/avatar-player/src/lib/geometry/skin-tones.ts/skin-tones.ts\nimport { SkinTone } from './avatar.model';\n\n/**\n * - `base` — main face fill\n * - `ear` — secondary tone for ears\n * - `lip` — lips\n * - `shadow` — a THIRD tone reserved for small linework (nose stroke).\n *\n * `shadow` used to be conflated with `ear`, which made the nose vanish\n * on the two darkest tones: `deep` ear is #3C200E against a #4A2912 face,\n * far too close to read as a line. `shadow` is always a clear step darker\n * than `ear` so it stays legible at the 2px stroke width we use.\n *\n * The style guide allows at most two tones per *region*. The nose is its\n * own region, so a dedicated third value is compliant.\n */\nexport const SKIN_TONES: Record<SkinTone, { base: string; ear: string; lip: string; shadow: string }> = {\n light: { base: '#FDDBB4', ear: '#F5C89A', lip: '#E8967A', shadow: '#E0A473' },\n medium: { base: '#F0AC78', ear: '#E09A62', lip: '#C97050', shadow: '#C67C45' },\n tan: { base: '#D4845A', ear: '#C0724A', lip: '#A8533A', shadow: '#A15532' },\n dark: { base: '#8D5524', ear: '#7A4820', lip: '#6B3A18', shadow: '#5E3312' },\n deep: { base: '#4A2912', ear: '#3C200E', lip: '#5C2E18', shadow: '#291505' },\n};\n","// libs/avatar-player/src/lib/geometry/hair-colors.ts/hair-colors.ts\nimport { HairColor } from './avatar.model';\n\nexport const HAIR_COLORS: Record<HairColor, string> = {\n black: '#1A1A2E',\n brown: '#6B3A2A',\n blonde: '#E8C96E',\n red: '#C0392B',\n gray: '#9E9E9E',\n white: '#F0F0F0',\n};\n","// libs/avatar-player/src/lib/geometry/eye-colors.ts/eye-colors.ts\nimport { EyeColor } from './avatar.model';\n\nexport const EYE_COLORS: Record<EyeColor, string> = {\n brown: '#6B3A2A',\n blue: '#2980B9',\n green: '#27AE60',\n gray: '#7F8C8D',\n black: '#1A1A2E',\n};\n","// libs/avatar-player/src/lib/geometry/svg-parts/hair-shapes.ts/svg-parts/hair-shapes.ts\n// 7 haircut styles: short, long, curly, bald, bun, ponytail, mohawk\n// Each provides back (rendered behind face) + front (rendered on top)\n// All shapes: solid fill, no gradients, no strokes > 2.5, flat silhouettes\n// Colors use CSS var: fill=\"var(--hair-color)\"\n// Face ellipse: cx=100 cy=88 rx=52 ry=56 → top of head ~y=32\n\nimport { HaircutStyle } from '../avatar.model';\n\ninterface HairShapeParts {\n back: string;\n front: string;\n}\n\nexport const HAIR_SHAPES: Record<HaircutStyle, HairShapeParts> = {\n\n short: {\n back: `\n <ellipse cx=\"100\" cy=\"62\" rx=\"54\" ry=\"34\" fill=\"var(--hair-color)\"/>`,\n front: `\n <path d=\"M48,75 Q48,32 100,28 Q152,32 152,75 Q140,58 100,54 Q60,58 48,75 Z\"\n fill=\"var(--hair-color)\"/>`,\n },\n\n long: {\n back: `\n <path d=\"M44,70 Q44,28 100,22 Q156,28 156,70 L156,140 Q156,150 148,150\n L52,150 Q44,150 44,140 Z\"\n fill=\"var(--hair-color)\"/>`,\n front: `\n <path d=\"M46,78 Q46,28 100,22 Q154,28 154,78 Q142,54 100,48 Q58,54 46,78 Z\"\n fill=\"var(--hair-color)\"/>\n <path d=\"M46,78 L46,110 Q48,95 52,85 Z\" fill=\"var(--hair-color)\"/>\n <path d=\"M154,78 L154,110 Q152,95 148,85 Z\" fill=\"var(--hair-color)\"/>`,\n },\n\n curly: {\n back: `\n <path d=\"M42,80 Q38,30 100,22 Q162,30 158,80 L160,135 Q155,145 148,140\n Q142,148 132,142 Q122,150 112,142 Q102,148 92,142 Q82,150 72,142\n Q62,148 52,140 Q45,145 40,135 Z\"\n fill=\"var(--hair-color)\"/>`,\n front: `\n <path d=\"M44,82 Q42,30 100,22 Q158,30 156,82\n Q148,60 130,50 Q100,42 70,50 Q52,60 44,82 Z\"\n fill=\"var(--hair-color)\"/>\n <circle cx=\"44\" cy=\"88\" r=\"8\" fill=\"var(--hair-color)\"/>\n <circle cx=\"156\" cy=\"88\" r=\"8\" fill=\"var(--hair-color)\"/>\n <circle cx=\"48\" cy=\"100\" r=\"7\" fill=\"var(--hair-color)\"/>\n <circle cx=\"152\" cy=\"100\" r=\"7\" fill=\"var(--hair-color)\"/>`,\n },\n\n bald: {\n back: '',\n front: '',\n },\n\n bun: {\n back: `\n <circle cx=\"100\" cy=\"24\" r=\"18\" fill=\"var(--hair-color)\"/>\n <ellipse cx=\"100\" cy=\"60\" rx=\"54\" ry=\"32\" fill=\"var(--hair-color)\"/>`,\n front: `\n <path d=\"M48,75 Q48,32 100,28 Q152,32 152,75 Q140,56 100,52 Q60,56 48,75 Z\"\n fill=\"var(--hair-color)\"/>`,\n },\n\n ponytail: {\n back: `\n <ellipse cx=\"100\" cy=\"62\" rx=\"54\" ry=\"34\" fill=\"var(--hair-color)\"/>\n <path d=\"M130,42 Q158,40 162,60 Q166,90 158,130 Q154,142 148,140\n Q152,120 150,80 Q148,50 130,42 Z\"\n fill=\"var(--hair-color)\"/>`,\n front: `\n <path d=\"M48,75 Q48,32 100,28 Q152,32 152,75 Q140,56 100,52 Q60,56 48,75 Z\"\n fill=\"var(--hair-color)\"/>`,\n },\n\n mohawk: {\n back: '',\n front: `\n <!-- Shaved sides – faint stubble -->\n <path d=\"M54,70 Q54,42 100,34 Q146,42 146,70 Q136,56 100,52 Q64,56 54,70 Z\"\n fill=\"var(--hair-color)\" opacity=\"0.12\"/>\n <!-- Mohawk crest – 5 pointed spikes -->\n <path d=\"M84,64 Q84,48 86,40\n L80,20 L89,36\n L86,6 L95,30\n L100,2 L105,30\n L114,6 L111,36\n L120,20 L114,40\n Q116,48 116,64\n Q108,54 100,52 Q92,54 84,64 Z\"\n fill=\"var(--hair-color)\"/>`,\n },\n};\n","// libs/avatar-player/src/lib/geometry/svg-parts/eye-shapes.ts/svg-parts/eye-shapes.ts\n// 4 eye styles: round, almond, wide, narrow\n// Each style provides sclera + iris SVG strings\n// Eyes are positioned at cx=74 (left) and cx=126 (right), cy=88\n// Sclera r≈11, iris r≈9, pupil r≈5 — large eyes per style guide\n// Colors use CSS vars: fill=\"var(--eye-color)\"\n\nimport { EyeStyle } from '../avatar.model';\n\ninterface EyeShapeParts {\n sclera: string;\n iris: string;\n}\n\nexport const EYE_SHAPES: Record<EyeStyle, EyeShapeParts> = {\n round: {\n sclera: `\n <circle cx=\"74\" cy=\"88\" r=\"11\" fill=\"#FFF\"/>\n <circle cx=\"126\" cy=\"88\" r=\"11\" fill=\"#FFF\"/>`,\n iris: `\n <circle cx=\"74\" cy=\"88\" r=\"9\" fill=\"var(--eye-color)\"/>\n <circle cx=\"74\" cy=\"88\" r=\"5\" fill=\"#1A1A2E\"/>\n <circle cx=\"77\" cy=\"85\" r=\"2\" fill=\"#FFF\"/>\n <circle cx=\"126\" cy=\"88\" r=\"9\" fill=\"var(--eye-color)\"/>\n <circle cx=\"126\" cy=\"88\" r=\"5\" fill=\"#1A1A2E\"/>\n <circle cx=\"129\" cy=\"85\" r=\"2\" fill=\"#FFF\"/>`,\n },\n\n almond: {\n sclera: `\n <ellipse cx=\"74\" cy=\"88\" rx=\"12\" ry=\"9\" fill=\"#FFF\"/>\n <ellipse cx=\"126\" cy=\"88\" rx=\"12\" ry=\"9\" fill=\"#FFF\"/>`,\n iris: `\n <circle cx=\"74\" cy=\"88\" r=\"8\" fill=\"var(--eye-color)\"/>\n <circle cx=\"74\" cy=\"88\" r=\"5\" fill=\"#1A1A2E\"/>\n <circle cx=\"77\" cy=\"85\" r=\"2\" fill=\"#FFF\"/>\n <circle cx=\"126\" cy=\"88\" r=\"8\" fill=\"var(--eye-color)\"/>\n <circle cx=\"126\" cy=\"88\" r=\"5\" fill=\"#1A1A2E\"/>\n <circle cx=\"129\" cy=\"85\" r=\"2\" fill=\"#FFF\"/>`,\n },\n\n wide: {\n sclera: `\n <ellipse cx=\"74\" cy=\"88\" rx=\"13\" ry=\"11\" fill=\"#FFF\"/>\n <ellipse cx=\"126\" cy=\"88\" rx=\"13\" ry=\"11\" fill=\"#FFF\"/>`,\n iris: `\n <circle cx=\"74\" cy=\"88\" r=\"9\" fill=\"var(--eye-color)\"/>\n <circle cx=\"74\" cy=\"88\" r=\"5\" fill=\"#1A1A2E\"/>\n <circle cx=\"77\" cy=\"85\" r=\"2.5\" fill=\"#FFF\"/>\n <circle cx=\"126\" cy=\"88\" r=\"9\" fill=\"var(--eye-color)\"/>\n <circle cx=\"126\" cy=\"88\" r=\"5\" fill=\"#1A1A2E\"/>\n <circle cx=\"129\" cy=\"85\" r=\"2.5\" fill=\"#FFF\"/>`,\n },\n\n narrow: {\n sclera: `\n <ellipse cx=\"74\" cy=\"88\" rx=\"12\" ry=\"7\" fill=\"#FFF\"/>\n <ellipse cx=\"126\" cy=\"88\" rx=\"12\" ry=\"7\" fill=\"#FFF\"/>`,\n iris: `\n <circle cx=\"74\" cy=\"88\" r=\"7\" fill=\"var(--eye-color)\"/>\n <circle cx=\"74\" cy=\"88\" r=\"4\" fill=\"#1A1A2E\"/>\n <circle cx=\"77\" cy=\"86\" r=\"1.5\" fill=\"#FFF\"/>\n <circle cx=\"126\" cy=\"88\" r=\"7\" fill=\"var(--eye-color)\"/>\n <circle cx=\"126\" cy=\"88\" r=\"4\" fill=\"#1A1A2E\"/>\n <circle cx=\"129\" cy=\"86\" r=\"1.5\" fill=\"#FFF\"/>`,\n },\n};\n","// libs/avatar-player/src/lib/geometry/svg-parts/mustache-shapes.ts/svg-parts/mustache-shapes.ts\n// 5 mustache styles: none, thin, thick, handlebar, chevron\n// Positioned below nose (~y=112) and above mouth (~y=119)\n// Flat fill only, no gradients, stroke-width ≤ 3\n// Color: fill=\"var(--hair-color)\"\n\nimport { MustacheStyle } from '../avatar.model';\n\nexport const MUSTACHE_SHAPES: Record<MustacheStyle, string> = {\n\n none: '',\n\n thin: `\n <!-- Thin pencil mustache -->\n <path d=\"M82,114 Q91,118 100,115 Q109,118 118,114\"\n stroke=\"var(--hair-color)\" stroke-width=\"3\" fill=\"none\"\n stroke-linecap=\"round\"/>`,\n\n thick: `\n <!-- Thick walrus mustache -->\n <path d=\"M78,111 Q82,112 88,114 Q94,117 100,115\n Q106,117 112,114 Q118,112 122,111\n L124,113 Q120,120 112,122 Q106,123 100,122\n Q94,123 88,122 Q80,120 76,113 Z\"\n fill=\"var(--hair-color)\"/>`,\n\n handlebar: `\n <!-- Handlebar mustache with curled tips -->\n <path d=\"M80,112 Q90,118 100,115 Q110,118 120,112\n L122,114 Q112,121 100,119 Q88,121 78,114 Z\"\n fill=\"var(--hair-color)\"/>\n <!-- Left curl -->\n <path d=\"M80,113 Q74,110 72,114 Q70,118 73,122\"\n stroke=\"var(--hair-color)\" stroke-width=\"3.5\" fill=\"none\"\n stroke-linecap=\"round\"/>\n <!-- Right curl -->\n <path d=\"M120,113 Q126,110 128,114 Q130,118 127,122\"\n stroke=\"var(--hair-color)\" stroke-width=\"3.5\" fill=\"none\"\n stroke-linecap=\"round\"/>`,\n\n chevron: `\n <!-- Chevron mustache — wide inverted V shape -->\n <path d=\"M76,111 L88,112 Q94,118 100,116 Q106,118 112,112\n L124,111 L126,113 Q116,122 100,120\n Q84,122 74,113 Z\"\n fill=\"var(--hair-color)\"/>`,\n};\n","// libs/avatar-player/src/lib/geometry/svg-parts/beard-shapes.ts/svg-parts/beard-shapes.ts\n// 5 beard styles: none, stubble, short, long, goatee\n//\n// The outer edge of both full beards follows the face ellipse\n// (cx=100 cy=88 rx=52 ry=56) rather than a hand-typed curve. An earlier\n// version kept its outer edge near x=49 down to y=118, but the face has\n// already narrowed to x=55 by then, so the beard projected past the jaw\n// as a dark wedge on each side.\n//\n// Face half-width by row, which is what the outer curves are matched to:\n// y=96 -> 51.5 y=110 -> 47.9 y=120 -> 45.0\n// y=130 -> 37.2 y=140 -> 26.0 chin -> 0 (y=144)\n//\n// Flat fill only, no gradients, stroke-width ≤ 2.5\n// Color: fill=\"var(--hair-color)\"\n\nimport { BeardStyle } from '../avatar.model';\n\nexport const BEARD_SHAPES: Record<BeardStyle, string> = {\n\n none: '',\n\n stubble: `\n <!-- 5 o'clock shadow following the jawline. The previous version ran to\n x=76/124 at y=140, where the face half-width is only ~19 (x 81..119),\n so it overhung the jaw by ~5px on each side and had to be cut back by\n the face clip. It now ends at the chin instead of hanging below it. -->\n <path d=\"M72,114 Q70,126 80,136 Q90,143 100,144 Q110,143 120,136 Q130,126 128,114\n Q122,126 100,132 Q78,126 72,114 Z\"\n fill=\"var(--hair-color)\" opacity=\"0.2\"/>`,\n\n short: `\n <!-- Jaw-hugging beard. Outer edge is inset 3px inside the face ellipse so\n it never projects past the jaw; inner edge sits above it, leaving a\n band that thins at the cheeks and thickens at the chin. -->\n <path d=\"M52,96\n C52,108 56,120 64,128\n C72,136 86,143 100,143\n C114,143 128,136 136,128\n C144,120 148,108 148,96\n C146,104 140,114 130,121\n C120,128 110,131 100,131\n C90,131 80,128 70,121\n C60,114 54,104 52,96 Z\"\n fill=\"var(--hair-color)\"/>`,\n\n long: `\n <!-- Same jaw-hugging top, then flowing below the chin to a rounded point. -->\n <path d=\"M52,96\n C52,110 58,126 70,138\n C80,148 90,160 95,172\n C96,176 104,176 105,172\n C110,160 120,148 130,138\n C142,126 148,110 148,96\n C146,104 140,114 130,121\n C120,128 110,131 100,131\n C90,131 80,128 70,121\n C60,114 54,104 52,96 Z\"\n fill=\"var(--hair-color)\"/>`,\n\n goatee: `\n <!-- Soul patch -->\n <ellipse cx=\"100\" cy=\"126\" rx=\"4\" ry=\"2.5\" fill=\"var(--hair-color)\"/>\n <!-- Compact chin goatee -->\n <path d=\"M88,130 Q86,140 92,147 Q96,152 100,153 Q104,152 108,147 Q114,140 112,130\n Q108,138 100,142 Q92,138 88,130 Z\"\n fill=\"var(--hair-color)\"/>`,\n};\n","// libs/avatar-player/src/lib/geometry/svg-parts/glasses-shapes.ts/svg-parts/glasses-shapes.ts\n// 5 glasses styles: none, round, rectangular, sunglasses, monocle\n// Positioned over eyes at cy=88, cx=74 (left), cx=126 (right)\n// Stroke-only where possible, flat fills, no gradients\n// stroke-width ≤ 2.5\n\nimport { GlassesStyle } from '../avatar.model';\n\nexport const GLASSES_SHAPES: Record<GlassesStyle, string> = {\n\n none: '',\n\n round: `\n <circle cx=\"74\" cy=\"88\" r=\"16\" fill=\"none\" stroke=\"#1A1A2E\" stroke-width=\"2\"/>\n <circle cx=\"126\" cy=\"88\" r=\"16\" fill=\"none\" stroke=\"#1A1A2E\" stroke-width=\"2\"/>\n <line x1=\"90\" y1=\"88\" x2=\"110\" y2=\"88\" stroke=\"#1A1A2E\" stroke-width=\"2\"/>\n <line x1=\"58\" y1=\"88\" x2=\"48\" y2=\"86\" stroke=\"#1A1A2E\" stroke-width=\"1.5\"/>\n <line x1=\"142\" y1=\"88\" x2=\"152\" y2=\"86\" stroke=\"#1A1A2E\" stroke-width=\"1.5\"/>`,\n\n rectangular: `\n <rect x=\"58\" y=\"78\" width=\"32\" height=\"20\" rx=\"4\" fill=\"none\"\n stroke=\"#1A1A2E\" stroke-width=\"2\"/>\n <rect x=\"110\" y=\"78\" width=\"32\" height=\"20\" rx=\"4\" fill=\"none\"\n stroke=\"#1A1A2E\" stroke-width=\"2\"/>\n <line x1=\"90\" y1=\"88\" x2=\"110\" y2=\"88\" stroke=\"#1A1A2E\" stroke-width=\"2\"/>\n <line x1=\"58\" y1=\"86\" x2=\"48\" y2=\"84\" stroke=\"#1A1A2E\" stroke-width=\"1.5\"/>\n <line x1=\"142\" y1=\"86\" x2=\"152\" y2=\"84\" stroke=\"#1A1A2E\" stroke-width=\"1.5\"/>`,\n\n sunglasses: `\n <path d=\"M58,82 Q58,76 66,76 L82,76 Q90,76 90,82 L90,94 Q90,100 82,100\n L66,100 Q58,100 58,94 Z\" fill=\"#1A1A2E\"/>\n <path d=\"M110,82 Q110,76 118,76 L134,76 Q142,76 142,82 L142,94 Q142,100 134,100\n L118,100 Q110,100 110,94 Z\" fill=\"#1A1A2E\"/>\n <line x1=\"90\" y1=\"86\" x2=\"110\" y2=\"86\" stroke=\"#1A1A2E\" stroke-width=\"2.5\"/>\n <line x1=\"58\" y1=\"84\" x2=\"48\" y2=\"82\" stroke=\"#1A1A2E\" stroke-width=\"2\"/>\n <line x1=\"142\" y1=\"84\" x2=\"152\" y2=\"82\" stroke=\"#1A1A2E\" stroke-width=\"2\"/>`,\n\n monocle: `\n <circle cx=\"126\" cy=\"88\" r=\"16\" fill=\"none\" stroke=\"#C9A84C\" stroke-width=\"2\"/>\n <line x1=\"126\" y1=\"104\" x2=\"120\" y2=\"140\" stroke=\"#C9A84C\" stroke-width=\"1.5\"/>`,\n};\n","// libs/avatar-player/src/lib/geometry/svg-parts/profession-layers.ts/svg-parts/profession-layers.ts\n// 9 professions: none, doctor, engineer, teacher, chef, police, astronaut, artist, business\n// Each provides: body (shoulders/clothing) + accessory (badge/hat/item on top of head)\n// Canvas: 200×200. Face center (100,88). Neck x=88–112, y=135–157.\n// Body renders as layer 1 (below head). Accessory renders as layer 14 (above hair).\n// Flat fills only, no gradients/filters, stroke-width ≤ 2.5\n\nimport { ProfessionType } from '../avatar.model';\n\ninterface ProfessionParts {\n body: string;\n accessory: string;\n}\n\nexport const PROFESSION_LAYERS: Record<ProfessionType, ProfessionParts> = {\n\n // ─── None: Plain crew-neck T-shirt ──────────────────\n none: {\n body: `\n <path d=\"M30,200 Q30,158 55,150 Q75,143 88,140\n L88,145 Q90,148 100,148 Q110,148 112,145 L112,140\n Q125,143 145,150 Q170,158 170,200 Z\" fill=\"#6B7280\"/>\n <!-- Collar line -->\n <path d=\"M88,145 Q90,150 100,151 Q110,150 112,145\"\n fill=\"none\" stroke=\"#5B6370\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>`,\n accessory: '',\n },\n\n // ─── Doctor: White lab coat over blue scrubs, stethoscope ───\n doctor: {\n body: `\n <!-- Blue scrub underneath -->\n <path d=\"M30,200 Q30,158 55,150 Q75,143 88,140\n L88,145 Q90,148 100,148 Q110,148 112,145 L112,140\n Q125,143 145,150 Q170,158 170,200 Z\" fill=\"#3B82F6\"/>\n <!-- V-neck scrub neckline -->\n <path d=\"M88,140 L96,155 L100,152 L104,155 L112,140\"\n fill=\"#3B82F6\" stroke=\"#2563EB\" stroke-width=\"1\"/>\n <!-- White lab coat — open front, over shoulders -->\n <path d=\"M30,200 Q30,158 55,150 Q70,145 80,142\n L82,155 L90,200 Z\" fill=\"#F8FAFC\"/>\n <path d=\"M170,200 Q170,158 145,150 Q130,145 120,142\n L118,155 L110,200 Z\" fill=\"#F8FAFC\"/>\n <!-- Coat lapels -->\n <path d=\"M80,142 L86,158 L82,155\" fill=\"#E2E8F0\" stroke=\"#CBD5E1\" stroke-width=\"0.5\"/>\n <path d=\"M120,142 L114,158 L118,155\" fill=\"#E2E8F0\" stroke=\"#CBD5E1\" stroke-width=\"0.5\"/>\n <!-- Breast pocket -->\n <rect x=\"34\" y=\"168\" width=\"14\" height=\"10\" rx=\"1\" fill=\"none\" stroke=\"#CBD5E1\" stroke-width=\"1\"/>\n <!-- Stethoscope — tubes start behind neck, just under jaw (~y=142) -->\n <path d=\"M95,142 Q80,148 76,156 Q74,166 84,172 Q94,177 100,175\"\n fill=\"none\" stroke=\"#475569\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n <path d=\"M105,142 Q120,148 124,156 Q126,166 116,172 Q106,177 100,175\"\n fill=\"none\" stroke=\"#475569\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n <!-- Stethoscope chest piece (centered on chest) -->\n <circle cx=\"100\" cy=\"177\" r=\"5\" fill=\"#64748B\" stroke=\"#475569\" stroke-width=\"1.2\"/>\n <circle cx=\"100\" cy=\"177\" r=\"2\" fill=\"#94A3B8\"/>`,\n accessory: ``,\n },\n\n // ─── Engineer: Orange safety vest, hard hat ─────────\n engineer: {\n body: `\n <!-- Blue work shirt -->\n <path d=\"M30,200 Q30,158 55,150 Q75,143 88,140\n L88,145 Q90,148 100,148 Q110,148 112,145 L112,140\n Q125,143 145,150 Q170,158 170,200 Z\" fill=\"#1E3A5F\"/>\n <!-- Orange hi-vis vest -->\n <path d=\"M45,200 Q48,160 65,152 Q78,146 88,144\n L92,200 Z\" fill=\"#F97316\"/>\n <path d=\"M155,200 Q152,160 135,152 Q122,146 112,144\n L108,200 Z\" fill=\"#F97316\"/>\n <!-- Reflective stripes -->\n <line x1=\"55\" y1=\"164\" x2=\"88\" y2=\"152\" stroke=\"#FDE68A\" stroke-width=\"2.5\"/>\n <line x1=\"145\" y1=\"164\" x2=\"112\" y2=\"152\" stroke=\"#FDE68A\" stroke-width=\"2.5\"/>\n <line x1=\"58\" y1=\"178\" x2=\"90\" y2=\"168\" stroke=\"#FDE68A\" stroke-width=\"2.5\"/>\n <line x1=\"142\" y1=\"178\" x2=\"110\" y2=\"168\" stroke=\"#FDE68A\" stroke-width=\"2.5\"/>`,\n accessory: `\n <!-- Hair cover (hides any hair poking through) -->\n <ellipse cx=\"100\" cy=\"52\" rx=\"56\" ry=\"20\" fill=\"#D97706\"/>\n <!-- Hard hat — dome -->\n <path d=\"M50,52 Q50,14 100,8 Q150,14 150,52 Z\" fill=\"#F59E0B\"/>\n <!-- Brim -->\n <rect x=\"44\" y=\"50\" width=\"112\" height=\"9\" rx=\"4\" fill=\"#D97706\"/>\n <!-- Brim shadow line -->\n <line x1=\"48\" y1=\"57\" x2=\"152\" y2=\"57\" stroke=\"#B45309\" stroke-width=\"1\"/>\n <!-- Center ridge -->\n <line x1=\"100\" y1=\"10\" x2=\"100\" y2=\"50\" stroke=\"#EAB308\" stroke-width=\"1\" opacity=\"0.4\"/>`,\n },\n\n // ─── Teacher: Professor with graduation cap, sweater vest, bow tie ─────\n teacher: {\n body: `\n <!-- Light blue dress shirt -->\n <path d=\"M30,200 Q30,158 55,150 Q75,143 88,140\n L88,145 Q90,148 100,148 Q110,148 112,145 L112,140\n Q125,143 145,150 Q170,158 170,200 Z\" fill=\"#DBEAFE\"/>\n <!-- Shirt collar -->\n <path d=\"M88,140 L82,148 L94,152\" fill=\"#BFDBFE\" stroke=\"#93C5FD\" stroke-width=\"0.7\"/>\n <path d=\"M112,140 L118,148 L106,152\" fill=\"#BFDBFE\" stroke=\"#93C5FD\" stroke-width=\"0.7\"/>\n <!-- Dark navy sweater vest -->\n <path d=\"M55,200 Q56,162 65,154 Q78,147 88,144\n L94,152 L100,148 L106,152 L112,144\n Q122,147 135,154 Q144,162 145,200 Z\" fill=\"#1E293B\"/>\n <!-- V-neck of sweater vest -->\n <path d=\"M88,144 L94,152 L100,148 L106,152 L112,144\"\n fill=\"#DBEAFE\" stroke=\"#1E293B\" stroke-width=\"0.8\"/>\n <!-- Sweater vest bottom ribbing -->\n <line x1=\"58\" y1=\"196\" x2=\"142\" y2=\"196\" stroke=\"#334155\" stroke-width=\"1\"/>\n <line x1=\"57\" y1=\"198\" x2=\"143\" y2=\"198\" stroke=\"#334155\" stroke-width=\"0.5\"/>\n <!-- Sleeves (shirt showing) -->\n <path d=\"M30,200 Q30,158 55,150 Q62,148 65,154\n L58,200 Z\" fill=\"#DBEAFE\"/>\n <path d=\"M170,200 Q170,158 145,150 Q138,148 135,154\n L142,200 Z\" fill=\"#DBEAFE\"/>\n <!-- Bow tie -->\n <path d=\"M93,147 L88,143 L88,151 Z\" fill=\"#991B1B\"/>\n <path d=\"M107,147 L112,143 L112,151 Z\" fill=\"#991B1B\"/>\n <circle cx=\"100\" cy=\"147\" r=\"2.5\" fill=\"#B91C1C\" stroke=\"#7F1D1D\" stroke-width=\"0.5\"/>`,\n accessory: `\n <!-- Graduation mortarboard cap -->\n <!-- Cap crown (skull cap — covers top of head generously) -->\n <path d=\"M46,56 Q44,38 62,30 Q80,22 100,22 Q120,22 138,30 Q156,38 154,56\n Q134,62 100,62 Q66,62 46,56 Z\" fill=\"#1E293B\" stroke=\"#0F172A\" stroke-width=\"0.6\"/>\n <!-- Flat board — large diamond shape for 3D perspective -->\n <polygon points=\"100,8 168,34 100,54 32,34\" fill=\"#1E293B\" stroke=\"#0F172A\" stroke-width=\"0.8\"/>\n <!-- Board top highlight for depth -->\n <polygon points=\"100,11 164,34 100,52 36,34\" fill=\"#334155\" opacity=\"0.3\"/>\n <!-- Button on top center -->\n <circle cx=\"100\" cy=\"32\" r=\"3.5\" fill=\"#475569\" stroke=\"#0F172A\" stroke-width=\"0.8\"/>\n <!-- Tassel string from center button draping to right -->\n <path d=\"M100,32 Q124,24 144,32 Q158,44 160,62\" fill=\"none\" stroke=\"#EAB308\" stroke-width=\"2.2\" stroke-linecap=\"round\"/>\n <!-- Tassel end fringe -->\n <line x1=\"158\" y1=\"60\" x2=\"156\" y2=\"70\" stroke=\"#EAB308\" stroke-width=\"1.2\"/>\n <line x1=\"160\" y1=\"62\" x2=\"159\" y2=\"72\" stroke=\"#EAB308\" stroke-width=\"1.2\"/>\n <line x1=\"162\" y1=\"61\" x2=\"162\" y2=\"71\" stroke=\"#EAB308\" stroke-width=\"1.2\"/>\n <line x1=\"159\" y1=\"71\" x2=\"159\" y2=\"77\" stroke=\"#CA8A04\" stroke-width=\"1.5\"/>`,\n },\n\n // ─── Chef: Double-breasted white jacket, tall toque ─\n chef: {\n body: `\n <!-- White chef jacket -->\n <path d=\"M30,200 Q30,158 55,150 Q75,143 88,140\n L88,145 Q90,148 100,148 Q110,148 112,145 L112,140\n Q125,143 145,150 Q170,158 170,200 Z\" fill=\"#FEFEFE\"/>\n <!-- Mandarin collar -->\n <rect x=\"86\" y=\"138\" width=\"28\" height=\"8\" rx=\"3\" fill=\"#F1F5F9\" stroke=\"#E2E8F0\" stroke-width=\"1\"/>\n <!-- Double-breasted buttons (left row) -->\n <circle cx=\"88\" cy=\"162\" r=\"2\" fill=\"#E2E8F0\" stroke=\"#CBD5E1\" stroke-width=\"0.8\"/>\n <circle cx=\"88\" cy=\"174\" r=\"2\" fill=\"#E2E8F0\" stroke=\"#CBD5E1\" stroke-width=\"0.8\"/>\n <circle cx=\"88\" cy=\"186\" r=\"2\" fill=\"#E2E8F0\" stroke=\"#CBD5E1\" stroke-width=\"0.8\"/>\n <!-- Right row -->\n <circle cx=\"96\" cy=\"162\" r=\"2\" fill=\"#E2E8F0\" stroke=\"#CBD5E1\" stroke-width=\"0.8\"/>\n <circle cx=\"96\" cy=\"174\" r=\"2\" fill=\"#E2E8F0\" stroke=\"#CBD5E1\" stroke-width=\"0.8\"/>\n <circle cx=\"96\" cy=\"186\" r=\"2\" fill=\"#E2E8F0\" stroke=\"#CBD5E1\" stroke-width=\"0.8\"/>\n <!-- Center front seam -->\n <line x1=\"100\" y1=\"146\" x2=\"100\" y2=\"200\" stroke=\"#E2E8F0\" stroke-width=\"0.8\"/>`,\n accessory: `\n <!-- Chef toque blanche — puffy hat, sized to sit inside the 0–200 canvas.\n The previous version ran to y=-48, so most of it was off-canvas and\n read as a shapeless blob. The face top is y=32, so the hat occupies\n y≈2…59: high enough to look tall, never past the top edge. -->\n <!-- Band at the forehead — spans the head width -->\n <rect x=\"48\" y=\"44\" width=\"104\" height=\"15\" rx=\"4\" fill=\"#FEFEFE\" stroke=\"#E2E8F0\" stroke-width=\"1.2\"/>\n <!-- Puffy toque body -->\n <path d=\"M56,46 C52,38 50,28 52,20\n C54,11 62,6 72,5\n C80,4 88,4 100,4\n C112,4 120,4 128,5\n C138,6 146,11 148,20\n C150,28 148,38 144,46 Z\"\n fill=\"#FEFEFE\" stroke=\"#E2E8F0\" stroke-width=\"1\"/>\n <!-- Crown puffs — overlapping rounded bumps, all within the canvas -->\n <ellipse cx=\"76\" cy=\"18\" rx=\"20\" ry=\"12\" fill=\"#FEFEFE\"/>\n <ellipse cx=\"124\" cy=\"18\" rx=\"20\" ry=\"12\" fill=\"#FEFEFE\"/>\n <ellipse cx=\"100\" cy=\"14\" rx=\"24\" ry=\"13\" fill=\"#FEFEFE\"/>\n <!-- Pleat lines -->\n <path d=\"M76,44 Q70,26 76,8\" stroke=\"#F0F0F0\" stroke-width=\"0.7\" fill=\"none\"/>\n <path d=\"M100,44 L100,4\" stroke=\"#F0F0F0\" stroke-width=\"0.7\" fill=\"none\"/>\n <path d=\"M124,44 Q130,26 124,8\" stroke=\"#F0F0F0\" stroke-width=\"0.7\" fill=\"none\"/>`,\n },\n\n // ─── Police: Navy uniform, cap with badge ───────────\n police: {\n body: `\n <!-- Navy uniform -->\n <path d=\"M30,200 Q30,158 55,150 Q75,143 88,140\n L88,145 Q90,148 100,148 Q110,148 112,145 L112,140\n Q125,143 145,150 Q170,158 170,200 Z\" fill=\"#1E3A8A\"/>\n <!-- Collar -->\n <path d=\"M86,140 L82,148 L92,152 L100,148 L108,152 L118,148 L114,140\"\n fill=\"#1E40AF\" stroke=\"#1E3A8A\" stroke-width=\"0.8\"/>\n <!-- Shoulder epaulettes -->\n <rect x=\"52\" y=\"148\" width=\"18\" height=\"5\" rx=\"2\" fill=\"#1E40AF\" stroke=\"#2563EB\" stroke-width=\"0.8\"/>\n <rect x=\"130\" y=\"148\" width=\"18\" height=\"5\" rx=\"2\" fill=\"#1E40AF\" stroke=\"#2563EB\" stroke-width=\"0.8\"/>\n <!-- Badge (left breast) -->\n <g transform=\"translate(82,168)\">\n <polygon points=\"0,-6 1.5,-2 5.5,-2 2.5,0.5 3.5,4.5 0,2 -3.5,4.5 -2.5,0.5 -5.5,-2 -1.5,-2\"\n fill=\"#EAB308\" stroke=\"#CA8A04\" stroke-width=\"0.5\"/>\n </g>\n <!-- Buttons down front -->\n <circle cx=\"100\" cy=\"175\" r=\"1.5\" fill=\"#D4D4D8\"/>\n <circle cx=\"100\" cy=\"185\" r=\"1.5\" fill=\"#D4D4D8\"/>\n <circle cx=\"100\" cy=\"195\" r=\"1.5\" fill=\"#D4D4D8\"/>\n <!-- Breast pockets -->\n <path d=\"M76,162 L76,172 L88,172\" fill=\"none\" stroke=\"#1E40AF\" stroke-width=\"1\"/>\n <path d=\"M124,162 L124,172 L112,172\" fill=\"none\" stroke=\"#1E40AF\" stroke-width=\"1\"/>`,\n accessory: `\n <!-- Hair cover -->\n <ellipse cx=\"100\" cy=\"54\" rx=\"56\" ry=\"20\" fill=\"#1E3A8A\"/>\n <!-- Police cap — crown -->\n <path d=\"M50,54 L50,40 Q50,20 100,16 Q150,20 150,40 L150,54 Z\" fill=\"#1E3A8A\"/>\n <!-- Cap band -->\n <rect x=\"50\" y=\"46\" width=\"100\" height=\"8\" rx=\"0\" fill=\"#1E40AF\"/>\n <!-- Cap badge -->\n <g transform=\"translate(100,44)\">\n <polygon points=\"0,-6 2,-2 6,-2 3,1 4,5 0,3 -4,5 -3,1 -6,-2 -2,-2\"\n fill=\"#EAB308\"/>\n </g>\n <!-- Cap brim (visor) -->\n <path d=\"M46,54 Q46,62 100,66 Q154,62 154,54 Z\" fill=\"#111827\"/>`,\n },\n\n // ─── Astronaut: Silver/white space suit, glass helmet ──\n astronaut: {\n body: `\n <!-- Neck cover — hides skin-tone neck behind collar -->\n <rect x=\"82\" y=\"130\" width=\"36\" height=\"20\" rx=\"6\" fill=\"#CFD8DC\"/>\n <!-- Bulky space suit -->\n <path d=\"M22,200 Q22,155 50,147 Q72,140 88,138\n L88,142 Q92,146 100,146 Q108,146 112,142 L112,138\n Q128,140 150,147 Q178,155 178,200 Z\" fill=\"#CFD8DC\"/>\n <!-- Suit seam lines -->\n <line x1=\"60\" y1=\"148\" x2=\"52\" y2=\"200\" stroke=\"#B0BEC5\" stroke-width=\"1.2\"/>\n <line x1=\"140\" y1=\"148\" x2=\"148\" y2=\"200\" stroke=\"#B0BEC5\" stroke-width=\"1.2\"/>\n <!-- Chest plate -->\n <rect x=\"76\" y=\"148\" width=\"48\" height=\"34\" rx=\"8\" fill=\"#B0BEC5\" stroke=\"#90A4AE\" stroke-width=\"1.2\"/>\n <!-- Status lights on chest -->\n <circle cx=\"90\" cy=\"157\" r=\"2.5\" fill=\"#4ADE80\"/>\n <circle cx=\"110\" cy=\"157\" r=\"2.5\" fill=\"#60A5FA\"/>\n <!-- Mission patch (left shoulder) -->\n <path d=\"M42,153 L50,150 L58,153 L50,162 Z\" fill=\"#1E3A5F\" stroke=\"#BBB\" stroke-width=\"0.5\"/>\n <path d=\"M46,154 L50,152 L54,154\" fill=\"none\" stroke=\"#60A5FA\" stroke-width=\"0.8\"/>\n <!-- Agency logo patch (right shoulder) -->\n <circle cx=\"155\" cy=\"159\" r=\"7\" fill=\"#1E3A5F\" stroke=\"#BBB\" stroke-width=\"0.5\"/>\n <circle cx=\"155\" cy=\"159\" r=\"4\" fill=\"none\" stroke=\"#60A5FA\" stroke-width=\"0.8\"/>\n <!-- Collar ring -->\n <ellipse cx=\"100\" cy=\"141\" rx=\"30\" ry=\"9\" fill=\"#607D8B\"/>\n <ellipse cx=\"100\" cy=\"139\" rx=\"28\" ry=\"7\" fill=\"#78909C\"/>\n <ellipse cx=\"100\" cy=\"137\" rx=\"24\" ry=\"5\" fill=\"#90A4AE\"/>`,\n accessory: `\n <!-- Helmet dome -->\n <ellipse cx=\"100\" cy=\"78\" rx=\"60\" ry=\"64\" fill=\"#CFD8DC\" opacity=\"0.30\"/>\n <!-- Helmet outer rim -->\n <ellipse cx=\"100\" cy=\"78\" rx=\"60\" ry=\"64\" fill=\"none\"\n stroke=\"#78909C\" stroke-width=\"4\"/>\n <!-- Visor glass reflection -->\n <path d=\"M64,52 Q74,36 90,40\" fill=\"none\" stroke=\"#FFF\" stroke-width=\"2.5\" stroke-linecap=\"round\" opacity=\"0.40\"/>\n <!-- Side bolts -->\n <circle cx=\"42\" cy=\"88\" r=\"4\" fill=\"#546E7A\" stroke=\"#455A64\" stroke-width=\"1\"/>\n <circle cx=\"158\" cy=\"88\" r=\"4\" fill=\"#546E7A\" stroke=\"#455A64\" stroke-width=\"1\"/>\n <!-- Antenna. The helmet crown is at y=14, leaving ~12px of headroom\n inside the 200×200 canvas — keep the tip's outer edge at y >= 2. -->\n <line x1=\"78\" y1=\"16\" x2=\"78\" y2=\"8\" stroke=\"#78909C\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n <circle cx=\"78\" cy=\"5\" r=\"3\" fill=\"#EF4444\" stroke=\"#DC2626\" stroke-width=\"0.8\"/>`,\n },\n\n // ─── Artist: Striped shirt, apron, beret + brush ─────\n artist: {\n body: `\n <!-- Dark striped undershirt -->\n <path d=\"M30,200 Q30,158 55,150 Q75,143 88,140\n L88,145 Q90,148 100,148 Q110,148 112,145 L112,140\n Q125,143 145,150 Q170,158 170,200 Z\" fill=\"#333A45\"/>\n <!-- Horizontal stripes on shirt -->\n <line x1=\"42\" y1=\"168\" x2=\"158\" y2=\"168\" stroke=\"#4E5563\" stroke-width=\"2\"/>\n <line x1=\"36\" y1=\"180\" x2=\"164\" y2=\"180\" stroke=\"#4E5563\" stroke-width=\"2\"/>\n <line x1=\"34\" y1=\"192\" x2=\"166\" y2=\"192\" stroke=\"#4E5563\" stroke-width=\"2\"/>\n <!-- Canvas-colored apron over shirt -->\n <path d=\"M72,152 L72,200 L128,200 L128,152\n Q128,148 120,148 L100,148 L80,148 Q72,148 72,152 Z\" fill=\"#E8DCCA\"/>\n <!-- Apron neck strap (V-shape) -->\n <path d=\"M88,140 L80,148\" stroke=\"#D4C4A8\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n <path d=\"M112,140 L120,148\" stroke=\"#D4C4A8\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n <!-- Apron pocket -->\n <rect x=\"84\" y=\"170\" width=\"32\" height=\"16\" rx=\"2\" fill=\"none\" stroke=\"#C4B498\" stroke-width=\"1\"/>\n <!-- Paintbrush in pocket -->\n <line x1=\"92\" y1=\"163\" x2=\"92\" y2=\"172\" stroke=\"#8B5E3C\" stroke-width=\"2\" stroke-linecap=\"round\"/>\n <rect x=\"90\" y=\"160\" width=\"4\" height=\"5\" rx=\"1\" fill=\"#3B82F6\"/>\n <!-- Paint smudge on apron — natural drip shapes -->\n <path d=\"M110,175 Q112,173 114,176 Q113,178 110,177 Z\" fill=\"#EF4444\" opacity=\"0.6\"/>\n <path d=\"M78,188 Q80,186 83,188 Q82,191 79,190 Z\" fill=\"#7C3AED\" opacity=\"0.5\"/>\n <circle cx=\"120\" cy=\"190\" r=\"2\" fill=\"#FBBF24\" opacity=\"0.5\"/>`,\n accessory: `\n <!-- Hair cover -->\n <ellipse cx=\"100\" cy=\"54\" rx=\"56\" ry=\"20\" fill=\"#DC2626\"/>\n <!-- Beret — classic flat shape sitting on head -->\n <path d=\"M50,54 Q50,36 70,28 Q86,22 100,22 Q120,22 140,30 Q156,38 156,54 Z\" fill=\"#DC2626\"/>\n <!-- Beret puff — floppy top drooping to the right -->\n <path d=\"M100,22 Q120,14 145,18 Q164,24 162,38 Q160,48 156,54\n Q152,40 130,30 Q110,22 100,22 Z\" fill=\"#EF4444\"/>\n <!-- Band (rim that hugs the forehead) -->\n <path d=\"M50,54 Q50,60 100,62 Q150,60 156,54\" fill=\"none\"\n stroke=\"#B91C1C\" stroke-width=\"2.5\"/>\n <!-- Nub on top -->\n <circle cx=\"138\" cy=\"20\" r=\"3.5\" fill=\"#B91C1C\"/>`,\n },\n\n // ─── Business: Black suit, white shirt, red tie ─────\n business: {\n body: `\n <!-- White dress shirt -->\n <path d=\"M30,200 Q30,158 55,150 Q75,143 88,140\n L88,145 Q90,148 100,148 Q110,148 112,145 L112,140\n Q125,143 145,150 Q170,158 170,200 Z\" fill=\"#F8FAFC\"/>\n <!-- Shirt collar -->\n <path d=\"M86,140 L80,150 L94,150 Z\" fill=\"#F1F5F9\" stroke=\"#E2E8F0\" stroke-width=\"0.5\"/>\n <path d=\"M114,140 L120,150 L106,150 Z\" fill=\"#F1F5F9\" stroke=\"#E2E8F0\" stroke-width=\"0.5\"/>\n <!-- Suit jacket -->\n <path d=\"M30,200 Q30,158 55,150 Q66,146 76,143\n L82,152 L88,200 Z\" fill=\"#333A45\"/>\n <path d=\"M170,200 Q170,158 145,150 Q134,146 124,143\n L118,152 L112,200 Z\" fill=\"#333A45\"/>\n <!-- Lapels (V-shape) -->\n <path d=\"M76,143 L86,158 L82,152\" fill=\"#3E4652\" stroke=\"#4E5563\" stroke-width=\"0.5\"/>\n <path d=\"M124,143 L114,158 L118,152\" fill=\"#3E4652\" stroke=\"#4E5563\" stroke-width=\"0.5\"/>\n <!-- Suit button -->\n <circle cx=\"88\" cy=\"175\" r=\"2\" fill=\"#3F3F46\"/>\n <circle cx=\"112\" cy=\"175\" r=\"2\" fill=\"#3F3F46\"/>\n <!-- Necktie -->\n <path d=\"M96,148 L100,150 L104,148 L100,146 Z\" fill=\"#DC2626\"/>\n <path d=\"M97,150 L100,152 L103,150 L103.5,150\" fill=\"#DC2626\"/>\n <polygon points=\"100,152 95,200 105,200\" fill=\"#DC2626\"/>\n <!-- Tie knot -->\n <path d=\"M97,148 L100,151 L103,148\" fill=\"#B91C1C\"/>\n <!-- Tie stripe detail -->\n <line x1=\"97\" y1=\"165\" x2=\"103\" y2=\"162\" stroke=\"#991B1B\" stroke-width=\"1\"/>\n <line x1=\"97\" y1=\"175\" x2=\"103\" y2=\"172\" stroke=\"#991B1B\" stroke-width=\"1\"/>\n <line x1=\"97\" y1=\"185\" x2=\"103\" y2=\"182\" stroke=\"#991B1B\" stroke-width=\"1\"/>`,\n accessory: ``,\n },\n};\n","// libs/avatar-player/src/lib/geometry/trait-previews.ts/trait-previews.ts\n//\n// Small SVG thumbnails for the creator's pickers.\n//\n// The UI was showing an emoji per option while TraitOption already had an\n// unused `svgPreview` field. Emoji neither match the artwork nor tell you what\n// \"handlebar\" looks like. These build the real shape from the same part data\n// the renderer uses, so a preview can never drift from the result.\n//\n// Everything is drawn with literal hex colours, not CSS custom properties:\n// the thumbnails are injected with innerHTML and must stand on their own.\nimport { SKIN_TONES } from './skin-tones';\nimport { HAIR_COLORS } from './hair-colors';\nimport { EYE_COLORS } from './eye-colors';\nimport { HAIR_SHAPES } from './svg-parts/hair-shapes';\nimport { EYE_SHAPES } from './svg-parts/eye-shapes';\nimport { MUSTACHE_SHAPES } from './svg-parts/mustache-shapes';\nimport { BEARD_SHAPES } from './svg-parts/beard-shapes';\nimport { GLASSES_SHAPES } from './svg-parts/glasses-shapes';\nimport { PROFESSION_LAYERS } from './svg-parts/profession-layers';\nimport type {\n BeardStyle,\n EyeStyle,\n Gender,\n GlassesStyle,\n HaircutStyle,\n MustacheStyle,\n ProfessionType,\n} from './avatar.model';\n\nexport type PreviewKind =\n | 'gender'\n | 'haircut'\n | 'eyeStyle'\n | 'mustache'\n | 'beard'\n | 'glasses'\n | 'profession';\n\nconst SKIN = SKIN_TONES.medium.base;\nconst SKIN_DARK = SKIN_TONES.medium.ear;\nconst HAIR = HAIR_COLORS.brown;\nconst EYE = EYE_COLORS.brown;\n\n/**\n * Part data references `var(--hair-color)` and friends. Substitute literals so\n * a thumbnail renders correctly outside the avatar's own stylesheet.\n */\nfunction inlineVars(svg: string): string {\n return svg\n .replace(/var\\(--hair-color\\)/g, HAIR)\n .replace(/var\\(--eye-color\\)/g, EYE)\n .replace(/var\\(--skin-base\\)/g, SKIN)\n .replace(/var\\(--skin-ear\\)/g, SKIN_DARK)\n .replace(/var\\(--skin-shadow\\)/g, SKIN_DARK)\n .replace(/var\\(--lip-color\\)/g, SKIN_TONES.medium.lip);\n}\n\n/** Crop window around the head. Parts live in 0–200 space centred on x=100. */\nconst VIEWBOX = '32 18 136 136';\n\nfunction wrap(inner: string, viewBox = VIEWBOX): string {\n return (\n `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"${viewBox}\" ` +\n `preserveAspectRatio=\"xMidYMid meet\">${inner}</svg>`\n );\n}\n\nconst head = (rx = 52, ry = 56, cy = 88) =>\n `<ellipse cx=\"100\" cy=\"${cy}\" rx=\"${rx}\" ry=\"${ry}\" fill=\"${SKIN}\"/>`;\n\nconst ears = () =>\n `<ellipse cx=\"48\" cy=\"90\" rx=\"8\" ry=\"10\" fill=\"${SKIN_DARK}\"/>` +\n `<ellipse cx=\"152\" cy=\"90\" rx=\"8\" ry=\"10\" fill=\"${SKIN_DARK}\"/>`;\n\nconst eyes = () => {\n const e = EYE_SHAPES.round;\n return `<g>${inlineVars(e.sclera)}</g><g>${inlineVars(e.iris)}</g>`;\n};\n\nconst brows = () =>\n `<rect x=\"63\" y=\"72\" width=\"22\" height=\"5\" rx=\"3\" fill=\"${HAIR}\"/>` +\n `<rect x=\"115\" y=\"72\" width=\"22\" height=\"5\" rx=\"3\" fill=\"${HAIR}\"/>`;\n\nconst noseMouth = () =>\n `<path d=\"M97,105 Q100,112 103,105\" stroke=\"${SKIN_DARK}\" stroke-width=\"2\" fill=\"none\" stroke-linecap=\"round\"/>` +\n `<path d=\"M84,120 Q100,132 116,120\" stroke=\"${SKIN_TONES.medium.lip}\" stroke-width=\"2.5\" fill=\"none\" stroke-linecap=\"round\"/>`;\n\n/** Neutral bust used behind profession thumbnails. */\nconst bust = () =>\n `<path d=\"M30,200 Q30,150 100,150 Q170,150 170,200 Z\" fill=\"#8A93A5\"/>`;\n\nexport function buildTraitPreview(kind: PreviewKind, value: string): string {\n switch (kind) {\n case 'gender': {\n const woman = value === 'woman';\n const face = woman\n ? `<ellipse cx=\"100\" cy=\"86\" rx=\"49\" ry=\"55\" fill=\"${SKIN}\"/>` +\n `<g class=\"lashes\">${[0, 1, 2, 3, 4, 5, 6, 7]\n .map((i) => {\n const xs = [65, 70, 76, 82, 117, 122, 128, 134][i];\n return `<path d=\"M${xs},78 l-3,-4\" stroke=\"${HAIR}\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>`;\n })\n .join('')}</g>`\n : head();\n return wrap(ears() + face + eyes() + brows() + noseMouth());\n }\n\n case 'haircut': {\n const h = HAIR_SHAPES[value as HaircutStyle];\n if (!h) return wrap(head());\n return wrap(ears() + `<g>${inlineVars(h.back)}</g>` + head() + `<g>${inlineVars(h.front)}</g>`);\n }\n\n case 'eyeStyle': {\n const e = EYE_SHAPES[value as EyeStyle];\n if (!e) return wrap(head());\n return wrap(\n head() + `<g>${inlineVars(e.sclera)}</g><g>${inlineVars(e.iris)}</g>`,\n );\n }\n\n case 'mustache': {\n const m = MUSTACHE_SHAPES[value as MustacheStyle];\n return wrap(ears() + head() + eyes() + brows() + noseMouth() + (m ? `<g>${inlineVars(m)}</g>` : ''));\n }\n\n case 'beard': {\n const b = BEARD_SHAPES[value as BeardStyle];\n return wrap(ears() + head() + eyes() + brows() + noseMouth() + (b ? `<g>${inlineVars(b)}</g>` : ''));\n }\n\n case 'glasses': {\n const g = GLASSES_SHAPES[value as GlassesStyle];\n return wrap(ears() + head() + eyes() + brows() + noseMouth() + (g ? `<g>${inlineVars(g)}</g>` : ''));\n }\n\n case 'profession': {\n const p = PROFESSION_LAYERS[value as ProfessionType];\n const body = p?.body ? inlineVars(p.body) : bust();\n const acc = p?.accessory ? inlineVars(p.accessory) : '';\n // Profession art is drawn on the full 0–200 canvas, so use the whole\n // thing rather than the head crop the face previews use.\n return wrap(body + ears() + head() + eyes() + brows() + noseMouth() + acc, '0 0 200 200');\n }\n\n default:\n return wrap(head());\n }\n}\n","// libs/avatar-player/src/lib/geometry/visemes.ts/visemes.ts\n//\n// Pure text -> viseme mapping, shared by the runtime lip-sync service and the\n// offline generator that bakes an animated mouth into an exported SVG.\n//\n// Deliberately not a real phoneme model: every unmapped consonant falls back\n// to the \"spread\" viseme, so the result reads as \"mouth is moving\" rather\n// than accurate speech. Do not present it as phonetics.\n\n/** Silence. */\nexport const VISEME_SILENCE = 0;\n/** A / I — wide open */\nexport const VISEME_WIDE = 1;\n/** O / U — rounded */\nexport const VISEME_ROUND = 2;\n/** E — spread */\nexport const VISEME_SPREAD = 3;\n/** M / B / P — pressed */\nexport const VISEME_PRESSED = 4;\n/** F / V — teeth on lip */\nexport const VISEME_TEETH = 5;\n\n/** How long each viseme is held, in ms. */\nexport const MS_PER_VISEME = 80;\n\n/**\n * Map text characters to viseme ids 0–5.\n * 0=silence, 1=A/I wide, 2=O/U round, 3=E spread, 4=M/B/P pressed, 5=F/V teeth\n */\nexport function textToVisemes(text: string): number[] {\n return text\n .toLowerCase()\n .split('')\n .map((char) => {\n if ('ai'.includes(char)) return VISEME_WIDE;\n if ('ou'.includes(char)) return VISEME_ROUND;\n if ('e'.includes(char)) return VISEME_SPREAD;\n if ('mbp'.includes(char)) return VISEME_PRESSED;\n if ('fv'.includes(char)) return VISEME_TEETH;\n if (char === ' ') return VISEME_SILENCE;\n return VISEME_SPREAD; // default consonant\n });\n}\n","// libs/avatar-player/src/lib/geometry/svg-parts/mouth-shapes.ts/svg-parts/mouth-shapes.ts\n// 6 viseme paths for lip-sync animation\n// All paths fit within the face area (x: 82–118, y: 118–136)\n// stroke=\"var(--lip-color)\" stroke-width=\"3\" fill=\"none\" stroke-linecap=\"round\"\n\nexport const MOUTH_SHAPES: Record<number, string> = {\n 0: 'M84,120 Q100,132 116,120', // silence — gentle smile\n 1: 'M84,118 Q100,136 116,118', // A/I — wide open\n 2: 'M90,118 Q100,130 110,118', // O/U — round\n 3: 'M82,122 Q100,128 118,122', // E — spread\n 4: 'M86,122 Q100,124 114,122', // M/B/P — pressed\n 5: 'M84,120 Q92,126 100,122 Q108,126 116,120', // F/V — teeth-lip\n};\n","// libs/avatar-player/src/lib/geometry/svg-parts/beard-path.ts/svg-parts/beard-path.ts\n//\n// Facial hair is authored as artwork, then FITTED to the head at render\n// time.\n//\n// The shapes in beard-shapes.ts / mustache-shapes.ts were drawn against the\n// man's face (cx=100 cy=88 rx=52 ry=56). A woman's head is a different\n// ellipse, so the same path either juts past her jaw or leaves a gap.\n//\n// The first three attempts generated new geometry instead, which produced\n// uniform bands that lost the hand-drawn character. This keeps the original\n// silhouettes and rescales them by the ratio between the two faces, so:\n// - a man is unchanged, exactly, by construction (the ratios are 1)\n// - a woman gets the same shape at the same relative position on a\n// proportionally smaller head\n//\n// The rescale is applied to the NUMBERS in the path, never as a CSS\n// transform. An SVG transform attribute would be resolved against the\n// element's bounding box under `transform-box: fill-box` and fling the\n// beard across the face.\n\nimport type { BeardStyle, Gender } from '../avatar.model';\n\nexport interface FaceGeometry {\n cx: number;\n cy: number;\n rx: number;\n ry: number;\n}\n\n/** The two head shapes the renderer supports. */\nexport const MAN_FACE: FaceGeometry = { cx: 100, cy: 88, rx: 52, ry: 56 };\nexport const WOMAN_FACE: FaceGeometry = { cx: 100, cy: 86, rx: 49, ry: 55 };\n\nexport function faceFor(gender: Gender): FaceGeometry {\n return gender === 'woman' ? WOMAN_FACE : MAN_FACE;\n}\n\n/** Half-width of the head at a given y. Returns 0 at or past the chin. */\nexport function faceHalfWidthAt(face: FaceGeometry, y: number): number {\n const t = (y - face.cy) / face.ry;\n if (t <= -1 || t >= 1) return 0;\n return face.rx * Math.sqrt(1 - t * t);\n}\n\nconst r2 = (n: number) => Math.round(n * 100) / 100;\nconst lerp = (a: number, b: number, t: number) => a + (b - a) * t;\ntype Pt = [number, number];\n\n/** How far the beard's outer edge deliberately overshoots the face. */\nconst OVERHANG = 1;\n\n/** Closed path through points, smoothed with quadratic midpoints. */\nfunction smoothClosedPath(pts: Pt[]): string {\n if (pts.length < 3) return '';\n const mid = (a: Pt, b: Pt): Pt => [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];\n let d = `M${r2(pts[0][0])},${r2(pts[0][1])}`;\n for (let i = 1; i <= pts.length; i++) {\n const cur = pts[i % pts.length];\n const m = mid(cur, pts[(i + 1) % pts.length]);\n d += ` Q${r2(cur[0])},${r2(cur[1])} ${r2(m[0])},${r2(m[1])}`;\n }\n return d + ' Z';\n}\n\n/**\n * Rewrite every coordinate in a `d` attribute from the man's face into the\n * target face. Safe because these shapes use only absolute M/L/Q/C/Z — no\n * relative commands, no arcs, so all numbers come in x,y pairs.\n */\nfunction fitPathData(d: string, face: FaceGeometry): string {\n const sx = face.rx / MAN_FACE.rx;\n const sy = face.ry / MAN_FACE.ry;\n if (sx === 1 && sy === 1 && face.cx === MAN_FACE.cx && face.cy === MAN_FACE.cy) {\n return d; // man's face — leave the artwork byte-for-byte alone\n }\n\n let i = 0;\n return d.replace(/-?\\d+(\\.\\d+)?/g, (m) => {\n const v = parseFloat(m);\n const isX = i % 2 === 0;\n i++;\n const scaled = isX\n ? face.cx + (v - MAN_FACE.cx) * sx\n : face.cy + (v - MAN_FACE.cy) * sy;\n return String(r2(scaled));\n });\n}\n\n/** Fit every `d` and every ellipse in a piece of SVG markup to a head. */\nexport function fitToFace(markup: string, face: FaceGeometry): string {\n if (!markup) return '';\n const sx = face.rx / MAN_FACE.rx;\n const sy = face.ry / MAN_FACE.ry;\n\n return markup\n .replace(/d=\"([^\"]*)\"/g, (_, d: string) => `d=\"${fitPathData(d, face)}\"`)\n .replace(/<ellipse([^>]*)\\/>/g, (whole, attrs: string) => {\n const out = attrs\n .replace(/cx=\"(-?\\d+(?:\\.\\d+)?)\"/, (_, v) =>\n `cx=\"${r2(face.cx + (parseFloat(v) - MAN_FACE.cx) * sx)}\"`)\n .replace(/cy=\"(-?\\d+(?:\\.\\d+)?)\"/, (_, v) =>\n `cy=\"${r2(face.cy + (parseFloat(v) - MAN_FACE.cy) * sy)}\"`)\n .replace(/rx=\"(-?\\d+(?:\\.\\d+)?)\"/, (_, v) => `rx=\"${r2(parseFloat(v) * sx)}\"`)\n .replace(/ry=\"(-?\\d+(?:\\.\\d+)?)\"/, (_, v) => `ry=\"${r2(parseFloat(v) * sy)}\"`);\n return `<ellipse${out}/>`;\n });\n}\n\n\n// ── Beard generation ─────────────────────────────────────────────────\n//\n// A beard is a BAND along the jaw, so it must be built as one: an outer\n// edge tracking the face and an inner edge just inside it. The hand-drawn\n// shapes in beard-shapes.ts cannot be used here — their inner edge cuts\n// deep across the lower face (x=70 where the jaw is at x=55), which is\n// exactly the skin gap this is meant to eliminate. The gap is not an\n// authoring slip, it is what that silhouette is.\n//\n// These parameters are tuned to read like the original artwork: thin at\n// the cheekbones, thickening toward the chin, tapering to nothing at the\n// sides by the ears.\n\ninterface BandOptions {\n /** Where the beard starts, as a fraction of ry below the face centre. */\n startFrac: number;\n endFrac: number;\n /** Gap between the face outline and the band's outer edge. */\n inset: number;\n thicknessTop: number;\n thicknessBottom: number;\n /** Drop below the chin: length and half-width, as fractions of ry / rx. */\n drop?: number;\n dropWidth?: number;\n}\n\nconst BEARD_BANDS: Record<Exclude<BeardStyle, 'none'>, BandOptions> = {\n // 5 o'clock shadow: low and faint, hugging the jaw only.\n stubble: { startFrac: 0.36, endFrac: 0.97, inset: 3, thicknessTop: 4, thicknessBottom: 7 },\n // Jaw-hugging crescent, tapering to nothing by the ears.\n short: { startFrac: 0.17, endFrac: 0.99, inset: 2.5, thicknessTop: 4, thicknessBottom: 14 },\n // Same crescent, then a narrow drop. Kept narrow on purpose: a wide drop\n // reads as a bib rather than a beard.\n long: {\n startFrac: 0.17, endFrac: 0.99, inset: 2.5, thicknessTop: 4, thicknessBottom: 15,\n drop: 0.30, dropWidth: 0.26,\n },\n // Chin patch only.\n goatee: { startFrac: 0.60, endFrac: 0.99, inset: 13, thicknessTop: 5, thicknessBottom: 12 },\n};\n\nexport function buildBeardSvg(\n style: BeardStyle,\n face: FaceGeometry,\n fill: string,\n): string {\n if (style === 'none') return '';\n const o = BEARD_BANDS[style];\n const steps = 26;\n\n const yTop = face.cy + face.ry * o.startFrac;\n const yJaw = face.cy + face.ry * o.endFrac;\n const chinY = face.cy + face.ry;\n const dropLen = o.drop ? face.ry * o.drop : 0;\n const dropW = o.dropWidth ? face.rx * o.dropWidth : 0;\n\n /**\n * Outer half-width; past the chin it becomes the drop's taper.\n *\n * The band's outer edge is drawn slightly OUTSIDE the face outline, not\n * inset inside it. Two reasons: the smoothing below passes through the\n * MIDPOINTS between samples rather than through the samples themselves, so\n * an edge placed exactly on the face ends up a sliver inside it and shows\n * as a skin gap. Overhanging by a pixel and letting the face clip trim it\n * back gives contact by construction instead of by tuning.\n */\n const outerHalf = (y: number): number => {\n if (y <= chinY) return Math.max(0, faceHalfWidthAt(face, y) + OVERHANG);\n if (!dropLen) return 0;\n const t = (y - chinY) / dropLen;\n return t >= 1 ? 0 : dropW * Math.sqrt(1 - t * t);\n };\n\n const innerR: Pt[] = [];\n const innerL: Pt[] = [];\n for (let i = 0; i <= steps; i++) {\n const t = i / steps;\n const y = lerp(yTop, yJaw, t);\n // The inner edge follows the same curve, a band-thickness inside it.\n // Deriving it from the face is what closes the gap.\n const hw = Math.max(0, faceHalfWidthAt(face, y) - o.inset - lerp(o.thicknessTop, o.thicknessBottom, t));\n innerR.push([face.cx + hw, y]);\n innerL.push([face.cx - hw, y]);\n }\n\n const outerR: Pt[] = [];\n const outerL: Pt[] = [];\n for (let i = 0; i <= steps; i++) {\n const y = lerp(yTop, chinY + dropLen, i / steps);\n const hw = outerHalf(y);\n outerR.push([face.cx + hw, y]);\n outerL.push([face.cx - hw, y]);\n }\n\n const ring: Pt[] = [\n ...innerL,\n ...[...innerR].reverse(),\n ...outerR,\n ...[...outerL].reverse(),\n ];\n const d = smoothClosedPath(ring);\n if (!d) return '';\n // Stubble still relies on opacity rather than a second tone; logged for Phase 5.\n const opacity = style === 'stubble' ? ' opacity=\"0.22\"' : '';\n return `<path d=\"${d}\" fill=\"${fill}\"${opacity}/>`;\n}\n","// libs/avatar-player/src/lib/geometry/avatar-renderer.ts/avatar-renderer.ts\n//\n// SINGLE SOURCE OF TRUTH for avatar geometry.\n//\n// Both the live preview (SvgAvatarComponent) and the export bundle\n// (AvatarService.buildSvg) call this. Previously each had its own copy\n// of the render logic, and they drifted badly: the exporter ignored gender\n// and omitted every clipPath, so head-wearing professions exported with\n// hair punching through their hats.\n//\n// Nothing in here may read DOM state. Everything the renderer needs is\n// derived from the config (plus an optional viseme), which is what makes\n// the output reproducible and testable.\n\nimport type { AvatarConfig } from './avatar.model';\nimport { SKIN_TONES } from './skin-tones';\nimport { HAIR_COLORS } from './hair-colors';\nimport { EYE_COLORS } from './eye-colors';\nimport { MOUTH_SHAPES } from './svg-parts/mouth-shapes';\nimport { textToVisemes, MS_PER_VISEME } from './visemes';\nimport { EYE_SHAPES } from './svg-parts/eye-shapes';\nimport { HAIR_SHAPES } from './svg-parts/hair-shapes';\nimport { MUSTACHE_SHAPES } from './svg-parts/mustache-shapes';\nimport { faceFor, fitToFace, buildBeardSvg } from './svg-parts/beard-path';\nimport { GLASSES_SHAPES } from './svg-parts/glasses-shapes';\nimport { PROFESSION_LAYERS } from './svg-parts/profession-layers';\n\n/**\n * Professions whose accessory covers the top of the head, so hair must clip.\n *\n * `chef` is here because the toque band sits across the forehead; without it\n * long hair renders straight through the hat.\n */\nexport const HAT_PROFESSIONS: ReadonlySet<string> = new Set([\n 'engineer',\n 'police',\n 'artist',\n 'chef',\n]);\n\n/** Professions with a full helmet, which clips hair AND facial hair. */\nexport const HELMET_PROFESSIONS: ReadonlySet<string> = new Set(['astronaut']);\n\n/** Canonical canvas. */\nexport const CANVAS = { width: 200, height: 200, viewBox: '0 0 200 200' } as const;\n\n/**\n * Hat brim clip. A flat cut at y=56 — see the plan: deriving this from the\n * actual hat geometry is Phase 3 work.\n */\nconst HAT_CLIP_RECT = { x: 0, y: 56, width: 200, height: 144 } as const;\n/** Helmet aperture. */\nconst HELMET_CLIP_ELLIPSE = { cx: 100, cy: 78, rx: 57, ry: 62 } as const;\n\nexport interface BuildAvatarOptions {\n /** Lip-sync viseme id 0–5. Defaults to 0 (silence). */\n viseme?: number;\n /** Emit the `.animate-idle` hook so CSS head-idle motion runs. */\n animated?: boolean;\n /**\n * Bake a self-running SMIL mouth animation for this text into the output.\n *\n * Uses SMIL rather than CSS because the exported file is loaded through\n * `<img>`, where the consuming page's stylesheet is not available. SMIL is\n * declarative and travels inside the file, so the animation survives being\n * referenced from another document. (Scripts do not run in `<img>`; SMIL\n * does.)\n */\n speech?: string;\n /**\n * Namespace for generated element ids. Required when more than one avatar\n * is inlined into the same document, otherwise `url(#hat-clip)` resolves\n * against the first match and every avatar clips identically.\n */\n idsPrefix?: string;\n}\n\n/**\n * Build the `<animate>` child for the mouth, or '' when there is nothing to\n * animate. Consecutive duplicates are collapsed so the animation spends its\n * time on distinct mouth shapes rather than repeating the same one.\n */\nfunction mouthAnimation(text: string): string {\n const visemes = textToVisemes(text);\n if (visemes.length === 0) return '';\n\n // Collapse runs of the same viseme, then always return to silence so the\n // loop does not snap between two open shapes.\n const seq: number[] = [];\n for (const v of visemes) {\n if (seq.length === 0 || seq[seq.length - 1] !== v) seq.push(v);\n }\n if (seq[seq.length - 1] !== 0) seq.push(0);\n\n const values = seq.map((v) => MOUTH_SHAPES[v] ?? MOUTH_SHAPES[0]);\n const totalMs = seq.length * MS_PER_VISEME;\n\n // calcMode=\"discrete\" steps between the shapes with no interpolation —\n // which is what lip sync wants, and also avoids interpolating between\n // paths whose command counts differ (viseme 5 is a three-segment path).\n return (\n `<animate attributeName=\"d\" calcMode=\"discrete\" dur=\"${(totalMs / 1000).toFixed(3)}s\"` +\n ` repeatCount=\"indefinite\" values=\"${values.join(';')}\"/>`\n );\n}\n\nexport function isAstronaut(config: AvatarConfig): boolean {\n return HELMET_PROFESSIONS.has(config.profession);\n}\n\nexport function hasHat(config: AvatarConfig): boolean {\n return HAT_PROFESSIONS.has(config.profession);\n}\n\n/** Clip reference for the hair-back / hair-front layers, or null. */\nexport function hairClipUrl(config: AvatarConfig, idsPrefix = ''): string | null {\n if (isAstronaut(config)) return `url(#${idsPrefix}helmet-clip)`;\n if (hasHat(config)) return `url(#${idsPrefix}hat-clip)`;\n return null;\n}\n\n/** Clip reference for ears / mustache / beard, or null. */\nexport function helmetClipUrl(config: AvatarConfig, idsPrefix = ''): string | null {\n return isAstronaut(config) ? `url(#${idsPrefix}helmet-clip)` : null;\n}\n\n/** CSS custom properties that drive every colour in the artwork. */\nexport function avatarCssVars(config: AvatarConfig): Record<string, string> {\n const skin = SKIN_TONES[config.skinTone];\n return {\n '--skin-base': skin.base,\n '--skin-ear': skin.ear,\n '--skin-shadow': skin.shadow,\n '--lip-color': skin.lip,\n '--hair-color': HAIR_COLORS[config.hairColor],\n '--eye-color': EYE_COLORS[config.eyeColor],\n };\n}\n\nfunction cssVarDecls(config: AvatarConfig): string {\n return Object.entries(avatarCssVars(config))\n .map(([k, v]) => `${k}: ${v}`)\n .join(';');\n}\n\n/** `clip-path=\"url(#x)\"` only when the value exists — avoids empty attrs. */\nfunction clipAttr(url: string | null): string {\n return url ? ` clip-path=\"${url}\"` : '';\n}\n\nconst LASH_PATHS: readonly string[] = [\n 'M65,78 L62,74', 'M70,76 L68,72', 'M76,76 L78,72', 'M82,78 L85,74',\n 'M117,78 L114,74', 'M122,76 L120,72', 'M128,76 L130,72', 'M134,78 L137,74',\n];\n\n/**\n * The avatar markup WITHOUT the outer `<svg>` element.\n * The preview component supplies its own root so it can keep Angular\n * bindings on it; the exporter wraps this in a standalone document.\n */\nexport function buildAvatarSvgInner(config: AvatarConfig, opts: BuildAvatarOptions = {}): string {\n const { viseme = 0, animated = false, idsPrefix = '' } = opts;\n\n const isWoman = config.gender === 'woman';\n const astronaut = isAstronaut(config);\n const hair = HAIR_SHAPES[config.haircut] ?? { back: '', front: '' };\n const eyes = EYE_SHAPES[config.eyeStyle] ?? { sclera: '', iris: '' };\n // Facial hair is authored once and rescaled to whichever head is in use.\n // A man is untouched (the ratios are exactly 1); a woman gets the same\n // silhouette at the same relative position on a proportionally smaller\n // head, which is what the hand-drawn shapes were missing.\n const facialHead = faceFor(config.gender);\n const mustache = fitToFace(MUSTACHE_SHAPES[config.mustache] ?? '', facialHead);\n const beard = buildBeardSvg(config.beard, facialHead, HAIR_COLORS[config.hairColor]);\n const glasses = GLASSES_SHAPES[config.glasses] ?? '';\n const profession = PROFESSION_LAYERS[config.profession] ?? { body: '', accessory: '' };\n const mouthPath = MOUTH_SHAPES[viseme] ?? MOUTH_SHAPES[0];\n\n const hairClip = hairClipUrl(config, idsPrefix);\n const helmetClip = helmetClipUrl(config, idsPrefix);\n\n // ── defs ────────────────────────────────────────────────────────────\n // Face containment for facial hair. The hand-drawn beard and moustache\n // artwork is not perfectly authored to the jawline — `stubble` overhangs\n // even on a man — so rescaling alone cannot guarantee they stay on the\n // head. This clip is derived from the face in use and is the backstop.\n // The ellipse arcs over the top, then runs straight down past the chin so\n // a long beard may still fall onto the chest.\n const faceClipPath =\n `M${facialHead.cx - facialHead.rx},${facialHead.cy} ` +\n `A${facialHead.rx},${facialHead.ry} 0 0 1 ${facialHead.cx + facialHead.rx},${facialHead.cy} ` +\n `L${facialHead.cx + facialHead.rx},192 L${facialHead.cx - facialHead.rx},192 Z`;\n\n const defs =\n `<defs>` +\n `<clipPath id=\"${idsPrefix}face-clip\"><path d=\"${faceClipPath}\"/></clipPath>` +\n `<clipPath id=\"${idsPrefix}hat-clip\">` +\n `<rect x=\"${HAT_CLIP_RECT.x}\" y=\"${HAT_CLIP_RECT.y}\" width=\"${HAT_CLIP_RECT.width}\" height=\"${HAT_CLIP_RECT.height}\"/>` +\n `</clipPath>` +\n `<clipPath id=\"${idsPrefix}helmet-clip\">` +\n `<ellipse cx=\"${HELMET_CLIP_ELLIPSE.cx}\" cy=\"${HELMET_CLIP_ELLIPSE.cy}\" rx=\"${HELMET_CLIP_ELLIPSE.rx}\" ry=\"${HELMET_CLIP_ELLIPSE.ry}\"/>` +\n `</clipPath>` +\n `</defs>`;\n\n // ── 1. body ──────────────────────────────────────────────────────────\n const body = `<g class=\"layer-body\">${profession.body}</g>`;\n\n // ── 2. neck (hidden for astronaut — the collar covers it) ───────────\n const neck = astronaut\n ? ''\n : isWoman\n ? `<rect class=\"layer-neck\" x=\"90\" y=\"135\" width=\"20\" height=\"22\" rx=\"8\" fill=\"var(--skin-base)\"/>`\n : `<rect class=\"layer-neck\" x=\"88\" y=\"135\" width=\"24\" height=\"22\" rx=\"8\" fill=\"var(--skin-base)\"/>`;\n\n // ── 3. ears ─────────────────────────────────────────────────────────\n const earRx = isWoman ? 7 : 8;\n const earRy = isWoman ? 9 : 10;\n const ears =\n `<g class=\"layer-ears\"${clipAttr(helmetClip)}>` +\n `<ellipse cx=\"48\" cy=\"90\" rx=\"${earRx}\" ry=\"${earRy}\" fill=\"var(--skin-ear)\"/>` +\n `<ellipse cx=\"152\" cy=\"90\" rx=\"${earRx}\" ry=\"${earRy}\" fill=\"var(--skin-ear)\"/>` +\n `</g>`;\n\n // ── 4. hair back ────────────────────────────────────────────────────\n const hairBack = `<g class=\"layer-hair-back\"${clipAttr(hairClip)}>${hair.back}</g>`;\n\n // ── 5. face ─────────────────────────────────────────────────────────\n const face = isWoman\n ? `<ellipse class=\"layer-face\" cx=\"100\" cy=\"86\" rx=\"49\" ry=\"55\" fill=\"var(--skin-base)\"/>`\n : `<ellipse class=\"layer-face\" cx=\"100\" cy=\"88\" rx=\"52\" ry=\"56\" fill=\"var(--skin-base)\"/>`;\n\n // ── 6. eyes ─────────────────────────────────────────────────────────\n // `.pupils` is driven by --pupil-x/--pupil-y so no post-render DOM\n // patching is needed; see svg-avatar.component.scss.\n const eyeGroup =\n `<g class=\"layer-eyes\">` +\n `<g>${eyes.sclera}</g>` +\n `<g class=\"pupils\"><g>${eyes.iris}</g></g>` +\n `</g>`;\n\n // ── 6b. lashes (woman only) ─────────────────────────────────────────\n const lashes = isWoman\n ? `<g class=\"layer-lashes\">${LASH_PATHS.map(\n (d) =>\n `<path d=\"${d}\" stroke=\"var(--hair-color)\" stroke-width=\"1.5\" stroke-linecap=\"round\" fill=\"none\"/>`,\n ).join('')}</g>`\n : '';\n\n // ── 7. eyelids (blink) ──────────────────────────────────────────────\n // The eyelids are OPAQUE skin-coloured rects painted over the eyes. The\n // only thing that hides them is `transform: scaleY(0)` from the consuming\n // stylesheet — so a standalone SVG with no stylesheet ships with its eyes\n // completely covered. Two defences:\n // 1. static output omits the layer outright, because a still image has\n // nothing to blink\n // 2. animated output carries transform=\"scale(1,0)\" as an inline\n // presentation attribute, so the eyes are hidden even if the CSS\n // never arrives. CSS still wins over the attribute, so blinking works.\n const eyelids = animated\n ? `<g class=\"layer-eyelids\">` +\n `<rect class=\"eyelid-left\" x=\"63\" y=\"77\" width=\"22\" height=\"22\" rx=\"11\" fill=\"var(--skin-base)\" transform=\"scale(1,0)\"/>` +\n `<rect class=\"eyelid-right\" x=\"115\" y=\"77\" width=\"22\" height=\"22\" rx=\"11\" fill=\"var(--skin-base)\" transform=\"scale(1,0)\"/>` +\n `</g>`\n : '';\n\n // ── 8. brows ────────────────────────────────────────────────────────\n const brows = isWoman\n ? `<g class=\"layer-brows\">` +\n `<path d=\"M64,74 Q74,69 84,73\" stroke=\"var(--hair-color)\" stroke-width=\"2\" fill=\"none\" stroke-linecap=\"round\"/>` +\n `<path d=\"M116,73 Q126,69 136,74\" stroke=\"var(--hair-color)\" stroke-width=\"2\" fill=\"none\" stroke-linecap=\"round\"/>` +\n `</g>`\n : `<g class=\"layer-brows\">` +\n `<rect x=\"63\" y=\"72\" width=\"22\" height=\"5\" rx=\"3\" fill=\"var(--hair-color)\"/>` +\n `<rect x=\"115\" y=\"72\" width=\"22\" height=\"5\" rx=\"3\" fill=\"var(--hair-color)\"/>` +\n `</g>`;\n\n // ── 9. nose — uses --skin-shadow so it stays visible on deep skin ───\n const nose = isWoman\n ? `<path class=\"layer-nose\" d=\"M98,104 Q100,110 102,104\" stroke=\"var(--skin-shadow)\" stroke-width=\"1.8\" fill=\"none\" stroke-linecap=\"round\"/>`\n : `<path class=\"layer-nose\" d=\"M97,105 Q100,112 103,105\" stroke=\"var(--skin-shadow)\" stroke-width=\"2\" fill=\"none\" stroke-linecap=\"round\"/>`;\n\n // ── 10. mouth ───────────────────────────────────────────────────────\n // When `speech` is set the mouth carries a self-running SMIL animation,\n // so the file speaks on its own with no host page involved.\n const speech = opts.speech;\n const mouthAnim = speech ? mouthAnimation(speech) : '';\n const mouthInner = isWoman\n ? `stroke=\"var(--lip-color)\" stroke-width=\"2.5\" fill=\"var(--lip-color)\" fill-opacity=\"0.35\" stroke-linecap=\"round\"`\n : `stroke=\"var(--lip-color)\" stroke-width=\"2.5\" fill=\"none\" stroke-linecap=\"round\"`;\n const mouth =\n `<path class=\"layer-mouth\" d=\"${mouthPath}\" ${mouthInner}>` +\n mouthAnim +\n `</path>`;\n\n // ── 11-14 ───────────────────────────────────────────────────────────\n // No CSS transform: the shape is rescaled in the path data itself. The\n // inner groups carry the face clip and the outer the helmet clip, because\n // an element can only take one clip-path.\n const facialHair =\n `<g class=\"layer-mustache\"${clipAttr(helmetClip)}>` +\n `<g clip-path=\"url(#${idsPrefix}face-clip)\">${mustache}</g></g>` +\n `<g class=\"layer-beard\"${clipAttr(helmetClip)}>` +\n `<g clip-path=\"url(#${idsPrefix}face-clip)\">${beard}</g></g>`;\n const glassesGroup = `<g class=\"layer-glasses\">${glasses}</g>`;\n const hairFront = `<g class=\"layer-hair-front\"${clipAttr(hairClip)}>${hair.front}</g>`;\n const accessory = `<g class=\"layer-accessory\">${profession.accessory}</g>`;\n\n const head = `<g class=\"layer-head\"${animated ? ' animate-idle' : ''}>` + [ears, hairBack, face, eyeGroup, lashes, eyelids, brows, nose, mouth, facialHair, glassesGroup, hairFront, accessory].join('') + `</g>`;\n\n return defs + body + neck + head;\n}\n\n/**\n * A complete, standalone SVG document. This is what gets written to disk\n * and what the demo viewer renders — it must depend on nothing outside\n * itself, so the palette is inlined as a `style` attribute on the root.\n *\n * `animated` is forced off regardless of what the caller passed. That flag\n * emits the `.animate-idle` class, which is driven by the consuming\n * component's stylesheet; a standalone file has no stylesheet, so the class\n * would be a promise the document cannot keep. `speech` still applies,\n * because SMIL travels inside the file.\n */\nexport function buildAvatarSvg(config: AvatarConfig, opts: BuildAvatarOptions = {}): string {\n const inner = buildAvatarSvgInner(config, { ...opts, animated: false });\n return (\n `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"${CANVAS.viewBox}\"` +\n ` style=\"${cssVarDecls(config)}\">` +\n inner +\n `</svg>`\n );\n}\n","/**\n * Blink, pupil drift and head-idle driving — framework-free.\n *\n * This was the body of an Angular `@Injectable()` service. Nothing in it ever\n * touched Angular, only `setTimeout` and `requestAnimationFrame`, so it lives\n * here and the Angular wrapper just delegates. The React player uses it\n * directly.\n *\n * Head idle is deliberately *not* here: it is a pure CSS animation, so it costs\n * no JavaScript and no framework re-render. See the stylesheet.\n */\nexport class AvatarAnimator {\n private timers: ReturnType<typeof setTimeout>[] = [];\n private rafs: number[] = [];\n\n /**\n * Start random blink animation.\n * Calls onState with CSS class names: 'blinking' → 'blink-half' → ''\n * Interval: 2.5–5s random for natural feel.\n */\n startBlink(onState: (cssClass: string) => void): void {\n const doBlink = () => {\n onState('blinking');\n const t1 = setTimeout(() => {\n onState('blink-half');\n const t2 = setTimeout(() => {\n onState('');\n // Schedule next blink: 2.5s–5s random interval\n const t3 = setTimeout(doBlink, 2500 + Math.random() * 2500);\n this.timers.push(t3);\n }, 50);\n this.timers.push(t2);\n }, 80);\n this.timers.push(t1);\n };\n const init = setTimeout(doBlink, 1000 + Math.random() * 1000);\n this.timers.push(init);\n }\n\n /**\n * Start random eye movement (pupil tracking).\n * Calls onOffset with {x, y} pixel offsets for the pupil group.\n */\n startEyeMovement(onOffset: (v: { x: number; y: number }) => void): void {\n const move = () => {\n const dx = (Math.random() - 0.5) * 8;\n const dy = (Math.random() - 0.5) * 4;\n onOffset({ x: dx, y: dy });\n const t = setTimeout(move, 1800 + Math.random() * 2200);\n this.timers.push(t);\n };\n const init = setTimeout(move, 500);\n this.timers.push(init);\n }\n\n /**\n * Start subtle head idle motion via requestAnimationFrame.\n * Produces a gentle sine-wave rotation + vertical bob.\n */\n startHeadIdle(onTransform: (t: string) => void): void {\n const start = performance.now();\n const tick = (now: number) => {\n const t = (now - start) / 1000;\n const rotZ = Math.sin(t * 0.6) * 1.5;\n const transY = Math.sin(t * 0.4) * 1.5;\n onTransform(`rotate(${rotZ}, 100, 150) translate(0, ${transY})`);\n this.rafs.push(requestAnimationFrame(tick));\n };\n this.rafs.push(requestAnimationFrame(tick));\n }\n\n /** Stop all running animations and clear timers/rafs. */\n stopAll(): void {\n this.timers.forEach(clearTimeout);\n this.rafs.forEach(cancelAnimationFrame);\n this.timers = [];\n this.rafs = [];\n }\n}","/**\n * Drives visemes in real time — framework-free.\n *\n * The text -> viseme mapping itself lives in ./visemes because the offline SVG\n * generator needs the exact same mapping to bake an animated mouth into an\n * exported file. Keeping one copy is the whole point — if this drifted from\n * the generator, exported avatars would mouth different words than the preview\n * does.\n */\nimport { textToVisemes, MS_PER_VISEME } from './visemes';\n\nexport class LipSyncPlayer {\n private readonly MS_PER_VISEME = MS_PER_VISEME;\n\n /** @see textToVisemes */\n textToVisemes(text: string): number[] {\n return textToVisemes(text);\n }\n\n /**\n * Play a viseme sequence, calling onViseme(id) each frame.\n *\n * `onEnd` fires exactly once when the sequence finishes or is cancelled, so\n * callers can clear a \"speaking\" flag reliably. Do NOT infer the end from\n * onViseme(0): viseme 0 is silence and occurs at every space, so it would\n * end the utterance at the first word gap.\n *\n * Returns a cancel function. Always resets to 0 (silence) on end or cancel.\n */\n play(\n visemes: number[],\n onViseme: (v: number) => void,\n onEnd?: () => void,\n ): () => void {\n let i = 0;\n let cancelled = false;\n let finished = false;\n\n const finish = () => {\n if (finished) return;\n finished = true;\n onViseme(0); // reset to silence\n onEnd?.();\n };\n\n const tick = () => {\n if (cancelled || i >= visemes.length) {\n finish();\n return;\n }\n onViseme(visemes[i++]);\n setTimeout(tick, this.MS_PER_VISEME);\n };\n\n tick();\n return () => {\n cancelled = true;\n finish();\n };\n }\n}","/**\n * Public entry point of the avatar geometry.\n *\n * WHY THIS LIVES INSIDE THE ANGULAR LIBRARY\n *\n * The code below is plain TypeScript with no framework imports — it could sit in\n * a package of its own, and that is where it used to live. It lives here\n * because of a hard limit in ng-packagr:\n *\n * - ng-packagr treats EVERY bare specifier as external\n * (`isExternalDependency`, ng-packagr/lib/flatten/rollup.js), so a\n * `from 'avatar-core'` import ships unresolved and breaks the tarball.\n * - a relative import into another package does not work either: ng-packagr\n * compiles src/ -> dist/avatar-player/esm2022/ first and only then bundles,\n * so a path valid from src/ points at nothing from the intermediate dir.\n *\n * Living inside the library means ng-packagr compiles it as first-party source\n * and inlines it into the published bundle. The React package imports these\n * same files with a relative specifier at build time, so there is still exactly\n * ONE copy of the geometry in the repository and in CI.\n *\n * Consequence: consumers of angular-avatar-player and react-avatar-player get\n * their own copy of the renderer inside the tarball. That is deliberate — it\n * means `npm i angular-avatar-player` is a single, self-contained install.\n */\n\n// ── Model & types ───────────────────────────────────────────\nexport type {\n AvatarConfig,\n Gender,\n SkinTone,\n HaircutStyle,\n HairColor,\n EyeColor,\n MustacheStyle,\n BeardStyle,\n EyeStyle,\n GlassesStyle,\n ProfessionType,\n} from './avatar.model';\n\n// ── Color palettes ──────────────────────────────────────────\nexport { SKIN_TONES } from './skin-tones';\nexport { HAIR_COLORS } from './hair-colors';\nexport { EYE_COLORS } from './eye-colors';\n\n// ── Trait previews (small thumbnails for creator pickers) ─────\nexport { buildTraitPreview } from './trait-previews';\nexport type { PreviewKind } from './trait-previews';\n\n// ── Visemes ─────────────────────────────────────────────────\nexport { textToVisemes, MS_PER_VISEME } from './visemes';\nexport {\n VISEME_SILENCE,\n VISEME_WIDE,\n VISEME_ROUND,\n VISEME_SPREAD,\n VISEME_PRESSED,\n VISEME_TEETH,\n} from './visemes';\n\n// ── SVG renderer (works in Node too, for the offline export) ─\nexport {\n buildAvatarSvg,\n buildAvatarSvgInner,\n avatarCssVars,\n hairClipUrl,\n helmetClipUrl,\n isAstronaut,\n hasHat,\n HAT_PROFESSIONS,\n HELMET_PROFESSIONS,\n CANVAS,\n} from './avatar-renderer';\nexport type { BuildAvatarOptions } from './avatar-renderer';\n\n// ── SVG part constants ──────────────────────────────────────\nexport { MOUTH_SHAPES } from './svg-parts/mouth-shapes';\nexport { EYE_SHAPES } from './svg-parts/eye-shapes';\nexport { HAIR_SHAPES } from './svg-parts/hair-shapes';\nexport { GLASSES_SHAPES } from './svg-parts/glasses-shapes';\nexport { MUSTACHE_SHAPES } from './svg-parts/mustache-shapes';\nexport { BEARD_SHAPES } from './svg-parts/beard-shapes';\n\n// ── Face geometry + profession layers ───────────────────────\nexport {\n faceFor,\n faceHalfWidthAt,\n fitToFace,\n buildBeardSvg,\n MAN_FACE,\n WOMAN_FACE,\n} from './svg-parts/beard-path';\nexport type { FaceGeometry } from './svg-parts/beard-path';\nexport { PROFESSION_LAYERS } from './svg-parts/profession-layers';\n\n// ── Runtime drivers (used by the UI wrappers, useful on their own) ──\nexport { AvatarAnimator } from './avatar-animator';\nexport { LipSyncPlayer } from './lip-sync-player';","// libs/avatar-player/src/lib/services/lip-sync.service.ts\nimport { Injectable } from '@angular/core';\nimport { LipSyncPlayer } from '../geometry';\n\n/**\n * Angular-facing wrapper around the framework-free {@link LipSyncPlayer}.\n *\n * The logic lives in ../geometry so the React player and any non-Angular\n * renderer use the exact same mapping. This class exists only so Angular apps\n * can inject it and so DI gives each player component its own instance.\n */\n@Injectable()\nexport class LipSyncService extends LipSyncPlayer {}","// libs/avatar-player/src/lib/services/avatar-animation.service.ts\nimport { Injectable } from '@angular/core';\nimport { AvatarAnimator } from '../geometry';\n\n/**\n * Angular-facing wrapper around the framework-free {@link AvatarAnimator}.\n *\n * The timers live in ../geometry so the React player can drive the same blink\n * and pupil logic. This class exists only so Angular apps can inject it and so\n * DI scopes one animator per component — a shared instance would mean one avatar\n * leaving the DOM stops blinking for every other avatar on the page.\n */\n@Injectable()\nexport class AvatarAnimationService extends AvatarAnimator {}","// libs/avatar-player/src/lib/components/svg-avatar/svg-avatar.component.ts\nimport {\n Component,\n OnInit,\n OnDestroy,\n input,\n signal,\n computed,\n effect,\n inject,\n ElementRef,\n NgZone,\n ViewEncapsulation,\n} from '@angular/core';\nimport { DomSanitizer, SafeHtml, SafeStyle } from '@angular/platform-browser';\nimport type { AvatarConfig } from '../../geometry';\nimport {\n avatarCssVars,\n buildAvatarSvgInner,\n isAstronaut,\n hasHat,\n hairClipUrl,\n helmetClipUrl,\n CANVAS,\n} from '../../geometry';\nimport { MOUTH_SHAPES } from '../../geometry';\nimport { AvatarAnimationService } from '../../services/avatar-animation.service';\n\n@Component({\n selector: 'app-svg-avatar',\n standalone: true,\n // The markup is injected with [innerHTML], so Angular's emulated\n // encapsulation would not match it and the styles would silently do\n // nothing — the eyelids would render at full size and hide the eyes.\n // Every selector in the SCSS is namespaced under `.avatar-svg` instead.\n encapsulation: ViewEncapsulation.None,\n // Per-component, not app-wide. ngOnDestroy calls anim.stopAll(), so a shared\n // instance would mean one avatar leaving the DOM stops blinking and head\n // motion for every other avatar on the page. That is invisible with a single\n // avatar and breaks a gallery of them.\n providers: [AvatarAnimationService],\n template: `<svg xmlns=\"http://www.w3.org/2000/svg\"\n [attr.viewBox]=\"viewBox\"\n class=\"avatar-svg\"\n [class.blink-blinking]=\"eyeBlinkClass() === 'blinking'\"\n [class.blink-half]=\"eyeBlinkClass() === 'blink-half'\"\n [style]=\"cssVarsStyle()\"\n [innerHTML]=\"innerHtml()\"></svg>`,\n styleUrl: './svg-avatar.component.scss',\n})\nexport class SvgAvatarComponent implements OnInit, OnDestroy {\n // ── Signal inputs ────────────────────────────────────────────\n //\n // These were `@Input` fields mirrored into signals via ngOnChanges.\n // That indirection was the root cause of four separate bugs: a `computed`\n // or `effect` that reads a plain property registers no dependency, so it\n // evaluates once and never invalidates. The symptoms were a frozen avatar,\n // permanently disabled undo/redo, and a dead Test Speech button.\n //\n // Signal inputs make that mistake impossible: reading `config()` inside a\n // computed registers a real dependency, so the derived values below cannot\n // silently go stale.\n readonly config = input.required<AvatarConfig>();\n readonly animationsEnabled = input(true);\n /** 0–5, controlled externally for lip sync. */\n readonly viseme = input(0);\n /**\n * Namespace for generated element ids. Set this when inlining more than\n * one avatar into a document, otherwise every avatar's `url(#hat-clip)`\n * resolves to the first matching element on the page.\n */\n readonly idsPrefix = input('');\n\n private readonly sanitizer = inject(DomSanitizer);\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly zone = inject(NgZone);\n private readonly anim = inject(AvatarAnimationService);\n\n readonly viewBox = CANVAS.viewBox;\n\n // Animation state (driven by AvatarAnimationService)\n readonly eyeBlinkClass = signal<string>('');\n readonly pupilOffset = signal<{ x: number; y: number }>({ x: 0, y: 0 });\n\n /**\n * Palette plus the two pupil-offset custom properties the SCSS reads.\n *\n * A computed, not a getter: a getter returns a new SafeStyle on every\n * change-detection pass, so Angular re-wrote the style attribute each\n * cycle. A getter also cannot track `config`, so it went stale too.\n */\n readonly cssVarsStyle = computed<SafeStyle>(() => {\n const p = this.pupilOffset();\n const decls =\n `--pupil-x: ${p.x}px; --pupil-y: ${p.y}px; ` +\n Object.entries(avatarCssVars(this.config()))\n .map(([k, v]) => `${k}: ${v}`)\n .join('; ');\n return this.sanitizer.bypassSecurityTrustStyle(decls);\n });\n\n /**\n * The markup is regenerated only when the *config* changes — deliberately\n * NOT when the viseme does. Re-assigning innerHTML mid-utterance would\n * recreate `.layer-head` and restart the head-idle animation every 80ms.\n * Viseme changes are applied to the existing mouth path below instead.\n */\n readonly innerHtml = computed<SafeHtml>(() =>\n this.sanitizer.bypassSecurityTrustHtml(\n buildAvatarSvgInner(this.config(), {\n animated: this.animationsEnabled(),\n idsPrefix: this.idsPrefix(),\n }),\n ),\n );\n\n // Public shape helpers — the geometry itself lives in lib/shared.\n readonly isAstronaut = computed(() => isAstronaut(this.config()));\n readonly hasHat = computed(() => hasHat(this.config()));\n readonly hairClipUrl = computed(() => hairClipUrl(this.config(), this.idsPrefix()));\n readonly helmetClipUrl = computed(() => helmetClipUrl(this.config(), this.idsPrefix()));\n readonly mouthPath = computed(() => MOUTH_SHAPES[this.viseme()] ?? MOUTH_SHAPES[0]);\n\n constructor() {\n // Keep the mouth in sync without re-rendering the whole avatar.\n effect(() => {\n // Both reads are signal-tracked: mouthPath so the mouth follows lip\n // sync, innerHtml so the path is re-applied after each markup swap,\n // when the fresh .layer-mouth element is guaranteed to be in the DOM.\n const path = this.mouthPath();\n this.innerHtml();\n this.applyMouthPath(path);\n });\n }\n\n private applyMouthPath(path: string): void {\n const mouth = this.host.nativeElement.querySelector<SVGPathElement>('.layer-mouth');\n if (mouth) {\n mouth.setAttribute('d', path);\n return;\n }\n // Markup was swapped this tick and the element is not queryable yet.\n requestAnimationFrame(() => {\n this.host.nativeElement\n .querySelector<SVGPathElement>('.layer-mouth')\n ?.setAttribute('d', path);\n });\n }\n\n ngOnInit(): void {\n if (this.animationsEnabled()) {\n this.zone.runOutsideAngular(() => {\n this.anim.startBlink((v) => this.zone.run(() => this.eyeBlinkClass.set(v)));\n this.anim.startEyeMovement((v) => this.zone.run(() => this.pupilOffset.set(v)));\n // Head idle is a pure CSS animation — no JS needed\n });\n }\n }\n\n ngOnDestroy(): void {\n this.anim.stopAll();\n }\n}\n","// libs/avatar-player/src/lib/components/avatar-player/avatar-player.component.ts\nimport {\n Component,\n Input,\n OnChanges,\n SimpleChanges,\n signal,\n inject,\n NgZone,\n} from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport type { AvatarConfig } from '../../geometry';\nimport { LipSyncService } from '../../services/lip-sync.service';\nimport { SvgAvatarComponent } from '../svg-avatar/svg-avatar.component';\n\n@Component({\n selector: 'app-avatar-player',\n standalone: true,\n imports: [CommonModule, SvgAvatarComponent],\n // Declared here so importing this standalone component is enough. Without it\n // a consumer that skips AvatarPlayerModule gets a NullInjectorError at\n // runtime rather than a compile error.\n providers: [LipSyncService],\n template: `\n <div class=\"avatar-player\" [class.avatar-player--speaking]=\"speaking\">\n <app-svg-avatar\n [config]=\"config\"\n [animationsEnabled]=\"true\"\n [viseme]=\"currentViseme()\"\n [idsPrefix]=\"idsPrefix\"\n [style.width.px]=\"displaySize()\"\n [style.height.px]=\"displaySize()\"\n class=\"player-avatar\" />\n </div>\n `,\n styleUrl: './avatar-player.component.scss',\n})\nexport class AvatarPlayerComponent implements OnChanges {\n @Input({ required: true }) config!: AvatarConfig;\n @Input() speaking = false;\n @Input() message = '';\n\n /**\n * Namespace for the generated `clipPath` ids.\n *\n * REQUIRED when more than one player is on a page. Every avatar emits\n * `url(#hat-clip)`, which resolves against the *first* matching element in\n * the document — so without a unique prefix per avatar they all clip against\n * whichever came first, and hats and helmets stop hiding hair.\n */\n @Input() idsPrefix = '';\n\n /** Base edge length in px. It grows while speaking. */\n @Input() size = 48;\n\n currentViseme = signal(0);\n\n private lipSync = inject(LipSyncService);\n private zone = inject(NgZone);\n private lipSyncCancelFn: (() => void) | null = null;\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes['speaking'] || changes['message']) {\n // Always cancel previous playback before starting new\n this.lipSyncCancelFn?.();\n\n if (this.speaking && this.message) {\n const visemes = this.lipSync.textToVisemes(this.message);\n this.lipSyncCancelFn = this.lipSync.play(visemes, (v) => {\n this.zone.run(() => this.currentViseme.set(v));\n });\n } else {\n this.currentViseme.set(0);\n }\n }\n }\n\n /** Speaking reads as a small \"leaning in\", so the avatar grows. */\n displaySize(): number {\n return this.speaking ? this.size * 1.5 : this.size;\n }\n}\n","// libs/avatar-player/src/lib/avatar-player.module.ts\nimport { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { SvgAvatarComponent } from './components/svg-avatar/svg-avatar.component';\nimport { AvatarPlayerComponent } from './components/avatar-player/avatar-player.component';\nimport { LipSyncService } from './services/lip-sync.service';\nimport { AvatarAnimationService } from './services/avatar-animation.service';\n\n@NgModule({\n imports: [\n CommonModule,\n // Standalone components imported into the module\n SvgAvatarComponent,\n AvatarPlayerComponent,\n ],\n exports: [\n // Only these two need to be exported for consumers\n SvgAvatarComponent,\n AvatarPlayerComponent,\n ],\n providers: [\n LipSyncService,\n AvatarAnimationService,\n ],\n})\nexport class AvatarPlayerModule {}\n","/**\n * Single public entry point of the Angular avatar package.\n *\n * A consuming app only ever imports from here:\n *\n * import { AvatarPlayerComponent } from 'angular-avatar-player';\n *\n * The SVG geometry, palettes and viseme mapping live in ./lib/geometry — plain\n * TypeScript with no Angular imports, but shipped inside this package. They are\n * re-exported below, so there is one import path and a consumer never needs a\n * second package to reach the renderer. `react-avatar-player` compiles the same\n * geometry files into its own bundle, which is what keeps the two players from\n * drifting apart.\n */\n\n// ── Components ──────────────────────────────────────────────\nexport { AvatarPlayerComponent } from './lib/components/avatar-player/avatar-player.component';\nexport { SvgAvatarComponent } from './lib/components/svg-avatar/svg-avatar.component';\n\n// ── NgModule (for NgModule-based apps) ──────────────────────\nexport { AvatarPlayerModule } from './lib/avatar-player.module';\n\n// ── Services ────────────────────────────────────────────────\nexport { LipSyncService } from './lib/services/lip-sync.service';\nexport { AvatarAnimationService } from './lib/services/avatar-animation.service';\n\n// The geometry, shipped in this package and re-exported verbatim.\nexport * from './lib/geometry';","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
package/index.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Single public entry point of the Angular avatar package.
3
+ *
4
+ * A consuming app only ever imports from here:
5
+ *
6
+ * import { AvatarPlayerComponent } from 'angular-avatar-player';
7
+ *
8
+ * The SVG geometry, palettes and viseme mapping live in ./lib/geometry — plain
9
+ * TypeScript with no Angular imports, but shipped inside this package. They are
10
+ * re-exported below, so there is one import path and a consumer never needs a
11
+ * second package to reach the renderer. `react-avatar-player` compiles the same
12
+ * geometry files into its own bundle, which is what keeps the two players from
13
+ * drifting apart.
14
+ */
15
+ export { AvatarPlayerComponent } from './lib/components/avatar-player/avatar-player.component';
16
+ export { SvgAvatarComponent } from './lib/components/svg-avatar/svg-avatar.component';
17
+ export { AvatarPlayerModule } from './lib/avatar-player.module';
18
+ export { LipSyncService } from './lib/services/lip-sync.service';
19
+ export { AvatarAnimationService } from './lib/services/avatar-animation.service';
20
+ export * from './lib/geometry';
@@ -0,0 +1,9 @@
1
+ import * as i0 from "@angular/core";
2
+ import * as i1 from "@angular/common";
3
+ import * as i2 from "./components/svg-avatar/svg-avatar.component";
4
+ import * as i3 from "./components/avatar-player/avatar-player.component";
5
+ export declare class AvatarPlayerModule {
6
+ static ɵfac: i0.ɵɵFactoryDeclaration<AvatarPlayerModule, never>;
7
+ static ɵmod: i0.ɵɵNgModuleDeclaration<AvatarPlayerModule, never, [typeof i1.CommonModule, typeof i2.SvgAvatarComponent, typeof i3.AvatarPlayerComponent], [typeof i2.SvgAvatarComponent, typeof i3.AvatarPlayerComponent]>;
8
+ static ɵinj: i0.ɵɵInjectorDeclaration<AvatarPlayerModule>;
9
+ }
@@ -0,0 +1,28 @@
1
+ import { OnChanges, SimpleChanges } from '@angular/core';
2
+ import type { AvatarConfig } from '../../geometry';
3
+ import * as i0 from "@angular/core";
4
+ export declare class AvatarPlayerComponent implements OnChanges {
5
+ config: AvatarConfig;
6
+ speaking: boolean;
7
+ message: string;
8
+ /**
9
+ * Namespace for the generated `clipPath` ids.
10
+ *
11
+ * REQUIRED when more than one player is on a page. Every avatar emits
12
+ * `url(#hat-clip)`, which resolves against the *first* matching element in
13
+ * the document — so without a unique prefix per avatar they all clip against
14
+ * whichever came first, and hats and helmets stop hiding hair.
15
+ */
16
+ idsPrefix: string;
17
+ /** Base edge length in px. It grows while speaking. */
18
+ size: number;
19
+ currentViseme: import("@angular/core").WritableSignal<number>;
20
+ private lipSync;
21
+ private zone;
22
+ private lipSyncCancelFn;
23
+ ngOnChanges(changes: SimpleChanges): void;
24
+ /** Speaking reads as a small "leaning in", so the avatar grows. */
25
+ displaySize(): number;
26
+ static ɵfac: i0.ɵɵFactoryDeclaration<AvatarPlayerComponent, never>;
27
+ static ɵcmp: i0.ɵɵComponentDeclaration<AvatarPlayerComponent, "app-avatar-player", never, { "config": { "alias": "config"; "required": true; }; "speaking": { "alias": "speaking"; "required": false; }; "message": { "alias": "message"; "required": false; }; "idsPrefix": { "alias": "idsPrefix"; "required": false; }; "size": { "alias": "size"; "required": false; }; }, {}, never, never, true, never>;
28
+ }
@@ -0,0 +1,52 @@
1
+ import { OnInit, OnDestroy } from '@angular/core';
2
+ import { SafeHtml, SafeStyle } from '@angular/platform-browser';
3
+ import type { AvatarConfig } from '../../geometry';
4
+ import * as i0 from "@angular/core";
5
+ export declare class SvgAvatarComponent implements OnInit, OnDestroy {
6
+ readonly config: import("@angular/core").InputSignal<AvatarConfig>;
7
+ readonly animationsEnabled: import("@angular/core").InputSignal<boolean>;
8
+ /** 0–5, controlled externally for lip sync. */
9
+ readonly viseme: import("@angular/core").InputSignal<number>;
10
+ /**
11
+ * Namespace for generated element ids. Set this when inlining more than
12
+ * one avatar into a document, otherwise every avatar's `url(#hat-clip)`
13
+ * resolves to the first matching element on the page.
14
+ */
15
+ readonly idsPrefix: import("@angular/core").InputSignal<string>;
16
+ private readonly sanitizer;
17
+ private readonly host;
18
+ private readonly zone;
19
+ private readonly anim;
20
+ readonly viewBox: "0 0 200 200";
21
+ readonly eyeBlinkClass: import("@angular/core").WritableSignal<string>;
22
+ readonly pupilOffset: import("@angular/core").WritableSignal<{
23
+ x: number;
24
+ y: number;
25
+ }>;
26
+ /**
27
+ * Palette plus the two pupil-offset custom properties the SCSS reads.
28
+ *
29
+ * A computed, not a getter: a getter returns a new SafeStyle on every
30
+ * change-detection pass, so Angular re-wrote the style attribute each
31
+ * cycle. A getter also cannot track `config`, so it went stale too.
32
+ */
33
+ readonly cssVarsStyle: import("@angular/core").Signal<SafeStyle>;
34
+ /**
35
+ * The markup is regenerated only when the *config* changes — deliberately
36
+ * NOT when the viseme does. Re-assigning innerHTML mid-utterance would
37
+ * recreate `.layer-head` and restart the head-idle animation every 80ms.
38
+ * Viseme changes are applied to the existing mouth path below instead.
39
+ */
40
+ readonly innerHtml: import("@angular/core").Signal<SafeHtml>;
41
+ readonly isAstronaut: import("@angular/core").Signal<boolean>;
42
+ readonly hasHat: import("@angular/core").Signal<boolean>;
43
+ readonly hairClipUrl: import("@angular/core").Signal<string | null>;
44
+ readonly helmetClipUrl: import("@angular/core").Signal<string | null>;
45
+ readonly mouthPath: import("@angular/core").Signal<string>;
46
+ constructor();
47
+ private applyMouthPath;
48
+ ngOnInit(): void;
49
+ ngOnDestroy(): void;
50
+ static ɵfac: i0.ɵɵFactoryDeclaration<SvgAvatarComponent, never>;
51
+ static ɵcmp: i0.ɵɵComponentDeclaration<SvgAvatarComponent, "app-svg-avatar", never, { "config": { "alias": "config"; "required": true; "isSignal": true; }; "animationsEnabled": { "alias": "animationsEnabled"; "required": false; "isSignal": true; }; "viseme": { "alias": "viseme"; "required": false; "isSignal": true; }; "idsPrefix": { "alias": "idsPrefix"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
52
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Blink, pupil drift and head-idle driving — framework-free.
3
+ *
4
+ * This was the body of an Angular `@Injectable()` service. Nothing in it ever
5
+ * touched Angular, only `setTimeout` and `requestAnimationFrame`, so it lives
6
+ * here and the Angular wrapper just delegates. The React player uses it
7
+ * directly.
8
+ *
9
+ * Head idle is deliberately *not* here: it is a pure CSS animation, so it costs
10
+ * no JavaScript and no framework re-render. See the stylesheet.
11
+ */
12
+ export declare class AvatarAnimator {
13
+ private timers;
14
+ private rafs;
15
+ /**
16
+ * Start random blink animation.
17
+ * Calls onState with CSS class names: 'blinking' → 'blink-half' → ''
18
+ * Interval: 2.5–5s random for natural feel.
19
+ */
20
+ startBlink(onState: (cssClass: string) => void): void;
21
+ /**
22
+ * Start random eye movement (pupil tracking).
23
+ * Calls onOffset with {x, y} pixel offsets for the pupil group.
24
+ */
25
+ startEyeMovement(onOffset: (v: {
26
+ x: number;
27
+ y: number;
28
+ }) => void): void;
29
+ /**
30
+ * Start subtle head idle motion via requestAnimationFrame.
31
+ * Produces a gentle sine-wave rotation + vertical bob.
32
+ */
33
+ startHeadIdle(onTransform: (t: string) => void): void;
34
+ /** Stop all running animations and clear timers/rafs. */
35
+ stopAll(): void;
36
+ }
@@ -0,0 +1,64 @@
1
+ import type { AvatarConfig } from './avatar.model';
2
+ /**
3
+ * Professions whose accessory covers the top of the head, so hair must clip.
4
+ *
5
+ * `chef` is here because the toque band sits across the forehead; without it
6
+ * long hair renders straight through the hat.
7
+ */
8
+ export declare const HAT_PROFESSIONS: ReadonlySet<string>;
9
+ /** Professions with a full helmet, which clips hair AND facial hair. */
10
+ export declare const HELMET_PROFESSIONS: ReadonlySet<string>;
11
+ /** Canonical canvas. */
12
+ export declare const CANVAS: {
13
+ readonly width: 200;
14
+ readonly height: 200;
15
+ readonly viewBox: "0 0 200 200";
16
+ };
17
+ export interface BuildAvatarOptions {
18
+ /** Lip-sync viseme id 0–5. Defaults to 0 (silence). */
19
+ viseme?: number;
20
+ /** Emit the `.animate-idle` hook so CSS head-idle motion runs. */
21
+ animated?: boolean;
22
+ /**
23
+ * Bake a self-running SMIL mouth animation for this text into the output.
24
+ *
25
+ * Uses SMIL rather than CSS because the exported file is loaded through
26
+ * `<img>`, where the consuming page's stylesheet is not available. SMIL is
27
+ * declarative and travels inside the file, so the animation survives being
28
+ * referenced from another document. (Scripts do not run in `<img>`; SMIL
29
+ * does.)
30
+ */
31
+ speech?: string;
32
+ /**
33
+ * Namespace for generated element ids. Required when more than one avatar
34
+ * is inlined into the same document, otherwise `url(#hat-clip)` resolves
35
+ * against the first match and every avatar clips identically.
36
+ */
37
+ idsPrefix?: string;
38
+ }
39
+ export declare function isAstronaut(config: AvatarConfig): boolean;
40
+ export declare function hasHat(config: AvatarConfig): boolean;
41
+ /** Clip reference for the hair-back / hair-front layers, or null. */
42
+ export declare function hairClipUrl(config: AvatarConfig, idsPrefix?: string): string | null;
43
+ /** Clip reference for ears / mustache / beard, or null. */
44
+ export declare function helmetClipUrl(config: AvatarConfig, idsPrefix?: string): string | null;
45
+ /** CSS custom properties that drive every colour in the artwork. */
46
+ export declare function avatarCssVars(config: AvatarConfig): Record<string, string>;
47
+ /**
48
+ * The avatar markup WITHOUT the outer `<svg>` element.
49
+ * The preview component supplies its own root so it can keep Angular
50
+ * bindings on it; the exporter wraps this in a standalone document.
51
+ */
52
+ export declare function buildAvatarSvgInner(config: AvatarConfig, opts?: BuildAvatarOptions): string;
53
+ /**
54
+ * A complete, standalone SVG document. This is what gets written to disk
55
+ * and what the demo viewer renders — it must depend on nothing outside
56
+ * itself, so the palette is inlined as a `style` attribute on the root.
57
+ *
58
+ * `animated` is forced off regardless of what the caller passed. That flag
59
+ * emits the `.animate-idle` class, which is driven by the consuming
60
+ * component's stylesheet; a standalone file has no stylesheet, so the class
61
+ * would be a promise the document cannot keep. `speech` still applies,
62
+ * because SMIL travels inside the file.
63
+ */
64
+ export declare function buildAvatarSvg(config: AvatarConfig, opts?: BuildAvatarOptions): string;
@@ -0,0 +1,24 @@
1
+ export type Gender = 'man' | 'woman';
2
+ export type SkinTone = 'light' | 'medium' | 'tan' | 'dark' | 'deep';
3
+ export type HaircutStyle = 'short' | 'long' | 'curly' | 'bald' | 'bun' | 'ponytail' | 'mohawk';
4
+ export type MustacheStyle = 'none' | 'thin' | 'thick' | 'handlebar' | 'chevron';
5
+ export type BeardStyle = 'none' | 'stubble' | 'short' | 'long' | 'goatee';
6
+ export type EyeStyle = 'round' | 'almond' | 'wide' | 'narrow';
7
+ export type GlassesStyle = 'none' | 'round' | 'rectangular' | 'sunglasses' | 'monocle';
8
+ export type ProfessionType = 'none' | 'doctor' | 'engineer' | 'teacher' | 'chef' | 'police' | 'astronaut' | 'artist' | 'business';
9
+ export type HairColor = 'black' | 'brown' | 'blonde' | 'red' | 'gray' | 'white';
10
+ export type EyeColor = 'brown' | 'blue' | 'green' | 'gray' | 'black';
11
+ export interface AvatarConfig {
12
+ id: string;
13
+ name: string;
14
+ gender: Gender;
15
+ skinTone: SkinTone;
16
+ haircut: HaircutStyle;
17
+ hairColor: HairColor;
18
+ eyeColor: EyeColor;
19
+ mustache: MustacheStyle;
20
+ beard: BeardStyle;
21
+ eyeStyle: EyeStyle;
22
+ glasses: GlassesStyle;
23
+ profession: ProfessionType;
24
+ }
@@ -0,0 +1,2 @@
1
+ import { EyeColor } from './avatar.model';
2
+ export declare const EYE_COLORS: Record<EyeColor, string>;
@@ -0,0 +1,2 @@
1
+ import { HairColor } from './avatar.model';
2
+ export declare const HAIR_COLORS: Record<HairColor, string>;