@dotcraft/avatar 0.7.1 → 0.7.3
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.
- package/README.md +18 -20
- package/dist/AnimatedDecoration.d.ts +2 -2
- package/dist/AnimatedDecoration.d.ts.map +1 -1
- package/dist/AnimatedDecoration.js +2 -2
- package/dist/AnimatedDecoration.js.map +1 -1
- package/dist/AppearanceRig.d.ts +7 -1
- package/dist/AppearanceRig.d.ts.map +1 -1
- package/dist/AppearanceRig.js +2 -2
- package/dist/AppearanceRig.js.map +1 -1
- package/dist/Avatar.d.ts +2 -0
- package/dist/Avatar.d.ts.map +1 -1
- package/dist/Avatar.js +19 -7
- package/dist/Avatar.js.map +1 -1
- package/dist/BackDecorations.d.ts +9 -0
- package/dist/BackDecorations.d.ts.map +1 -0
- package/dist/BackDecorations.js +73 -0
- package/dist/BackDecorations.js.map +1 -0
- package/dist/DecorationShapes.d.ts +9 -2
- package/dist/DecorationShapes.d.ts.map +1 -1
- package/dist/DecorationShapes.js +8 -2
- package/dist/DecorationShapes.js.map +1 -1
- package/dist/Decorations.d.ts +6 -9
- package/dist/Decorations.d.ts.map +1 -1
- package/dist/Decorations.js +33 -16
- package/dist/Decorations.js.map +1 -1
- package/dist/FaceDecorations.d.ts +14 -0
- package/dist/FaceDecorations.d.ts.map +1 -0
- package/dist/FaceDecorations.js +69 -0
- package/dist/FaceDecorations.js.map +1 -0
- package/dist/HandDecorations.d.ts +5 -0
- package/dist/HandDecorations.d.ts.map +1 -0
- package/dist/HandDecorations.js +8 -0
- package/dist/HandDecorations.js.map +1 -0
- package/dist/HatDecorations.d.ts +2 -2
- package/dist/HatDecorations.d.ts.map +1 -1
- package/dist/HatDecorations.js +4 -0
- package/dist/HatDecorations.js.map +1 -1
- package/dist/MascotRig.d.ts +13 -1
- package/dist/MascotRig.d.ts.map +1 -1
- package/dist/MascotRig.js +6 -26
- package/dist/MascotRig.js.map +1 -1
- package/dist/ObjectDecorations.d.ts +2 -2
- package/dist/ObjectDecorations.d.ts.map +1 -1
- package/dist/ObjectDecorations.js +18 -2
- package/dist/ObjectDecorations.js.map +1 -1
- package/dist/RigMaterial.d.ts +18 -0
- package/dist/RigMaterial.d.ts.map +1 -0
- package/dist/RigMaterial.js +9 -0
- package/dist/RigMaterial.js.map +1 -0
- package/dist/SkinDecorations.d.ts +10 -0
- package/dist/SkinDecorations.d.ts.map +1 -0
- package/dist/SkinDecorations.js +28 -0
- package/dist/SkinDecorations.js.map +1 -0
- package/dist/accessories.css +1 -1
- package/dist/appearanceModel.d.ts +18 -14
- package/dist/appearanceModel.d.ts.map +1 -1
- package/dist/appearanceModel.js +62 -30
- package/dist/appearanceModel.js.map +1 -1
- package/dist/composer/styles/energy.css +34 -0
- package/dist/decorationCatalog.d.ts +17 -7
- package/dist/decorationCatalog.d.ts.map +1 -1
- package/dist/decorationCatalog.js +95 -36
- package/dist/decorationCatalog.js.map +1 -1
- package/dist/decorationMotion.d.ts +2 -2
- package/dist/decorationMotion.d.ts.map +1 -1
- package/dist/decorationMotion.js +4 -1
- package/dist/decorationMotion.js.map +1 -1
- package/dist/effects.css +86 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/items.d.ts +460 -0
- package/dist/items.d.ts.map +1 -0
- package/dist/items.js +52 -0
- package/dist/items.js.map +1 -0
- package/dist/paintMaterials.d.ts +31 -0
- package/dist/paintMaterials.d.ts.map +1 -0
- package/dist/paintMaterials.js +10 -0
- package/dist/paintMaterials.js.map +1 -0
- package/dist/palette.d.ts +2 -0
- package/dist/palette.d.ts.map +1 -1
- package/dist/palette.js +4 -4
- package/dist/palette.js.map +1 -1
- package/dist/react.d.ts +2 -2
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +2 -2
- package/dist/react.js.map +1 -1
- package/dist/rig.css +6 -21
- package/dist/styles.css +1 -0
- package/package.json +1 -1
- package/src/AnimatedDecoration.tsx +4 -4
- package/src/AppearanceRig.tsx +5 -4
- package/src/Avatar.tsx +25 -11
- package/src/BackDecorations.tsx +134 -0
- package/src/DecorationShapes.tsx +11 -3
- package/src/Decorations.tsx +47 -22
- package/src/FaceDecorations.tsx +145 -0
- package/src/HandDecorations.tsx +113 -0
- package/src/HatDecorations.tsx +29 -2
- package/src/MascotRig.tsx +27 -44
- package/src/ObjectDecorations.tsx +69 -3
- package/src/RigMaterial.tsx +23 -0
- package/src/SkinDecorations.tsx +56 -0
- package/src/accessories.css +1 -1
- package/src/appearanceModel.ts +57 -37
- package/src/composer/styles/energy.css +34 -0
- package/src/decorationCatalog.ts +103 -38
- package/src/decorationMotion.ts +6 -3
- package/src/effects.css +86 -0
- package/src/index.ts +2 -0
- package/src/items.ts +86 -0
- package/src/paintMaterials.ts +18 -0
- package/src/palette.ts +7 -3
- package/src/react.ts +2 -2
- package/src/rig.css +6 -21
- package/src/styles.css +1 -0
- package/dist/HeldDecorations.d.ts +0 -5
- package/dist/HeldDecorations.d.ts.map +0 -1
- package/dist/HeldDecorations.js +0 -5
- package/dist/HeldDecorations.js.map +0 -1
- package/src/HeldDecorations.tsx +0 -48
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { useId, type ReactNode } from 'react'
|
|
2
|
+
import type { FaceId, Zone } from './items.js'
|
|
3
|
+
import { itemOf } from './items.js'
|
|
4
|
+
import type { Faceplate } from './MascotRig.js'
|
|
5
|
+
import { useClipId } from './DecorationShapes.js'
|
|
6
|
+
|
|
7
|
+
export function FaceDecoration({ id }: { id: FaceId }) {
|
|
8
|
+
switch (id) {
|
|
9
|
+
case 'forehead-goggles': return <g strokeLinejoin="round">
|
|
10
|
+
<path d="M374 419h109v46H374Zm167 0h109v46H541Z" fill="#a2c5d1" stroke="#8b7568" strokeWidth="14" />
|
|
11
|
+
<path d="M483 442q29 20 58 0" stroke="#8b7568" strokeWidth="11" fill="none" />
|
|
12
|
+
<path d="m389 429 18 21m149-21 18 21" stroke="#e0eff1" strokeWidth="8" />
|
|
13
|
+
</g>
|
|
14
|
+
case 'shades': return <>
|
|
15
|
+
<rect x="372" y="416" width="116" height="44" rx="16" fill="#2b2f3a" stroke="#fff" strokeWidth="12" paintOrder="stroke fill" />
|
|
16
|
+
<rect x="536" y="416" width="116" height="44" rx="16" fill="#2b2f3a" stroke="#fff" strokeWidth="12" paintOrder="stroke fill" />
|
|
17
|
+
<path d="M488 432h48" stroke="#2b2f3a" strokeWidth="12" strokeLinecap="round" />
|
|
18
|
+
<path d="M386 428h44M550 428h44" stroke="#6b7280" strokeWidth="10" strokeLinecap="round" />
|
|
19
|
+
</>
|
|
20
|
+
default: return null
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function isFaceplate(id: FaceId): boolean { return (itemOf(id).zones as readonly Zone[]).includes('screen') }
|
|
25
|
+
|
|
26
|
+
function useGlowFilter() {
|
|
27
|
+
const id = `dca-mask-glow-${useId().replace(/:/g, '')}`
|
|
28
|
+
return { id, defs: <defs><filter id={id} x="-30%" y="-60%" width="160%" height="220%"><feGaussianBlur stdDeviation="14" /></filter></defs> }
|
|
29
|
+
}
|
|
30
|
+
function Light({ name, filter, glow, core, glowOpacity = .85, coreOpacity = 1, effect = 'dca-fx-pulse', children }: {
|
|
31
|
+
name: string; filter: string; glow: string; core: string; glowOpacity?: number; coreOpacity?: number; effect?: string; children: ReactNode
|
|
32
|
+
}) {
|
|
33
|
+
return <g className={`dca-part-face dca-part-face-${name}`} data-mask-face={name}>
|
|
34
|
+
<g className={`dca-fx ${effect}`} filter={`url(#${filter})`} fill={glow} stroke={glow} opacity={glowOpacity}>{children}</g>
|
|
35
|
+
<g className="dca-part-eyes" fill={core} stroke={core} opacity={coreOpacity}>{children}</g>
|
|
36
|
+
</g>
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const goldSlits = 'M364 592 458 578 462 630 368 646ZM660 592 566 578 562 630 656 646Z'
|
|
40
|
+
function GoldEyes() {
|
|
41
|
+
const glow = useGlowFilter()
|
|
42
|
+
const clip = useClipId()
|
|
43
|
+
return <>
|
|
44
|
+
{glow.defs}
|
|
45
|
+
<defs><clipPath id={clip}><path d={goldSlits} /></clipPath></defs>
|
|
46
|
+
<path d={goldSlits} fill="#2b2010" />
|
|
47
|
+
<Light name="neutral" filter={glow.id} glow="#6fdcff" core="#dff7ff" coreOpacity={.92}><path d={goldSlits} /><path d="M376 612h76M572 612h76" fill="none" stroke="#fff" strokeWidth="8" strokeLinecap="round" /></Light>
|
|
48
|
+
<Light name="happy" filter={glow.id} glow="#ffd970" core="#fff6d6" glowOpacity={1} effect="dca-fx-flare"><path d={goldSlits} /><path d="M372 612h84M568 612h84" fill="none" stroke="#fff" strokeWidth="14" strokeLinecap="round" /></Light>
|
|
49
|
+
<Light name="operator" filter={glow.id} glow="#ffb547" core="#ffd9a0" glowOpacity={.7} effect="dca-fx-none"><path d={goldSlits} /></Light>
|
|
50
|
+
<g className="dca-part-face dca-part-face-operator" data-mask-face="operator-scan" clipPath={`url(#${clip})`}>
|
|
51
|
+
<g className="dca-fx dca-fx-scan-eyes"><rect x="360" y="570" width="10" height="84" fill="#fff" opacity=".95" /><rect x="352" y="570" width="26" height="84" fill="#fff" opacity=".35" /></g>
|
|
52
|
+
</g>
|
|
53
|
+
<Light name="sleep" filter={glow.id} glow="#6fdcff" core="#6fdcff" glowOpacity={.3} coreOpacity={.28} effect="dca-fx-breathe"><path d={goldSlits} /></Light>
|
|
54
|
+
</>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function NeonEyes() {
|
|
58
|
+
const glow = useGlowFilter()
|
|
59
|
+
return <>
|
|
60
|
+
{glow.defs}
|
|
61
|
+
<Light name="neutral" filter={glow.id} glow="#4de3ff" core="#dffaff"><rect x="398" y="578" width="64" height="80" rx="24" /><rect x="562" y="578" width="64" height="80" rx="24" /></Light>
|
|
62
|
+
<Light name="happy" filter={glow.id} glow="#4de3ff" core="#eefdff" glowOpacity={1} effect="dca-fx-flare">
|
|
63
|
+
<path d="M398 602a24 24 0 0 1 24-24h16a24 24 0 0 1 24 24v30q-32-22-64 0Z" /><path d="M562 602a24 24 0 0 1 24-24h16a24 24 0 0 1 24 24v30q-32-22-64 0Z" />
|
|
64
|
+
</Light>
|
|
65
|
+
<Light name="operator" filter={glow.id} glow="#4de3ff" core="#dffaff" glowOpacity={.75} effect="dca-fx-none"><rect x="406" y="606" width="64" height="52" rx="12" /><rect x="554" y="606" width="64" height="52" rx="12" /></Light>
|
|
66
|
+
<Light name="sleep" filter={glow.id} glow="#4de3ff" core="#4de3ff" glowOpacity={.3} coreOpacity={.35} effect="dca-fx-breathe"><rect x="398" y="652" width="64" height="10" rx="5" /><rect x="562" y="652" width="64" height="10" rx="5" /></Light>
|
|
67
|
+
</>
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function Plate({ fill, children }: { fill: string; children?: ReactNode }) {
|
|
71
|
+
const clip = useClipId()
|
|
72
|
+
return <>
|
|
73
|
+
<defs><clipPath id={clip}><rect x="307" y="476" width="410" height="291" rx="68" /></clipPath></defs>
|
|
74
|
+
<rect x="307" y="476" width="410" height="291" rx="68" fill={fill} />
|
|
75
|
+
{children && <g clipPath={`url(#${clip})`}>{children}</g>}
|
|
76
|
+
</>
|
|
77
|
+
}
|
|
78
|
+
const topLight = (fill: string, opacity: number) => <path d="M307 544v-68h410v68q-205-44-410 0Z" fill={fill} opacity={opacity} />
|
|
79
|
+
|
|
80
|
+
const mechaEyes = 'M356 604 448 614 452 648 362 636ZM668 604 576 614 572 648 662 636Z'
|
|
81
|
+
function MechaEyes() {
|
|
82
|
+
const glow = useGlowFilter()
|
|
83
|
+
const clip = useClipId()
|
|
84
|
+
return <>
|
|
85
|
+
{glow.defs}
|
|
86
|
+
<defs><clipPath id={clip}><path d={mechaEyes} /></clipPath></defs>
|
|
87
|
+
<Light name="neutral" filter={glow.id} glow="#d9ff5a" core="#f6ffb0"><path d={mechaEyes} /></Light>
|
|
88
|
+
<Light name="happy" filter={glow.id} glow="#f6ffb0" core="#ffffff" glowOpacity={1} effect="dca-fx-flare"><path d={mechaEyes} /></Light>
|
|
89
|
+
<Light name="operator" filter={glow.id} glow="#ffb547" core="#ffd9a0" glowOpacity={.75} effect="dca-fx-none"><path d={mechaEyes} /></Light>
|
|
90
|
+
<g className="dca-part-face dca-part-face-operator" data-mask-face="operator-scan" clipPath={`url(#${clip})`}>
|
|
91
|
+
<g className="dca-fx dca-fx-scan-eyes"><rect x="352" y="596" width="10" height="60" fill="#fff" opacity=".95" /><rect x="344" y="596" width="26" height="60" fill="#fff" opacity=".35" /></g>
|
|
92
|
+
</g>
|
|
93
|
+
<Light name="sleep" filter={glow.id} glow="#d9ff5a" core="#d9ff5a" glowOpacity={.25} coreOpacity={.3} effect="dca-fx-breathe"><path d={mechaEyes} /></Light>
|
|
94
|
+
</>
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function PixelEyes() {
|
|
98
|
+
const glow = useGlowFilter()
|
|
99
|
+
const block = (x: number, y: number, w = 48, h = 48) => <rect key={`${x}-${y}`} x={x} y={y} width={w} height={h} />
|
|
100
|
+
return <>
|
|
101
|
+
{glow.defs}
|
|
102
|
+
<Light name="neutral" filter={glow.id} glow="#7dff8a" core="#c8ffcf">{block(416, 596)}{block(560, 596)}</Light>
|
|
103
|
+
<Light name="happy" filter={glow.id} glow="#7dff8a" core="#c8ffcf" glowOpacity={1} effect="dca-fx-flare">{block(416, 580)}{block(560, 580)}{block(440, 664, 24, 24)}{block(560, 664, 24, 24)}{block(464, 688, 96, 24)}</Light>
|
|
104
|
+
<Light name="operator" filter={glow.id} glow="#7dff8a" core="#c8ffcf" glowOpacity={.75} effect="dca-fx-none">{block(404, 604, 44, 32)}{block(456, 604, 44, 32)}{block(508, 604, 44, 32)}<rect className="dca-fx dca-fx-node" x="560" y="604" width="44" height="32" /></Light>
|
|
105
|
+
<Light name="sleep" filter={glow.id} glow="#7dff8a" core="#7dff8a" glowOpacity={.25} coreOpacity={.45} effect="dca-fx-breathe">{block(416, 640, 48, 12)}{block(560, 640, 48, 12)}</Light>
|
|
106
|
+
</>
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function FaceplatePlate({ id }: { id: FaceId }) {
|
|
110
|
+
switch (id) {
|
|
111
|
+
case 'mecha-faceplate': return <g data-faceplate={id}><Plate fill="#f4f6fb">
|
|
112
|
+
<rect x="330" y="582" width="364" height="84" rx="14" fill="#1d2433" />
|
|
113
|
+
<path d="M470 700h84l12 67H458Z" fill="#d94a3a" />
|
|
114
|
+
<rect x="432" y="690" width="16" height="50" rx="4" fill="#1d2433" /><rect x="576" y="690" width="16" height="50" rx="4" fill="#1d2433" />
|
|
115
|
+
</Plate></g>
|
|
116
|
+
case 'pixel-screen': return <g data-faceplate={id}><Plate fill="#1f2a3a">{topLight('#33445a', .8)}</Plate></g>
|
|
117
|
+
case 'gold-faceplate': return <g data-faceplate={id}><Plate fill="#efc04a">
|
|
118
|
+
{topLight('#ffd970', .9)}
|
|
119
|
+
<path d="M307 700v67h410v-67q-205 40-410 0Z" fill="#d9a53a" opacity=".85" />
|
|
120
|
+
</Plate></g>
|
|
121
|
+
case 'neon-visor': return <g data-faceplate={id}><Plate fill="#1d2433">
|
|
122
|
+
<path d="M307 476h410v70q-205-34-410 0Z" fill="#3a4358" opacity=".55" />
|
|
123
|
+
<g className="dca-fx dca-fx-scan">
|
|
124
|
+
<rect x="330" y="476" width="46" height="291" fill="#4de3ff" opacity=".16" />
|
|
125
|
+
<rect x="348" y="476" width="10" height="291" fill="#4de3ff" opacity=".45" />
|
|
126
|
+
</g>
|
|
127
|
+
</Plate></g>
|
|
128
|
+
default: return null
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function FaceplateEyes({ id }: { id: FaceId }) {
|
|
133
|
+
switch (id) {
|
|
134
|
+
case 'mecha-faceplate': return <MechaEyes />
|
|
135
|
+
case 'pixel-screen': return <PixelEyes />
|
|
136
|
+
case 'gold-faceplate': return <GoldEyes />
|
|
137
|
+
case 'neon-visor': return <NeonEyes />
|
|
138
|
+
default: return null
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function faceplateOf(id: FaceId | 'none'): Faceplate | undefined {
|
|
143
|
+
if (id === 'none' || !isFaceplate(id)) return undefined
|
|
144
|
+
return { plate: <FaceplatePlate id={id} />, eyes: <FaceplateEyes id={id} /> }
|
|
145
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { HandId } from './items.js'
|
|
2
|
+
import { Glow } from './DecorationShapes.js'
|
|
3
|
+
|
|
4
|
+
const mark = 'var(--dca-held-mark, #3161f7)'
|
|
5
|
+
const accent = 'var(--dca-held-accent, #f6b500)'
|
|
6
|
+
|
|
7
|
+
export function HandDecoration({ id }: { id: HandId }) {
|
|
8
|
+
return <g data-held-decoration={id} strokeLinecap="round" strokeLinejoin="round">
|
|
9
|
+
{id === 'task-board' && <g transform="translate(233 640) scale(.8) translate(-840 -665)">
|
|
10
|
+
<rect x="718" y="562" width="244" height="194" rx="36" fill="#fff" stroke="#fff" strokeWidth="30" />
|
|
11
|
+
<rect x="718" y="562" width="244" height="194" rx="36" fill="#fff" stroke={mark} strokeWidth="11" />
|
|
12
|
+
<path d="M760 620h114" stroke={mark} strokeWidth="18" />
|
|
13
|
+
<circle cx="775" cy="689" r="14" fill={mark} />
|
|
14
|
+
<circle cx="842" cy="661" r="14" fill={accent} />
|
|
15
|
+
<circle cx="907" cy="708" r="14" fill={mark} />
|
|
16
|
+
<path d="M789 683 828 667 893 699" stroke="#202124" strokeWidth="13" opacity=".72" />
|
|
17
|
+
</g>}
|
|
18
|
+
{id === 'wrench' && <g transform="translate(233 670) rotate(-60) scale(.65) translate(-642 -817)">
|
|
19
|
+
<path d="M756 595c30-30 74-39 113-23l-56 56 46 46 56-56c16 39 7 83-23 113-32 32-80 39-119 20L674 850c-18 18-47 18-65 0s-18-47 0-65l99-99c-19-39-12-87 20-119Z" fill="#fff" stroke="#fff" strokeWidth="34" />
|
|
20
|
+
<path d="M756 595c30-30 74-39 113-23l-56 56 46 46 56-56c16 39 7 83-23 113-32 32-80 39-119 20L674 850c-18 18-47 18-65 0s-18-47 0-65l99-99c-19-39-12-87 20-119Z" fill="#fff" stroke={mark} strokeWidth="13" />
|
|
21
|
+
<circle cx="642" cy="817" r="17" fill={accent} />
|
|
22
|
+
<path d="M706 752 774 684" stroke={mark} strokeWidth="20" opacity=".78" />
|
|
23
|
+
</g>}
|
|
24
|
+
{id === 'shield' && <g transform="translate(233 635) scale(.8) translate(-817 -690)">
|
|
25
|
+
<path d="M817 553 929 594v76c0 80-46 134-112 162-66-28-112-82-112-162v-76Z" fill="#fff" stroke="#fff" strokeWidth="30" />
|
|
26
|
+
<path d="M817 553 929 594v76c0 80-46 134-112 162-66-28-112-82-112-162v-76Z" fill="#fff" stroke={mark} strokeWidth="11" />
|
|
27
|
+
<path d="M761 683 802 722 881 632" stroke={mark} strokeWidth="28" />
|
|
28
|
+
</g>}
|
|
29
|
+
{id === 'magnifier' && <g transform="translate(233 670) rotate(-18) scale(.85) translate(-862 -735)">
|
|
30
|
+
<path d="M862 646v112" stroke="#fff" strokeWidth="48" />
|
|
31
|
+
<path d="M862 646v112" stroke={mark} strokeWidth="28" />
|
|
32
|
+
<circle cx="862" cy="562" r="82" fill="#fff" stroke="#fff" strokeWidth="30" />
|
|
33
|
+
<circle cx="862" cy="562" r="82" fill="#fff" fillOpacity=".72" stroke={mark} strokeWidth="18" />
|
|
34
|
+
<path d="M828 534c21-22 54-30 82-19" stroke="#fff" strokeWidth="12" opacity=".9" />
|
|
35
|
+
</g>}
|
|
36
|
+
{id === 'control-panel' && <g transform="translate(233 640) scale(.8) translate(-839 -686)">
|
|
37
|
+
<rect x="714" y="592" width="250" height="190" rx="38" fill="#fff" stroke="#fff" strokeWidth="30" />
|
|
38
|
+
<rect x="714" y="592" width="250" height="190" rx="38" fill="#fff" stroke={mark} strokeWidth="11" />
|
|
39
|
+
<path d="M765 650h148" stroke="#202124" strokeWidth="14" opacity=".62" />
|
|
40
|
+
<path d="M765 718h148" stroke="#202124" strokeWidth="14" opacity=".62" />
|
|
41
|
+
<circle cx="818" cy="650" r="23" fill={mark} stroke="#fff" strokeWidth="8" />
|
|
42
|
+
<circle cx="872" cy="718" r="23" fill={accent} stroke="#fff" strokeWidth="8" />
|
|
43
|
+
</g>}
|
|
44
|
+
{id === 'coffee-mug' && <g transform="translate(233 640) scale(1.05) translate(-800 -705)">
|
|
45
|
+
<g className="dca-fx dca-fx-steam" fill="none" stroke="#b9c2d1" strokeWidth="10" opacity=".75">
|
|
46
|
+
<path d="M772 628c10-14-2-26 8-40" /><path d="M800 620c10-14-2-26 8-40" /><path d="M828 628c10-14-2-26 8-40" />
|
|
47
|
+
</g>
|
|
48
|
+
<path d="M852 676a34 34 0 1 1 0 68" fill="none" stroke="#fff" strokeWidth="34" />
|
|
49
|
+
<path d="M852 676a34 34 0 1 1 0 68" fill="none" stroke={mark} strokeWidth="13" />
|
|
50
|
+
<rect x="748" y="650" width="104" height="112" rx="18" fill="#fff" stroke="#fff" strokeWidth="30" />
|
|
51
|
+
<rect x="748" y="650" width="104" height="112" rx="18" fill="#fff" stroke={mark} strokeWidth="11" />
|
|
52
|
+
<rect x="748" y="694" width="104" height="22" fill={mark} />
|
|
53
|
+
<ellipse cx="800" cy="656" rx="42" ry="10" fill="#5a3c2a" />
|
|
54
|
+
</g>}
|
|
55
|
+
{id === 'magic-wand' && <g transform="translate(233 640) rotate(-20) scale(1.1) translate(-800 -720)">
|
|
56
|
+
<g className="dca-fx dca-fx-sparkle" fill="#fff3c4">
|
|
57
|
+
<path d="M748 560l6 14 14 6-14 6-6 14-6-14-14-6 14-6Z" />
|
|
58
|
+
<path d="M856 556l5 11 11 5-11 5-5 11-5-11-11-5 11-5Z" style={{ animationDelay: '.6s' }} />
|
|
59
|
+
<path d="M838 636l4 9 9 4-9 4-4 9-4-9-9-4 9-4Z" style={{ animationDelay: '1.1s' }} />
|
|
60
|
+
</g>
|
|
61
|
+
<path d="M800 604v168" stroke="#fff" strokeWidth="40" />
|
|
62
|
+
<path d="M800 604v168" stroke="#3b2f5c" strokeWidth="22" />
|
|
63
|
+
<path d="M800 551 808 574 832 574 813 589 820 612 800 599 780 612 787 589 768 574 792 574Z" fill="#f6b500" stroke="#fff" strokeWidth="14" paintOrder="stroke fill" />
|
|
64
|
+
</g>}
|
|
65
|
+
{id === 'energy-blade' && <g transform="translate(233 640) rotate(-12) scale(.9) translate(-800 -760)">
|
|
66
|
+
<g className="dca-fx-blade" style={{ transformOrigin: '800px 716px' }}>
|
|
67
|
+
<Glow blur={14} className="dca-fx-pulse"><rect className="dca-fx-blade-color" x="778" y="420" width="44" height="300" rx="22" fill="#4de3ff" opacity=".7" /></Glow>
|
|
68
|
+
<rect className="dca-fx-blade-color" x="786" y="420" width="28" height="298" rx="14" fill="#4de3ff" stroke="#fff" strokeWidth="10" paintOrder="stroke fill" />
|
|
69
|
+
<rect x="795" y="432" width="10" height="276" rx="5" fill="#e6fbff" />
|
|
70
|
+
</g>
|
|
71
|
+
<rect x="777" y="712" width="46" height="18" rx="6" fill="#8b95a5" stroke="#fff" strokeWidth="10" paintOrder="stroke fill" />
|
|
72
|
+
<rect x="783" y="726" width="34" height="90" rx="12" fill="#2a3140" stroke="#fff" strokeWidth="14" paintOrder="stroke fill" />
|
|
73
|
+
</g>}
|
|
74
|
+
{id === 'paintbrush' && <g transform="translate(233 640) rotate(-25) translate(-800 -720)">
|
|
75
|
+
<rect x="788" y="660" width="24" height="180" rx="12" fill="#e0ad84" stroke="#fff" strokeWidth="14" paintOrder="stroke fill" />
|
|
76
|
+
<rect x="780" y="626" width="40" height="40" rx="6" fill="#8b95a5" stroke="#fff" strokeWidth="12" paintOrder="stroke fill" />
|
|
77
|
+
<path d="M778 630h44l6-50h-56Z" fill="#f1c8a2" stroke="#fff" strokeWidth="12" strokeLinejoin="round" paintOrder="stroke fill" />
|
|
78
|
+
<path d="M774 598h52l2-18h-56Z" fill={mark} />
|
|
79
|
+
</g>}
|
|
80
|
+
{id === 'boba-tea' && <g transform="translate(233 640) scale(1.2) translate(-800 -712)">
|
|
81
|
+
<rect x="812" y="576" width="16" height="100" rx="8" fill={accent} stroke="#fff" strokeWidth="10" paintOrder="stroke fill" />
|
|
82
|
+
<path d="M758 640h84l-10 140h-64Z" fill="#fff" stroke="#fff" strokeWidth="14" strokeLinejoin="round" paintOrder="stroke fill" />
|
|
83
|
+
<path d="M762 696h76l-6 84h-64Z" fill="#e0ad84" />
|
|
84
|
+
<g fill="#3c4658"><circle cx="782" cy="764" r="8" /><circle cx="800" cy="768" r="8" /><circle cx="818" cy="764" r="8" /><circle cx="791" cy="748" r="8" /></g>
|
|
85
|
+
<ellipse cx="800" cy="640" rx="46" ry="10" fill="#fff" stroke={mark} strokeWidth="6" />
|
|
86
|
+
</g>}
|
|
87
|
+
{id === 'gamepad' && <g transform="translate(233 640) scale(1.05) translate(-800 -695)">
|
|
88
|
+
<rect x="720" y="650" width="160" height="92" rx="40" fill="#fff" stroke="#fff" strokeWidth="30" />
|
|
89
|
+
<rect x="720" y="650" width="160" height="92" rx="40" fill="#fff" stroke={mark} strokeWidth="11" />
|
|
90
|
+
<path d="M752 690h12v-12h14v12h12v14h-12v12h-14v-12h-12Z" fill={mark} />
|
|
91
|
+
<circle cx="834" cy="684" r="10" fill={accent} /><circle cx="856" cy="704" r="10" fill={mark} />
|
|
92
|
+
</g>}
|
|
93
|
+
{id === 'flag' && <g transform="translate(233 640) rotate(-8) scale(.95) translate(-800 -760)">
|
|
94
|
+
<g className="dca-fx-wave" style={{ transformOrigin: '808px 600px' }}>
|
|
95
|
+
<path d="M808 566 934 602 808 640Z" fill={mark} stroke="#fff" strokeWidth="14" strokeLinejoin="round" paintOrder="stroke fill" />
|
|
96
|
+
</g>
|
|
97
|
+
<rect x="794" y="556" width="14" height="270" rx="7" fill="#8b95a5" stroke="#fff" strokeWidth="12" paintOrder="stroke fill" />
|
|
98
|
+
</g>}
|
|
99
|
+
{id === 'lantern' && <g transform="translate(233 640) scale(1.05) translate(-800 -690)">
|
|
100
|
+
<path d="M776 604a24 24 0 0 1 48 0" fill="none" stroke="#3c4658" strokeWidth="10" strokeLinecap="round" />
|
|
101
|
+
<rect x="752" y="616" width="96" height="124" rx="32" fill="#e8654f" stroke="#fff" strokeWidth="14" paintOrder="stroke fill" />
|
|
102
|
+
<Glow blur={14} className="dca-fx-pulse"><rect x="776" y="640" width="48" height="76" rx="18" fill="#ffd970" opacity=".9" /></Glow>
|
|
103
|
+
<rect x="776" y="640" width="48" height="76" rx="18" fill="#ffd970" />
|
|
104
|
+
<rect x="770" y="604" width="60" height="20" rx="6" fill="#3c4658" /><rect x="770" y="732" width="60" height="16" rx="6" fill="#3c4658" />
|
|
105
|
+
</g>}
|
|
106
|
+
{id === 'staff' && <g transform="translate(233 640) rotate(-10) translate(-800 -780)">
|
|
107
|
+
<rect x="792" y="560" width="16" height="284" rx="8" fill="#8a6520" stroke="#fff" strokeWidth="14" paintOrder="stroke fill" />
|
|
108
|
+
<Glow blur={16} className="dca-fx-pulse"><circle className="dca-fx-orb-color" cx="800" cy="544" r="40" fill="#8b5cf6" opacity=".8" /></Glow>
|
|
109
|
+
<circle className="dca-fx-orb-color" cx="800" cy="544" r="34" fill="#8b5cf6" stroke="#fff" strokeWidth="12" paintOrder="stroke fill" />
|
|
110
|
+
<circle cx="788" cy="532" r="10" fill="#fff" opacity=".85" />
|
|
111
|
+
</g>}
|
|
112
|
+
</g>
|
|
113
|
+
}
|
package/src/HatDecorations.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { HeadId } from './items.js'
|
|
2
2
|
import { Detail, Silhouette as S } from './DecorationShapes.js'
|
|
3
3
|
|
|
4
|
-
export function HatDecoration({ id }: { id:
|
|
4
|
+
export function HatDecoration({ id }: { id: HeadId }) {
|
|
5
5
|
switch (id) {
|
|
6
6
|
case 'baseball-cap': return <>
|
|
7
7
|
<S d="M337 368c0-94 44-163 139-163 91 0 142 65 142 155 36 0 71 8 88 24 9 9-2 23-24 23H363c-24 0-31-15-26-39Z" fill="#e87967" />
|
|
@@ -70,6 +70,33 @@ export function HatDecoration({ id }: { id: PrimaryId }) {
|
|
|
70
70
|
<path d="m405 302 214 0 8 35c-72 11-150 11-230 0Z" fill="#c57568" /><ellipse cx="512" cy="258" rx="97" ry="16" fill="#f5dfb2" />
|
|
71
71
|
<Detail><path d="M334 379q177 26 356 0" stroke="#c49b5e" strokeWidth="10" strokeLinecap="round" /></Detail>
|
|
72
72
|
</>
|
|
73
|
+
case 'cowboy-hat': return <>
|
|
74
|
+
<S d="M300 376c60-26 100-30 110-34l-8-84c-2-30 40-56 110-56s112 26 110 56l-8 84c10 4 50 8 110 34 22 10 12 40-18 42-130-16-264-16-388 0-30-2-40-32-18-42Z" fill="#b98352" />
|
|
75
|
+
<path d="M300 376c60-26 100-30 110-34 62 14 142 14 204 0 10 4 50 8 110 34 22 10 12 40-18 42-130-16-264-16-388 0-30-2-40-32-18-42Z" fill="#a8734a" />
|
|
76
|
+
<path d="M408 338c62 14 142 14 204 0l-4 26c-62 12-134 12-196 0Z" fill="#6e4a2e" />
|
|
77
|
+
</>
|
|
78
|
+
case 'propeller-cap': return <>
|
|
79
|
+
<g className="dca-fx-spin-flat" style={{ transformOrigin: '512px 228px' }}>
|
|
80
|
+
<rect x="392" y="216" width="240" height="24" rx="12" fill="#c9d2ff" stroke="#fff" strokeWidth="14" paintOrder="stroke fill" />
|
|
81
|
+
</g>
|
|
82
|
+
<S d="M368 404c0-94 60-160 144-160s144 66 144 160Z" fill="#f6b500" />
|
|
83
|
+
<path d="M512 244c56 0 104 30 128 76l-128 84Z" fill="#e8654f" /><path d="M512 244c-56 0-104 30-128 76l128 84Z" fill="#e8654f" opacity=".55" />
|
|
84
|
+
<circle cx="512" cy="240" r="16" fill="#3c4658" stroke="#fff" strokeWidth="10" paintOrder="stroke fill" />
|
|
85
|
+
</>
|
|
86
|
+
case 'graduation-cap': return <>
|
|
87
|
+
<S d="M420 404v-30c0-38 40-66 92-66s92 28 92 66v30Z" fill="#3c4658" />
|
|
88
|
+
<S d="M512 250 700 316 512 382 324 316Z" fill="#2b2f3a" />
|
|
89
|
+
<g className="dca-fx-swing" style={{ transformOrigin: '512px 316px' }}>
|
|
90
|
+
<path d="M512 316 616 372" stroke="#fff" strokeWidth="24" strokeLinecap="round" /><path d="M512 316 616 372" stroke="#f6b500" strokeWidth="10" strokeLinecap="round" />
|
|
91
|
+
<circle cx="622" cy="386" r="18" fill="#f6b500" stroke="#fff" strokeWidth="10" paintOrder="stroke fill" />
|
|
92
|
+
</g>
|
|
93
|
+
<circle cx="512" cy="316" r="12" fill="#f6b500" />
|
|
94
|
+
</>
|
|
95
|
+
case 'pirate-hat': return <>
|
|
96
|
+
<S d="M312 392c40-96 116-160 200-160s160 64 200 160c-64 36-336 36-400 0Z" fill="#2b2f3a" />
|
|
97
|
+
<path d="M312 392c56-22 344-22 400 0-64 36-336 36-400 0Z" fill="#3c4658" />
|
|
98
|
+
<circle cx="512" cy="300" r="28" fill="#fff" /><circle cx="501" cy="296" r="5" fill="#2b2f3a" /><circle cx="523" cy="296" r="5" fill="#2b2f3a" />
|
|
99
|
+
</>
|
|
73
100
|
default: return null
|
|
74
101
|
}
|
|
75
102
|
}
|
package/src/MascotRig.tsx
CHANGED
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
import { useId, type CSSProperties, type JSX, type ReactNode } from 'react'
|
|
2
2
|
import { Faces } from './Faces.js'
|
|
3
3
|
import { mascotPaletteOf } from './palette.js'
|
|
4
|
-
|
|
5
|
-
// Paired arm layers share view-box pivots and paint rules in rig.css.
|
|
4
|
+
import { RigMaterial, RigMaterialClip } from './RigMaterial.js'
|
|
6
5
|
|
|
7
6
|
export type MascotExpression = 'neutral' | 'happy' | 'operator' | 'sleep'
|
|
8
7
|
|
|
9
8
|
export type MascotLight = 'default' | 'error' | 'success'
|
|
10
9
|
|
|
10
|
+
export interface BodyPaint {
|
|
11
|
+
render: (id: string) => ReactNode
|
|
12
|
+
shadow: string
|
|
13
|
+
}
|
|
14
|
+
export interface Faceplate {
|
|
15
|
+
plate: ReactNode
|
|
16
|
+
eyes: ReactNode
|
|
17
|
+
}
|
|
18
|
+
|
|
11
19
|
interface MascotRobotProps {
|
|
12
20
|
baseFace?: number
|
|
13
21
|
held?: ReactNode
|
|
14
22
|
accessory?: ReactNode
|
|
23
|
+
faceplate?: Faceplate
|
|
15
24
|
top?: ReactNode
|
|
25
|
+
back?: ReactNode
|
|
26
|
+
front?: ReactNode
|
|
16
27
|
surface?: ReactNode
|
|
28
|
+
paint?: BodyPaint
|
|
17
29
|
expression?: MascotExpression
|
|
18
30
|
light?: MascotLight
|
|
19
31
|
size?: number
|
|
@@ -26,34 +38,13 @@ interface MascotRobotProps {
|
|
|
26
38
|
avatar?: { palette: number }
|
|
27
39
|
}
|
|
28
40
|
|
|
29
|
-
function mixHex(a: string, b: string, amount: number): string {
|
|
30
|
-
const left = parseHex(a)
|
|
31
|
-
const right = parseHex(b)
|
|
32
|
-
const ratio = Math.max(0, Math.min(1, amount))
|
|
33
|
-
const mix = (from: number, to: number): number => Math.round(from + (to - from) * ratio)
|
|
34
|
-
return `#${toHex(mix(left.r, right.r))}${toHex(mix(left.g, right.g))}${toHex(mix(left.b, right.b))}`
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function parseHex(hex: string): { r: number; g: number; b: number } {
|
|
38
|
-
const normalized = hex.trim().replace(/^#/, '')
|
|
39
|
-
return {
|
|
40
|
-
r: Number.parseInt(normalized.slice(0, 2), 16),
|
|
41
|
-
g: Number.parseInt(normalized.slice(2, 4), 16),
|
|
42
|
-
b: Number.parseInt(normalized.slice(4, 6), 16)
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function toHex(value: number): string {
|
|
47
|
-
return value.toString(16).padStart(2, '0')
|
|
48
|
-
}
|
|
49
|
-
|
|
50
41
|
export function MascotRig({
|
|
51
42
|
expression = 'neutral',
|
|
52
43
|
light = 'default',
|
|
53
44
|
size = 48,
|
|
54
45
|
className,
|
|
55
46
|
style,
|
|
56
|
-
avatar, top, surface, baseFace, accessory, held
|
|
47
|
+
avatar, top, back, front, surface, paint, baseFace, accessory, faceplate, held
|
|
57
48
|
}: MascotRobotProps): JSX.Element {
|
|
58
49
|
const uid = useId().replace(/:/g, '')
|
|
59
50
|
const blue = `dca-part-blue-${uid}`
|
|
@@ -62,6 +53,7 @@ export function MascotRig({
|
|
|
62
53
|
const softShadow = `dca-part-soft-shadow-${uid}`
|
|
63
54
|
const innerLift = `dca-part-inner-lift-${uid}`
|
|
64
55
|
const laptopClip = `dca-part-laptop-clip-${uid}`
|
|
56
|
+
const materialClip = `dca-part-material-clip-${uid}`
|
|
65
57
|
const lightFill =
|
|
66
58
|
light === 'error' ? 'var(--dca-error, #dc2626)' : light === 'success' ? 'var(--dca-success, #16a34a)' : `url(#${yellow})`
|
|
67
59
|
const glowFill = light === 'error' ? 'var(--dca-error, #dc2626)' : light === 'success' ? 'var(--dca-success, #16a34a)' : '#f6b500'
|
|
@@ -73,18 +65,14 @@ export function MascotRig({
|
|
|
73
65
|
const mark0 = palette.markD
|
|
74
66
|
const mark1 = palette.markM
|
|
75
67
|
const mark2 = palette.markL
|
|
76
|
-
const softShadowColor = palette.shadow
|
|
77
|
-
const innerLiftColor = avatar ? palette.shadow : '#163a88'
|
|
78
|
-
const raisedArmLeft = avatar ? mixHex(palette.bodyD, palette.bodyM, 0.22) : '#3161f7'
|
|
79
|
-
const raisedArmRight = avatar ? mixHex(palette.bodyM, palette.bodyL, 0.56) : '#7a96fb'
|
|
68
|
+
const softShadowColor = paint?.shadow ?? palette.shadow
|
|
69
|
+
const innerLiftColor = paint?.shadow ?? (avatar ? palette.shadow : '#163a88')
|
|
80
70
|
const propMark = avatar ? palette.markD : '#3161f7'
|
|
81
71
|
const laptopLine = palette.markL
|
|
82
72
|
const svgStyle = {
|
|
83
73
|
'--dca-held-mark': `url(#${blueMark})`,
|
|
84
74
|
'--dca-held-accent': `url(#${yellow})`,
|
|
85
75
|
'--dca-part-body-paint': `url(#${blue})`,
|
|
86
|
-
'--dca-part-raised-arm-left': raisedArmLeft,
|
|
87
|
-
'--dca-part-raised-arm-right': raisedArmRight,
|
|
88
76
|
'--dca-part-shadow-color': softShadowColor,
|
|
89
77
|
overflow: 'visible',
|
|
90
78
|
...style
|
|
@@ -103,11 +91,12 @@ export function MascotRig({
|
|
|
103
91
|
aria-hidden="true"
|
|
104
92
|
>
|
|
105
93
|
<defs>
|
|
106
|
-
<linearGradient className="dca-paint-body" id={blue} x1="279" y1="766" x2="736" y2="334" gradientUnits="userSpaceOnUse">
|
|
94
|
+
{paint ? paint.render(blue) : <linearGradient className="dca-paint-body" id={blue} x1="279" y1="766" x2="736" y2="334" gradientUnits="userSpaceOnUse">
|
|
107
95
|
<stop offset="0" stopColor={body0} />
|
|
108
96
|
<stop offset=".46" stopColor={body1} />
|
|
109
97
|
<stop offset="1" stopColor={body2} />
|
|
110
|
-
</linearGradient>
|
|
98
|
+
</linearGradient>}
|
|
99
|
+
<RigMaterialClip id={materialClip} />
|
|
111
100
|
<linearGradient className="dca-paint-mark" id={blueMark} x1="380" y1="696" x2="492" y2="557" gradientUnits="userSpaceOnUse">
|
|
112
101
|
<stop offset="0" stopColor={mark0} />
|
|
113
102
|
<stop offset=".55" stopColor={mark1} />
|
|
@@ -129,6 +118,7 @@ export function MascotRig({
|
|
|
129
118
|
</defs>
|
|
130
119
|
|
|
131
120
|
<g transform="translate(512 528) scale(1.3) translate(-512 -512)">
|
|
121
|
+
{back}
|
|
132
122
|
<g filter={`url(#${softShadow})`}>
|
|
133
123
|
<rect x="201" y="365" width="622" height="513" rx="151" fill="#fff" />
|
|
134
124
|
{/* Centered on the blue band (233 / 791), not offset toward the body as the
|
|
@@ -139,23 +129,16 @@ export function MascotRig({
|
|
|
139
129
|
{!top && <circle className="dca-part-antenna-w" cx="512" cy="229" r="116" fill="#fff" />}
|
|
140
130
|
</g>
|
|
141
131
|
|
|
142
|
-
<
|
|
143
|
-
<rect x="243" y="408" width="538" height="426" rx="113" fill={`url(#${blue})`} />
|
|
144
|
-
<rect className="dca-part-arm-r-b" x="746" y="514" width="90" height="171" rx="19" fill={`url(#${blue})`} />
|
|
145
|
-
{!top && <rect x="479" y="337" width="66" height="119" rx="6" fill={`url(#${blue})`} />}
|
|
146
|
-
</g>
|
|
147
|
-
|
|
148
|
-
{surface}
|
|
132
|
+
<RigMaterial clipId={materialClip} paintId={blue} shadowId={innerLift} surface={surface} antenna={!top} />
|
|
149
133
|
<rect x="295" y="464" width="434" height="315" rx="78" fill="#fff" />
|
|
150
134
|
{!top && <circle className="dca-part-glow" cx="512" cy="229" r="96" fill={glowFill} />}
|
|
151
135
|
{top ?? <circle className="dca-part-light" cx="512" cy="229" r="73" fill={lightFill} />}
|
|
152
136
|
|
|
153
|
-
<g className="dca-part-arm-l">
|
|
154
|
-
<rect className="dca-part-arm-l-b" x="188" y="514" width="90" height="171" rx="19" fill={`url(#${blue})`} filter={`url(#${innerLift})`} />
|
|
155
|
-
{held}
|
|
156
|
-
</g>
|
|
137
|
+
{held && <g className="dca-part-arm-l">{held}</g>}
|
|
157
138
|
{accessory}
|
|
158
|
-
|
|
139
|
+
{faceplate?.plate}
|
|
140
|
+
<g className="dca-face-motion">{faceplate ? faceplate.eyes : <Faces baseFace={baseFace} mark={`url(#${blueMark})`} accent={`url(#${yellow})`} />}</g>
|
|
141
|
+
{front}
|
|
159
142
|
|
|
160
143
|
{/* A white frame would melt into the white face screen behind it, so the lid
|
|
161
144
|
is dark with a white stroke ring. */}
|
|
@@ -1,8 +1,74 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import { Detail, Silhouette as S } from './DecorationShapes.js'
|
|
1
|
+
import type { HeadId } from './items.js'
|
|
2
|
+
import { Detail, Glow, Silhouette as S, useClipId } from './DecorationShapes.js'
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
function RingedPlanet() {
|
|
5
|
+
const clip = useClipId()
|
|
6
|
+
return <>
|
|
7
|
+
<defs><clipPath id={clip}><circle cx="512" cy="300" r="92" /></clipPath></defs>
|
|
8
|
+
<Glow blur={22} className="dca-fx-pulse"><circle cx="512" cy="312" r="150" fill="#f1e3c8" opacity=".5" /></Glow>
|
|
9
|
+
<g transform="rotate(-16 512 335)">
|
|
10
|
+
<ellipse cx="512" cy="335" rx="175" ry="40" stroke="#fff" strokeWidth="40" fill="none" />
|
|
11
|
+
<ellipse cx="512" cy="335" rx="175" ry="40" stroke="#f1e3c8" strokeWidth="22" fill="none" />
|
|
12
|
+
</g>
|
|
13
|
+
<S d="M512 208a92 92 0 1 1 0 184a92 92 0 1 1 0-184Z" fill="#d9b07a" />
|
|
14
|
+
<g clipPath={`url(#${clip})`}>
|
|
15
|
+
<path d="M410 272c70 24 140 24 210 0v40c-70 24-140 24-210 0Z" fill="#b8875a" />
|
|
16
|
+
</g>
|
|
17
|
+
<g transform="rotate(-16 512 335)">
|
|
18
|
+
<path d="M337 335A175 40 0 0 0 687 335" stroke="#fff" strokeWidth="40" fill="none" />
|
|
19
|
+
<path d="M337 335A175 40 0 0 0 687 335" stroke="#f1e3c8" strokeWidth="22" fill="none" />
|
|
20
|
+
</g>
|
|
21
|
+
</>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function Ufo() {
|
|
25
|
+
return <g className="dca-fx-hover" style={{ transformOrigin: '512px 300px' }}>
|
|
26
|
+
<Glow blur={16} className="dca-fx-pulse"><path d="M452 336 402 404h220l-50-68Z" fill="#c7f6ff" opacity=".6" /></Glow>
|
|
27
|
+
<path d="M452 336 402 404h220l-50-68Z" fill="#c7f6ff" opacity=".4" />
|
|
28
|
+
<S d="M432 296c0-50 36-86 80-86s80 36 80 86Z" fill="#a2c5d1" />
|
|
29
|
+
<S d="M342 304c0-26 76-46 170-46s170 20 170 46-76 46-170 46-170-20-170-46Z" fill="#8b95a5" />
|
|
30
|
+
<g fill="#f6b500"><circle className="dca-fx dca-fx-node" cx="420" cy="312" r="11" /><circle className="dca-fx dca-fx-node" cx="512" cy="322" r="11" style={{ animationDelay: '.6s' }} /><circle className="dca-fx dca-fx-node" cx="604" cy="312" r="11" style={{ animationDelay: '1.2s' }} /></g>
|
|
31
|
+
</g>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function ObjectDecoration({ id }: { id: HeadId }) {
|
|
5
35
|
switch (id) {
|
|
36
|
+
case 'ringed-planet': return <RingedPlanet />
|
|
37
|
+
case 'cat-ears': return <>
|
|
38
|
+
<S d="M340 404 384 210 520 380Z" fill="#c9a27e" /><path d="M374 388 396 268 480 372Z" fill="#f2a0b4" />
|
|
39
|
+
<S d="M684 404 640 210 504 380Z" fill="#c9a27e" /><path d="M650 388 628 268 544 372Z" fill="#f2a0b4" />
|
|
40
|
+
</>
|
|
41
|
+
case 'mushroom': return <>
|
|
42
|
+
<rect x="470" y="356" width="84" height="52" rx="16" fill="#fff1dc" stroke="#fff" strokeWidth="18" paintOrder="stroke fill" />
|
|
43
|
+
<S d="M352 366c0-90 72-160 160-160s160 70 160 160c0 26-20 40-46 40H398c-26 0-46-14-46-40Z" fill="#e8654f" />
|
|
44
|
+
<g fill="#fff4ef"><circle cx="430" cy="300" r="24" /><circle cx="540" cy="266" r="20" /><circle cx="600" cy="334" r="18" /><circle cx="490" cy="350" r="16" /></g>
|
|
45
|
+
</>
|
|
46
|
+
case 'shark-fin': return <>
|
|
47
|
+
<S d="M446 404c10-92 42-166 104-214 22 62 72 134 118 214Z" fill="#8b95a5" />
|
|
48
|
+
<path d="M480 404c8-64 30-118 70-160 12 42 34 96 68 160Z" fill="#a7b1c0" />
|
|
49
|
+
</>
|
|
50
|
+
case 'ice-cream': return <>
|
|
51
|
+
<S d="M440 330 512 130 584 330Z" fill="#e0ad84" />
|
|
52
|
+
<S d="M512 258a82 82 0 1 1 0 164a82 82 0 1 1 0-164Z" fill="#f2a0b4" />
|
|
53
|
+
<circle cx="512" cy="120" r="20" fill="#e8654f" stroke="#fff" strokeWidth="10" paintOrder="stroke fill" />
|
|
54
|
+
</>
|
|
55
|
+
case 'flower-crown': return <>
|
|
56
|
+
<S d="M330 404c40-40 324-40 364 0v4H330Z" fill="#89b875" />
|
|
57
|
+
<g stroke="#fff" strokeWidth="12" paintOrder="stroke fill">
|
|
58
|
+
<circle cx="372" cy="392" r="26" fill="#f2a0b4" /><circle cx="442" cy="376" r="26" fill="#f6b500" /><circle cx="512" cy="370" r="28" fill="#f2a0b4" /><circle cx="582" cy="376" r="26" fill="#f6b500" /><circle cx="652" cy="392" r="26" fill="#f2a0b4" />
|
|
59
|
+
</g>
|
|
60
|
+
<g fill="#fff"><circle cx="372" cy="392" r="9" /><circle cx="442" cy="376" r="9" /><circle cx="512" cy="370" r="10" /><circle cx="582" cy="376" r="9" /><circle cx="652" cy="392" r="9" /></g>
|
|
61
|
+
</>
|
|
62
|
+
case 'lightning': return <>
|
|
63
|
+
<Glow blur={20} className="dca-fx-pulse"><path d="M548 160 438 320h70l-32 84 116-150h-70Z" fill="#fff3c4" opacity=".8" /></Glow>
|
|
64
|
+
<S d="M548 160 438 320h70l-32 84 116-150h-70Z" fill="#ffcf11" />
|
|
65
|
+
</>
|
|
66
|
+
case 'crystal-cluster': return <>
|
|
67
|
+
<Glow blur={20} className="dca-fx-pulse"><path d="M512 200 560 320 512 404 464 320Z" fill="#c4b5fd" opacity=".8" /></Glow>
|
|
68
|
+
<S d="M440 270 474 340 446 404 412 340Z" fill="#a78bfa" /><S d="M584 270 618 340 590 404 556 340Z" fill="#a78bfa" />
|
|
69
|
+
<S d="M512 200 560 320 512 404 464 320Z" fill="#8b5cf6" /><path d="M512 200 536 320 512 404Z" fill="#c4b5fd" />
|
|
70
|
+
</>
|
|
71
|
+
case 'ufo': return <Ufo />
|
|
6
72
|
case 'poop': return <>
|
|
7
73
|
<S d="M393 315c-17-37 10-65 57-67-12-33 15-48 39-53 21-5 29-17 27-35 52 20 69 43 55 75 47 0 72 35 52 73 70 9 81 92 8 92H389c-67 0-62-77 4-85Z" fill="#a97b5f" />
|
|
8
74
|
<path d="M397 313c64 19 157 22 227-5-9 32-171 60-227 5Zm53-65c35 16 80 15 121-13-1 31-82 49-121 13Z" fill="#805840" />
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
export const materialBounds = { x: 0, y: 0, width: 1024, height: 1024 } as const
|
|
4
|
+
|
|
5
|
+
export function RigMaterialClip({ id }: { id: string }) {
|
|
6
|
+
return <clipPath id={id} clipPathUnits="userSpaceOnUse">
|
|
7
|
+
<rect x="243" y="408" width="538" height="426" rx="113" />
|
|
8
|
+
<rect className="dca-part-arm-l-b" x="188" y="514" width="90" height="171" rx="19" />
|
|
9
|
+
<rect className="dca-part-arm-r-b" x="746" y="514" width="90" height="171" rx="19" />
|
|
10
|
+
</clipPath>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function RigMaterial({ clipId, paintId, shadowId, surface, antenna }: {
|
|
14
|
+
clipId: string; paintId: string; shadowId: string; surface?: ReactNode; antenna: boolean
|
|
15
|
+
}) {
|
|
16
|
+
return <g filter={`url(#${shadowId})`}>
|
|
17
|
+
<g className="dca-part-material" clipPath={`url(#${clipId})`}>
|
|
18
|
+
<rect {...materialBounds} fill={`url(#${paintId})`} />
|
|
19
|
+
{surface && <g className="dca-part-surface">{surface}</g>}
|
|
20
|
+
</g>
|
|
21
|
+
{antenna && <rect x="479" y="337" width="66" height="119" rx="6" fill={`url(#${paintId})`} />}
|
|
22
|
+
</g>
|
|
23
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import type { SkinId } from './items.js'
|
|
3
|
+
import type { BodyPaint } from './MascotRig.js'
|
|
4
|
+
import { isPaintSkin, paintMaterials, type PaintSkinId } from './paintMaterials.js'
|
|
5
|
+
import { materialBounds } from './RigMaterial.js'
|
|
6
|
+
|
|
7
|
+
const dots = [[280, 440], [372, 432], [464, 446], [556, 432], [648, 446], [740, 438], [268, 560], [758, 530], [262, 680], [762, 650], [292, 806], [400, 812], [512, 800], [624, 812], [732, 806], [268, 760]]
|
|
8
|
+
const star4 = (r: number) => `M0 ${-r}l${r * .28} ${r * .72}l${r * .72} ${r * .28}l${-r * .72} ${r * .28}l${-r * .28} ${r * .72}l${-r * .28} ${-r * .72}l${-r * .72} ${-r * .28}l${r * .72} ${-r * .28}Z`
|
|
9
|
+
|
|
10
|
+
export function SkinOverlay({ id }: { id: SkinId }) {
|
|
11
|
+
switch (id) {
|
|
12
|
+
case 'stripes': return <g fill="#fff" opacity=".34" data-skin-overlay={id}>
|
|
13
|
+
{[60, 250, 440, 630].map(x => <path key={x} d={`M${x} 834 L${x + 220} 408 h80 L${x + 80} 834Z`} />)}
|
|
14
|
+
</g>
|
|
15
|
+
default: return null
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const sheen = <g className="dca-fx dca-fx-sheen" fill="#fff">
|
|
20
|
+
<path d="M120 834 L420 408 h58 L178 834Z" opacity=".38" />
|
|
21
|
+
<path d="M200 834 L500 408 h22 L222 834Z" opacity=".55" />
|
|
22
|
+
</g>
|
|
23
|
+
|
|
24
|
+
const gradients: Record<PaintSkinId, (id: string) => ReactNode> = {
|
|
25
|
+
chrome: id => <linearGradient id={id} className="dca-skin-paint dca-skin-chrome" x1="279" y1="766" x2="736" y2="334" gradientUnits="userSpaceOnUse">
|
|
26
|
+
<stop offset="0" stopColor="#3c4658" /><stop offset=".36" stopColor="#9aa6b8" /><stop offset=".5" stopColor="#f4f7fb" />
|
|
27
|
+
<stop offset=".54" stopColor="#6b7688" /><stop offset=".8" stopColor="#c3ccd8" /><stop offset="1" stopColor="#ffffff" />
|
|
28
|
+
</linearGradient>,
|
|
29
|
+
holographic: id => <linearGradient id={id} className="dca-skin-paint dca-skin-holo" x1="279" y1="766" x2="736" y2="334" gradientUnits="userSpaceOnUse">
|
|
30
|
+
<stop offset="0" stopColor="#ff8ad4" /><stop offset=".25" stopColor="#ffd28a" /><stop offset=".5" stopColor="#c9ff8a" />
|
|
31
|
+
<stop offset=".75" stopColor="#8ad8ff" /><stop offset="1" stopColor="#c98aff" />
|
|
32
|
+
</linearGradient>,
|
|
33
|
+
gold: id => <linearGradient id={id} className="dca-skin-paint dca-skin-gold" x1="279" y1="766" x2="736" y2="334" gradientUnits="userSpaceOnUse">
|
|
34
|
+
<stop offset="0" stopColor="#b8862c" /><stop offset=".4" stopColor="#f6d365" /><stop offset=".52" stopColor="#fff3c4" />
|
|
35
|
+
<stop offset=".64" stopColor="#d9a53a" /><stop offset="1" stopColor="#ffe9a8" />
|
|
36
|
+
</linearGradient>,
|
|
37
|
+
lava: id => <linearGradient id={id} className="dca-skin-paint dca-skin-lava" x1="279" y1="766" x2="736" y2="334" gradientUnits="userSpaceOnUse">
|
|
38
|
+
<stop offset="0" stopColor="#4a1208" /><stop offset=".3" stopColor="#e8451f" /><stop offset=".55" stopColor="#ffb347" />
|
|
39
|
+
<stop offset=".8" stopColor="#e8451f" /><stop offset="1" stopColor="#7a1f0e" />
|
|
40
|
+
</linearGradient>,
|
|
41
|
+
galaxy: id => <linearGradient id={id} className="dca-skin-paint dca-skin-galaxy" x1="279" y1="766" x2="736" y2="334" gradientUnits="userSpaceOnUse">
|
|
42
|
+
<stop offset="0" stopColor="#1b1040" /><stop offset=".3" stopColor="#4c1d95" /><stop offset=".55" stopColor="#2563eb" />
|
|
43
|
+
<stop offset=".8" stopColor="#0b3d62" /><stop offset="1" stopColor="#7c3aed" />
|
|
44
|
+
</linearGradient>,
|
|
45
|
+
}
|
|
46
|
+
export function skinPaint(id: SkinId): BodyPaint | undefined {
|
|
47
|
+
return isPaintSkin(id) ? { render: gradients[id], shadow: paintMaterials[id].shadow } : undefined
|
|
48
|
+
}
|
|
49
|
+
export function SkinPaintSurface({ id }: { id: SkinId }) {
|
|
50
|
+
if (!isPaintSkin(id)) return null
|
|
51
|
+
return <g data-skin-overlay={id}>
|
|
52
|
+
{id !== 'galaxy' && id !== 'lava' && sheen}
|
|
53
|
+
{id === 'galaxy' && <g fill="#fff">{dots.filter((_, index) => index % 2 === 1).map(([cx, cy], index) => <path key={`${cx}-${cy}`} className="dca-fx dca-fx-node" d={star4(index % 2 ? 10 : 15)} transform={`translate(${cx} ${cy})`} style={{ animationDelay: `${(index * .43) % 1.8}s` }} />)}</g>}
|
|
54
|
+
<rect className="dca-fx dca-skin-energy" {...materialBounds} fill={paintMaterials[id].accent} opacity="0" />
|
|
55
|
+
</g>
|
|
56
|
+
}
|