@dotcraft/avatar 0.7.3 → 0.7.5
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 +1 -1
- package/dist/BackDecorations.d.ts.map +1 -1
- package/dist/BackDecorations.js +100 -6
- package/dist/BackDecorations.js.map +1 -1
- package/dist/CloudDragon.d.ts +3 -0
- package/dist/CloudDragon.d.ts.map +1 -0
- package/dist/CloudDragon.js +70 -0
- package/dist/CloudDragon.js.map +1 -0
- package/dist/DecorationShapes.d.ts +5 -0
- package/dist/DecorationShapes.d.ts.map +1 -1
- package/dist/DecorationShapes.js +4 -0
- package/dist/DecorationShapes.js.map +1 -1
- package/dist/Decorations.d.ts.map +1 -1
- package/dist/Decorations.js +3 -2
- package/dist/Decorations.js.map +1 -1
- package/dist/FaceDecorations.d.ts.map +1 -1
- package/dist/FaceDecorations.js +105 -5
- package/dist/FaceDecorations.js.map +1 -1
- package/dist/HandDecorations.d.ts.map +1 -1
- package/dist/HandDecorations.js +19 -2
- package/dist/HandDecorations.js.map +1 -1
- package/dist/HatDecorations.d.ts.map +1 -1
- package/dist/HatDecorations.js +1 -0
- package/dist/HatDecorations.js.map +1 -1
- package/dist/ObjectDecorations.d.ts.map +1 -1
- package/dist/ObjectDecorations.js +59 -0
- package/dist/ObjectDecorations.js.map +1 -1
- package/dist/SkinDecorations.d.ts.map +1 -1
- package/dist/SkinDecorations.js +37 -8
- package/dist/SkinDecorations.js.map +1 -1
- package/dist/accessories.css +4 -2
- package/dist/composer/ComposerMascot.d.ts +1 -1
- package/dist/composer/ComposerMascot.d.ts.map +1 -1
- package/dist/composer/ComposerMascot.js +5 -4
- package/dist/composer/ComposerMascot.js.map +1 -1
- package/dist/composer/styles/energy.css +23 -3
- package/dist/composer/types.d.ts +3 -0
- package/dist/composer/types.d.ts.map +1 -1
- package/dist/decorationCatalog.d.ts.map +1 -1
- package/dist/decorationCatalog.js +91 -0
- package/dist/decorationCatalog.js.map +1 -1
- package/dist/decorationMotion.d.ts.map +1 -1
- package/dist/decorationMotion.js +5 -2
- package/dist/decorationMotion.js.map +1 -1
- package/dist/effects.css +65 -4
- package/dist/items.d.ts +546 -0
- package/dist/items.d.ts.map +1 -1
- package/dist/items.js +42 -13
- package/dist/items.js.map +1 -1
- package/dist/paintMaterials.d.ts +36 -0
- package/dist/paintMaterials.d.ts.map +1 -1
- package/dist/paintMaterials.js +9 -0
- package/dist/paintMaterials.js.map +1 -1
- package/dist/spine.d.ts +15 -0
- package/dist/spine.d.ts.map +1 -0
- package/dist/spine.js +53 -0
- package/dist/spine.js.map +1 -0
- package/package.json +1 -1
- package/src/BackDecorations.tsx +250 -5
- package/src/CloudDragon.tsx +110 -0
- package/src/DecorationShapes.tsx +4 -0
- package/src/Decorations.tsx +4 -3
- package/src/FaceDecorations.tsx +254 -11
- package/src/HandDecorations.tsx +223 -1
- package/src/HatDecorations.tsx +8 -0
- package/src/ObjectDecorations.tsx +180 -0
- package/src/SkinDecorations.tsx +77 -12
- package/src/accessories.css +4 -2
- package/src/composer/ComposerMascot.tsx +5 -4
- package/src/composer/styles/energy.css +23 -3
- package/src/composer/types.ts +3 -0
- package/src/decorationCatalog.ts +91 -0
- package/src/decorationMotion.ts +5 -2
- package/src/effects.css +65 -4
- package/src/items.ts +43 -13
- package/src/paintMaterials.ts +9 -0
- package/src/spine.ts +61 -0
package/src/BackDecorations.tsx
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { ReactNode } from 'react'
|
|
2
2
|
import type { BackId } from './items.js'
|
|
3
|
-
import { Glow } from './DecorationShapes.js'
|
|
3
|
+
import { Detail, Glow, Silhouette as S, useClipId } from './DecorationShapes.js'
|
|
4
|
+
import { CloudDragon, CloudDragonFront } from './CloudDragon.js'
|
|
5
|
+
import { n0, oval, tube, type Pt } from './spine.js'
|
|
6
|
+
|
|
7
|
+
const mirrored = (art: ReactNode) => <>{art}<g transform="matrix(-1 0 0 1 1024 0)">{art}</g></>
|
|
4
8
|
|
|
5
9
|
function Wing({ outer, inner, fill, lining, outline = '#fff', flap = 'dca-fx-flap', children }: { outer: string; inner?: string; fill: string; lining?: string; outline?: string; flap?: string; children?: ReactNode }) {
|
|
6
10
|
return <g className={flap} style={{ transformOrigin: '272px 560px' }}>
|
|
@@ -10,7 +14,7 @@ function Wing({ outer, inner, fill, lining, outline = '#fff', flap = 'dca-fx-fla
|
|
|
10
14
|
</g>
|
|
11
15
|
}
|
|
12
16
|
function Wings(props: Parameters<typeof Wing>[0]) {
|
|
13
|
-
return
|
|
17
|
+
return mirrored(<Wing {...props} />)
|
|
14
18
|
}
|
|
15
19
|
const batWing = {
|
|
16
20
|
outer: 'M272 520C200 470 120 400 60 340c30 80 10 130-20 160 70 20 90 60 60 100 70 10 100 50 90 100h82Z',
|
|
@@ -42,6 +46,38 @@ const balloon = (cx: number, cy: number, fill: string, delay?: string) => <g cla
|
|
|
42
46
|
<ellipse cx={cx} cy={cy} rx="46" ry="54" fill={fill} stroke="#fff" strokeWidth="14" paintOrder="stroke fill" />
|
|
43
47
|
<ellipse cx={cx - 14} cy={cy - 18} rx="10" ry="16" fill="#fff" opacity=".6" />
|
|
44
48
|
</g>
|
|
49
|
+
const tether = (d: string) => <><path d={d} stroke="#fff" strokeWidth="16" strokeLinecap="round" /><path d={d} stroke="#8b95a5" strokeWidth="6" strokeLinecap="round" /></>
|
|
50
|
+
|
|
51
|
+
function SolarPanel() {
|
|
52
|
+
return <>
|
|
53
|
+
<path d="M300 455 188 350" stroke="#fff" strokeWidth="46" strokeLinecap="round" />
|
|
54
|
+
<path d="M300 455 188 350" stroke="#8b95a5" strokeWidth="24" strokeLinecap="round" />
|
|
55
|
+
<g transform="translate(188 350) rotate(-26)">
|
|
56
|
+
<rect x="-68" y="-192" width="136" height="178" rx="14" fill="#2f4a8a" stroke="#fff" strokeWidth="18" paintOrder="stroke fill" />
|
|
57
|
+
<path d="M-54-178h50v44h-50Zm58 0h50v44H4Zm-58 53h50v44h-50Zm58 0h50v44H4Zm-58 53h50v44h-50Zm58 0h50v44H4Z" fill="#5b7fd6" />
|
|
58
|
+
<circle r="18" fill="#8b95a5" stroke="#fff" strokeWidth="12" paintOrder="stroke fill" />
|
|
59
|
+
</g>
|
|
60
|
+
</>
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const bladePath = 'M-40-22H720L780 0 720 22H-40Z'
|
|
64
|
+
function Sword({ blade, glow, guard, children }: { blade: string; glow: string; guard: string; children?: ReactNode }) {
|
|
65
|
+
const clip = useClipId()
|
|
66
|
+
return <>
|
|
67
|
+
<defs><clipPath id={clip}><path d={bladePath} /></clipPath></defs>
|
|
68
|
+
<Glow blur={16} className="dca-fx-pulse"><path d="M-40-30H724L796 0 724 30H-40Z" fill={glow} opacity=".85" /></Glow>
|
|
69
|
+
<circle cx="-166" r="22" fill={guard} stroke="#fff" strokeWidth="14" paintOrder="stroke fill" />
|
|
70
|
+
<rect x="-160" y="-16" width="108" height="32" rx="12" fill="#2b2f3a" stroke="#fff" strokeWidth="14" paintOrder="stroke fill" />
|
|
71
|
+
<S d={bladePath} fill={blade} stroke={16} />
|
|
72
|
+
{children}
|
|
73
|
+
<g clipPath={`url(#${clip})`}><g transform="translate(689 0) scale(.3 1)"><g className="dca-fx dca-fx-sheen" fill="#fff"><path d="M3-30h67l-73 60h-67Z" opacity=".85" /></g></g></g>
|
|
74
|
+
<rect x="-64" y="-62" width="24" height="124" rx="10" fill={guard} stroke="#fff" strokeWidth="14" paintOrder="stroke fill" />
|
|
75
|
+
<rect x="-14" y="-36" width="30" height="72" rx="10" fill="#8b95a5" stroke="#fff" strokeWidth="12" paintOrder="stroke fill" />
|
|
76
|
+
</>
|
|
77
|
+
}
|
|
78
|
+
const leftShoulder = 'translate(265 392) rotate(43)'
|
|
79
|
+
|
|
80
|
+
const guitarBody = 'M-86 0C-86-48-48-86 0-86 42-86 66-62 90-54 110-46 124-64 160-64 196-64 224-36 224 0 224 36 196 64 160 64 124 64 110 46 90 54 66 62 42 86 0 86-48 86-86 48-86 0Z'
|
|
45
81
|
|
|
46
82
|
const orbit = { transform: 'translate(512 760) rotate(-12)', rx: 450, ry: 104 }
|
|
47
83
|
const orbitBodies: { key: string; delay?: string; art: ReactNode }[] = [
|
|
@@ -61,8 +97,106 @@ function OrbitArc({ side }: { side: 'front' | 'back' }) {
|
|
|
61
97
|
</>
|
|
62
98
|
}
|
|
63
99
|
|
|
100
|
+
const koiOrbit = `${orbit.transform} scale(.88)`
|
|
101
|
+
const koi = <>
|
|
102
|
+
<S d="M68 0 134-56 110 0 134 56Z" fill="#e8654f" />
|
|
103
|
+
<S d="M-114 2C-102-42-60-60-16-58S62-28 82 0C62 28 28 56-16 58S-106 44-114 2Z" fill="#ed985f" />
|
|
104
|
+
<ellipse cx="-10" cy="-22" rx="48" ry="24" transform="rotate(8 -10 -22)" fill="#fff4ef" />
|
|
105
|
+
</>
|
|
106
|
+
function KoiBodies({ side }: { side: 'front' | 'back' }) {
|
|
107
|
+
const art = side === 'front' ? koi : <g transform="scale(-1 1)">{koi}</g>
|
|
108
|
+
return <>{['a', 'c'].map(key => <g key={key} className={`dca-fx-orbit dca-fx-orbit-${side} dca-fx-orbit-${key}`} style={key === 'c' ? { animationDelay: '-4s' } : undefined}>{art}</g>)}</>
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// The tail spirals inward so its tip ends behind the antenna light or the hat crown.
|
|
112
|
+
function cometTail(width: number) {
|
|
113
|
+
const steps = 18, from = -28, to = -100
|
|
114
|
+
const point = (step: number, side: number) => {
|
|
115
|
+
const t = step / steps, a = ((from + (to - from) * t) * Math.PI) / 180, r = 380 - 120 * t + side * (width / 2) * (1 - t) ** .8
|
|
116
|
+
return `${(512 + r * Math.cos(a)).toFixed(0)} ${(520 + r * Math.sin(a)).toFixed(0)}`
|
|
117
|
+
}
|
|
118
|
+
const outer = Array.from({ length: steps + 1 }, (_, step) => point(step, 1))
|
|
119
|
+
const inner = Array.from({ length: steps }, (_, step) => point(steps - 1 - step, -1))
|
|
120
|
+
return `M${[...outer, ...inner].join('L')}Z`
|
|
121
|
+
}
|
|
122
|
+
|
|
64
123
|
const halo = { cx: 512, cy: 229, r: 168 }
|
|
65
124
|
|
|
125
|
+
function Floatie({ side }: { side: 'front' | 'back' }) {
|
|
126
|
+
const arc = (dy: number) => `M${-orbit.rx} ${dy}A${orbit.rx} ${orbit.ry} 0 0 ${side === 'back' ? 1 : 0} ${orbit.rx} ${dy}`
|
|
127
|
+
return <g className="dca-fx-hover"><g transform="translate(512 790) rotate(-6)">
|
|
128
|
+
<path d={arc(0)} stroke="#fff" strokeWidth="138" />
|
|
129
|
+
<path d={arc(0)} stroke="#dbab70" strokeWidth="120" />
|
|
130
|
+
<path d={arc(-16)} stroke="#f2a0b4" strokeWidth="84" />
|
|
131
|
+
{side === 'front' && <path d="M384 32l-18 10M252 66l-20 4M76 84l20 4M-110 82l-18 8M-282 62l20 6M-400 24l-14 12" stroke="#fff" strokeWidth="12" strokeLinecap="round" />}
|
|
132
|
+
</g></g>
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function TeslaCoil() {
|
|
136
|
+
return <>
|
|
137
|
+
<path d="M300 470 212 410" stroke="#fff" strokeWidth="46" strokeLinecap="round" />
|
|
138
|
+
<path d="M300 470 212 410" stroke="#8b95a5" strokeWidth="24" strokeLinecap="round" />
|
|
139
|
+
<rect x="180" y="226" width="64" height="190" rx="14" fill="#c9d2ff" stroke="#fff" strokeWidth="18" paintOrder="stroke fill" />
|
|
140
|
+
<path d="M180 270h64M180 306h64M180 342h64M180 378h64" stroke="#8b95a5" strokeWidth="12" />
|
|
141
|
+
<ellipse cx="212" cy="212" rx="84" ry="36" fill="#8b95a5" stroke="#fff" strokeWidth="18" paintOrder="stroke fill" />
|
|
142
|
+
<ellipse cx="212" cy="204" rx="46" ry="13" fill="#5b6577" />
|
|
143
|
+
</>
|
|
144
|
+
}
|
|
145
|
+
const teslaArc = 'M212 186L246 142L296 160L330 104L384 124L420 76L470 96L512 58L556 92L604 72L642 122L694 104L728 156L776 138L812 186'
|
|
146
|
+
|
|
147
|
+
const dragonTail = 'M313 773 275 795 241 809 211 815 184 815 161 810 140 799 121 782 106 759 94 729 88 693 88 651 94 605 58 595 44 645 38 694 40 739 49 782 66 820 92 854 125 880 165 898 211 905 260 903 313 890 367 867Z'
|
|
148
|
+
|
|
149
|
+
const sea = { deep: '#1e3f8f', band: '#5fb3ec', foam: '#fff8e6' }
|
|
150
|
+
function claw(x: number, y: number, deg: number, len: number, w: number, bend = .35) {
|
|
151
|
+
const a = (deg * Math.PI) / 180, c = Math.cos(a), s = Math.sin(a), h = w / 2, b = len * bend
|
|
152
|
+
const p = (u: number, v: number) => `${(x + u * c - v * s).toFixed(0)} ${(y + u * s + v * c).toFixed(0)}`
|
|
153
|
+
return `M${p(-h, 0)}C${p(-h, -h)} ${p(len * .55, -h * 1.1)} ${p(len, b)}C${p(len * .55, b * .45 + h * .5)} ${p(h * .2, h)} ${p(-h, 0)}Z`
|
|
154
|
+
}
|
|
155
|
+
type Claw = Parameters<typeof claw>
|
|
156
|
+
type Drop = readonly [x: number, y: number, r: number]
|
|
157
|
+
interface WaterMass { motion: string; pivot: string; body: string; band: string; claws: Claw[]; stagger: number; spray: Drop[] }
|
|
158
|
+
const swing = (points: Pt[]) => points.slice(1).map(([x, y], i) => { const [px, py] = points[i], k = (x - px) * .36; return `C${n0(px + k)} ${py} ${n0(x - k)} ${y} ${x} ${y}` }).join('')
|
|
159
|
+
const rim = 12
|
|
160
|
+
|
|
161
|
+
function Foam({ mass, paint }: { mass: WaterMass; paint: (d: string) => ReactNode }) {
|
|
162
|
+
return <>{mass.claws.map((c, i) => <g key={i} className="dca-fx-reach" style={{ transformOrigin: `${c[0]}px ${c[1]}px`, animationDelay: `${(mass.stagger + i * .45).toFixed(2)}s` }}>{paint(claw(...c))}</g>)}</>
|
|
163
|
+
}
|
|
164
|
+
function Spray({ drops }: { drops: Drop[] }) {
|
|
165
|
+
return <g className="dca-fx">{drops.map(([x, y, r], i) => <g key={i} className="dca-fx-spray" style={{ animationDelay: `${(-i * 4.8 / drops.length).toFixed(2)}s` }}>
|
|
166
|
+
<circle cx={x} cy={y} r={r + 9} fill="#fff" /><circle cx={x} cy={y} r={r} fill={sea.band} /><circle cx={x - r * .25} cy={y - r * .25} r={r * .55} fill={sea.foam} />
|
|
167
|
+
</g>)}</g>
|
|
168
|
+
}
|
|
169
|
+
// All contours are drawn before all fills so masses on different clocks overlap without seams; foam keeps a water-coloured rim off the white outline.
|
|
170
|
+
function Water({ masses }: { masses: WaterMass[] }) {
|
|
171
|
+
return <g strokeLinejoin="round">
|
|
172
|
+
{masses.map((m, i) => <g key={`c${i}`} className={m.motion} style={{ transformOrigin: m.pivot }}>
|
|
173
|
+
<path d={m.body} stroke="#fff" strokeWidth="18" />
|
|
174
|
+
<Foam mass={m} paint={d => <path d={d} fill="#fff" stroke="#fff" strokeWidth={2 * rim + 18} />} />
|
|
175
|
+
</g>)}
|
|
176
|
+
{masses.map((m, i) => <g key={`f${i}`} className={m.motion} style={{ transformOrigin: m.pivot }}>
|
|
177
|
+
<path d={m.body} fill={sea.deep} />
|
|
178
|
+
<Foam mass={m} paint={d => <path d={d} fill={sea.deep} stroke={sea.deep} strokeWidth={2 * rim} />} />
|
|
179
|
+
<Detail><path d={m.band} fill={sea.band} /></Detail>
|
|
180
|
+
<Foam mass={m} paint={d => <><path d={d} fill={sea.band} /><path className="dca-fx-foam" d={d} fill={sea.foam} /></>} />
|
|
181
|
+
<Spray drops={m.spray} />
|
|
182
|
+
</g>)}
|
|
183
|
+
</g>
|
|
184
|
+
}
|
|
185
|
+
const curl: WaterMass = {
|
|
186
|
+
motion: 'dca-fx-lean', pivot: '170px 900px', stagger: -3,
|
|
187
|
+
body: 'M230 270C300 270 350 296 352 330C354 356 330 366 306 356C270 340 230 330 200 350C170 370 160 420 164 470C170 580 180 680 200 740C222 806 262 846 330 870L330 880L56 880C38 866 30 836 30 796C20 660 28 520 60 420C92 324 156 270 230 270Z',
|
|
188
|
+
band: tube([[330, 904], [200, 872], [122, 790], [94, 660], [98, 520], [130, 420], [184, 358], [240, 336], [284, 340]], t => 56 - 28 * t),
|
|
189
|
+
claws: [[60, 420, -70, 96, 50, .9], [116, 326, -34, 100, 50, .95], [196, 280, 0, 104, 50, 1], [284, 286, 38, 100, 50, .95], [344, 336, 88, 72, 42, .8]],
|
|
190
|
+
spray: [[150, 248, 13], [226, 220, 15], [298, 228, 12], [352, 270, 10], [96, 316, 10]],
|
|
191
|
+
}
|
|
192
|
+
const swell: WaterMass = {
|
|
193
|
+
motion: 'dca-fx-swell', pivot: '512px 900px', stagger: -5.4,
|
|
194
|
+
body: `M30 886C28 856 44 838 64 836C96 834 124 846 170 850${swing([[170, 850], [300, 834], [430, 858], [560, 836], [700, 852]])}C750 846 776 796 812 786C846 776 880 762 910 770C942 780 964 812 964 848C964 886 950 906 924 914${swing([[924, 914], [810, 944], [690, 916], [570, 944], [450, 916], [330, 944], [210, 916], [90, 944]])}C60 944 32 922 30 886Z`,
|
|
195
|
+
band: tube([[900, 862], [820, 906], [700, 894], [580, 912], [460, 894], [340, 912], [240, 900], [180, 866], [134, 806]], t => 16 + 26 * t ** 2),
|
|
196
|
+
claws: [[58, 846, -84, 58, 36, .8], [96, 842, -54, 60, 36, .85], [134, 850, -26, 54, 34, .8], [870, 776, -40, 64, 38, .9], [914, 772, 6, 66, 38, .95], [950, 806, 56, 54, 34, .8]],
|
|
197
|
+
spray: [[944, 730, 11], [984, 774, 9]],
|
|
198
|
+
}
|
|
199
|
+
|
|
66
200
|
export function BackDecoration({ id }: { id: BackId }) {
|
|
67
201
|
switch (id) {
|
|
68
202
|
case 'cape': return <g className="dca-fx-sway" style={{ transformOrigin: '512px 452px' }}>
|
|
@@ -88,7 +222,7 @@ export function BackDecoration({ id }: { id: BackId }) {
|
|
|
88
222
|
case 'bat-wings': return <Wings {...batWing} fill="#5b3a8c" lining="#3d2563" />
|
|
89
223
|
case 'butterfly-wings': return <Wings {...butterflyWing} fill="#f2a0b4" lining="#fff3c4" />
|
|
90
224
|
case 'angel-wings': return <>
|
|
91
|
-
<Glow blur={22} className="dca-fx-pulse"
|
|
225
|
+
<Glow blur={22} className="dca-fx-pulse">{mirrored(<path d={angelWing.outer} fill="#fff7dc" opacity=".6" />)}</Glow>
|
|
92
226
|
<Wings {...angelWing} fill="#fff" lining="#dfe4ff" outline="#b9c4ff" flap="dca-fx-flap-slow" />
|
|
93
227
|
</>
|
|
94
228
|
case 'dragon-wings': return <Wings {...dragonWing} fill="#b23a48" lining="#7f2634">
|
|
@@ -104,9 +238,62 @@ export function BackDecoration({ id }: { id: BackId }) {
|
|
|
104
238
|
<path d="M920 690l12 30 30 12-30 12-12 30-12-30-30-12 30-12Z" style={{ animationDelay: '1.1s' }} />
|
|
105
239
|
</g>
|
|
106
240
|
case 'balloons': return <>
|
|
107
|
-
|
|
241
|
+
{tether('M150 384 262 560M206 306 262 560M112 484 262 560')}
|
|
108
242
|
{balloon(150, 330, '#e8654f')}{balloon(206, 250, '#f6b500', '.5s')}{balloon(112, 430, '#4f7cf6', '1s')}
|
|
109
243
|
</>
|
|
244
|
+
case 'kite': return <>
|
|
245
|
+
{tether('M885 336c-6 96-50 170-116 226')}
|
|
246
|
+
<g transform="rotate(10 908 206)">
|
|
247
|
+
<S d="M908 122 986 206 908 338 830 206Z" fill="#e8654f" />
|
|
248
|
+
<path d="M908 122V206H830ZM908 206H986L908 338Z" fill="#f6b500" />
|
|
249
|
+
</g>
|
|
250
|
+
</>
|
|
251
|
+
case 'solar-panels': return mirrored(<SolarPanel />)
|
|
252
|
+
case 'power-cord': return <>
|
|
253
|
+
<path d="M902 836C972 868 1006 812 984 772 962 734 908 748 920 792 930 826 976 832 996 806" stroke="#fff" strokeWidth="60" strokeLinecap="round" />
|
|
254
|
+
<path d="M902 836C972 868 1006 812 984 772 962 734 908 748 920 792 930 826 976 832 996 806" stroke="#3c4658" strokeWidth="36" strokeLinecap="round" />
|
|
255
|
+
<g transform="translate(808 792) rotate(25.5)">
|
|
256
|
+
<rect x="-14" y="-40" width="104" height="80" rx="18" fill="#3c4658" stroke="#fff" strokeWidth="18" paintOrder="stroke fill" />
|
|
257
|
+
<path d="M30-24v48M54-24v48" stroke="#5b6577" strokeWidth="8" strokeLinecap="round" />
|
|
258
|
+
<rect x="86" y="-20" width="24" height="40" rx="8" fill="#3c4658" stroke="#fff" strokeWidth="12" paintOrder="stroke fill" />
|
|
259
|
+
</g>
|
|
260
|
+
</>
|
|
261
|
+
case 'surfboard': return <g transform="rotate(8 900 540)">
|
|
262
|
+
<S d="M900 176c44 64 62 204 62 384s-16 290-40 330c-10 16-34 16-44 0-24-40-40-150-40-330s18-320 62-384Z" fill="#f3cf62" />
|
|
263
|
+
<rect x="886" y="250" width="28" height="600" rx="14" fill="#e8654f" />
|
|
264
|
+
</g>
|
|
265
|
+
case 'fox-tail': return <g className="dca-fx-sway" style={{ transformOrigin: '780px 830px' }}>
|
|
266
|
+
<S d="M730 790C840 790 878 720 885 650 890 600 905 560 940 552 972 545 990 580 990 640 990 720 968 800 918 848 868 894 780 900 715 866Z" fill="#ed985f" />
|
|
267
|
+
<path d="M715 866C780 900 868 894 918 848 968 800 990 720 990 640 978 716 948 784 898 818 848 852 780 860 732 850Z" fill="#c96c43" />
|
|
268
|
+
<path d="M885 650C890 600 905 560 940 552 972 545 990 580 990 640L972 626 956 652 936 630 916 654 900 632Z" fill="#fff1dc" />
|
|
269
|
+
</g>
|
|
270
|
+
case 'hero-scarf': return <g className="dca-fx-wave" style={{ transformOrigin: '250px 470px' }}>
|
|
271
|
+
<S d="M252 470C220 490 190 520 150 520 120 520 90 540 62 568L106 580 90 624C120 600 150 580 180 574 210 568 232 548 252 534Z" fill="#b94f50" />
|
|
272
|
+
<S d="M252 418C214 400 180 386 140 398 100 410 70 404 22 380L58 430 26 478C76 494 110 478 146 462 186 446 220 470 252 482Z" fill="#e8654f" />
|
|
273
|
+
</g>
|
|
274
|
+
case 'guitar': return <>
|
|
275
|
+
<g transform="translate(92 822) rotate(-35)">
|
|
276
|
+
<rect x="210" y="-19" width="690" height="38" rx="10" fill="#8b5a2b" stroke="#fff" strokeWidth="18" paintOrder="stroke fill" />
|
|
277
|
+
<g fill="#f6d365" stroke="#fff" strokeWidth="8" paintOrder="stroke fill"><circle cx="930" cy="-38" r="10" /><circle cx="972" cy="-38" r="10" /><circle cx="930" cy="38" r="10" /><circle cx="972" cy="38" r="10" /></g>
|
|
278
|
+
<S d="M884-32h116a16 16 0 0 1 16 16v32a16 16 0 0 1-16 16H884Z" fill="#8b5a2b" />
|
|
279
|
+
<S d={guitarBody} fill="#e0ad84" />
|
|
280
|
+
<circle cx="100" r="31" fill="#f6d365" /><circle cx="100" r="20" fill="#8b5a2b" />
|
|
281
|
+
<rect x="-40" y="-30" width="16" height="60" rx="7" fill="#8b5a2b" />
|
|
282
|
+
</g>
|
|
283
|
+
<path d="M826 334C840 384 834 424 800 460" stroke="#fff" strokeWidth="44" strokeLinecap="round" />
|
|
284
|
+
<path d="M826 334C840 384 834 424 800 460" stroke="#3c4658" strokeWidth="22" strokeLinecap="round" />
|
|
285
|
+
</>
|
|
286
|
+
case 'twin-blades': return <>
|
|
287
|
+
<g transform={leftShoulder}><Sword blade="#1f2330" glow="#9fb2ff" guard="#3c4658"><path d="M-28-13H718L772-3" stroke="#dfe6f5" strokeWidth="9" strokeLinecap="round" strokeLinejoin="round" /></Sword></g>
|
|
288
|
+
<g transform="matrix(-1 0 0 1 1024 0)"><g transform={leftShoulder}><Sword blade="#8fdccf" glow="#7fe8d8" guard="#a7b1c0"><path d="M-28 0H728" stroke="#e6fbf6" strokeWidth="10" strokeLinecap="round" /></Sword></g></g>
|
|
289
|
+
</>
|
|
290
|
+
case 'comet': return <>
|
|
291
|
+
<S d={cometTail(110)} fill="#b9c4ff" />
|
|
292
|
+
<path d={cometTail(46)} fill="#fff3c4" />
|
|
293
|
+
<Glow blur={24} className="dca-fx-pulse"><circle cx="848" cy="342" r="96" fill="#fff3c4" opacity=".8" /></Glow>
|
|
294
|
+
<S d="M848 282a60 60 0 1 1 0 120 60 60 0 1 1 0-120Z" fill="#f6b500" />
|
|
295
|
+
<circle cx="828" cy="322" r="20" fill="#fff3c4" />
|
|
296
|
+
</>
|
|
110
297
|
case 'sun-rays': return <SunRays />
|
|
111
298
|
case 'halo': return <>
|
|
112
299
|
<Glow blur={24} className="dca-fx-pulse"><circle cx={halo.cx} cy={halo.cy} r={halo.r} fill="none" stroke="#ffe08a" strokeWidth="74" opacity=".65" /></Glow>
|
|
@@ -118,6 +305,61 @@ export function BackDecoration({ id }: { id: BackId }) {
|
|
|
118
305
|
<OrbitArc side="back" />
|
|
119
306
|
<OrbitBodies side="back" />
|
|
120
307
|
</g>
|
|
308
|
+
case 'koi-orbit': return <g transform={koiOrbit}><KoiBodies side="back" /></g>
|
|
309
|
+
case 'peeking-cat': return <g transform="translate(786 376) rotate(24) scale(1.1)">
|
|
310
|
+
<S d="M-100 14C-100-46-58-84 0-84S100-46 100 14 60 96 0 96-100 74-100 14ZM-92-24-86-138-22-76ZM92-24 86-138 22-76Z" fill="#ed985f" />
|
|
311
|
+
<path d="M-78-52-75-112-40-80ZM78-52 75-112 40-80Z" fill="#c96c43" />
|
|
312
|
+
<path d="M-26-80v24M0-84v30M26-80v24" stroke="#c96c43" strokeWidth="12" strokeLinecap="round" />
|
|
313
|
+
<ellipse cx="-38" cy="6" rx="13" ry="18" fill="#3c4658" /><ellipse cx="38" cy="6" rx="13" ry="18" fill="#3c4658" />
|
|
314
|
+
<ellipse cx="0" cy="50" rx="44" ry="26" fill="#fff1dc" /><path d="M-10 32h20l-10 12Z" fill="#e8654f" />
|
|
315
|
+
</g>
|
|
316
|
+
case 'donut-floatie': return <Floatie side="back" />
|
|
317
|
+
case 'ladybug': return <g transform="translate(232 386) rotate(-34) scale(1.05)" strokeLinecap="round">
|
|
318
|
+
<path d="M-18-92c-10-22-26-34-44-38M18-92c10-22 26-34 44-38" stroke="#fff" strokeWidth="30" fill="none" /><path d="M-18-92c-10-22-26-34-44-38M18-92c10-22 26-34 44-38" stroke="#2b2f3a" strokeWidth="12" fill="none" />
|
|
319
|
+
<S d={`${oval(0, -70, 36, 30)}${oval(0, 26, 72, 92)}`} fill="#e8654f" />
|
|
320
|
+
<path d={oval(0, -70, 36, 30)} fill="#2b2f3a" /><path d="M0-44V118" stroke="#2b2f3a" strokeWidth="10" />
|
|
321
|
+
<g fill="#2b2f3a"><circle cx="-30" cy="-6" r="13" /><circle cx="30" cy="4" r="13" /><circle cx="-24" cy="56" r="13" /><circle cx="28" cy="66" r="12" /><circle cx="-36" cy="-42" r="9" /><circle cx="38" cy="-36" r="9" /></g>
|
|
322
|
+
<circle cx="-12" cy="-76" r="6" fill="#fff" /><circle cx="12" cy="-76" r="6" fill="#fff" />
|
|
323
|
+
</g>
|
|
324
|
+
case 'dragon-tail': return <g className="dca-fx-sway" style={{ transformOrigin: '250px 830px' }}>
|
|
325
|
+
<S d={`M172 892 117 931 118 871ZM106 857 42 868 68 817ZM63 797 2 783 45 748ZM47 721-5 689 45 670Z${dragonTail}`} fill="#7f2634" />
|
|
326
|
+
<g transform="translate(76 600) rotate(16) scale(1.1)"><S d="M0-96C20-70 52-44 50-12 48 8 26 16 10 4L0 20-10 4C-26 16-48 8-50-12-52-44-20-70 0-96Z" fill="#b23a48" /></g>
|
|
327
|
+
<path d={dragonTail} fill="#b23a48" />
|
|
328
|
+
</g>
|
|
329
|
+
case 'drone-buddy': return <>
|
|
330
|
+
{tether('M170 256C176 360 226 424 262 500')}
|
|
331
|
+
<g className="dca-fx-hover"><g transform="translate(170 236) scale(1.4)" strokeLinecap="round">
|
|
332
|
+
{[[-50, -20, -64, 45, 10], [50, -20, -64, 45, 10], [-86, -2, -36, 54, 12], [86, -2, -36, 54, 12]].map(([x, base, top, rx, ry]) => <g key={x}>
|
|
333
|
+
<path d={`M${x} ${base}V${top}`} stroke="#fff" strokeWidth="32" /><path d={`M${x} ${base}V${top}`} stroke="#3c4658" strokeWidth="16" />
|
|
334
|
+
<g transform={`translate(${x} ${top})`}><g className="dca-fx-spin-flat" style={{ transformOrigin: '0px 0px' }}>
|
|
335
|
+
<ellipse rx={rx} ry={ry} fill="#8b95a5" stroke="#fff" strokeWidth="12" paintOrder="stroke fill" />
|
|
336
|
+
</g></g>
|
|
337
|
+
</g>)}
|
|
338
|
+
<path d="M-86 0-30 12M86 0 30 12" stroke="#fff" strokeWidth="36" /><path d="M-86 0-30 12M86 0 30 12" stroke="#3c4658" strokeWidth="18" />
|
|
339
|
+
<S d={oval(0, 8, 60, 42)} fill="#f4f6fb" />
|
|
340
|
+
<rect x="-54" y="2" width="108" height="22" rx="11" fill="#3c4658" />
|
|
341
|
+
<circle cy="13" r="25" fill="#3c4658" stroke="#fff" strokeWidth="9" paintOrder="stroke fill" />
|
|
342
|
+
<circle cy="13" r="13" fill="#4de3ff" /><circle cx="-4" cy="8" r="4.5" fill="#fff" />
|
|
343
|
+
</g></g>
|
|
344
|
+
</>
|
|
345
|
+
case 'shade-tree': return <>
|
|
346
|
+
<S d="M380 380C404 330 426 290 440 240h144c14 50 36 90 60 140Z" fill="#805840" />
|
|
347
|
+
<g className="dca-fx-sway" style={{ transformOrigin: '512px 320px' }}>
|
|
348
|
+
<S d={[[512, 180, 142], [352, 224, 104], [672, 224, 104], [428, 122, 84], [596, 122, 84]].map(([x, y, r]) => oval(x, y, r)).join('')} fill="#3f8f4f" />
|
|
349
|
+
<path d={[[500, 160, 114], [342, 206, 76], [660, 206, 76], [418, 108, 62], [586, 108, 62]].map(([x, y, r]) => oval(x, y, r)).join('')} fill="#6fbf73" />
|
|
350
|
+
</g>
|
|
351
|
+
</>
|
|
352
|
+
case 'tesla-coils': return <>
|
|
353
|
+
{mirrored(<TeslaCoil />)}
|
|
354
|
+
<Glow blur={14} className="dca-fx-node"><path d={teslaArc} stroke="#4de3ff" strokeWidth="44" strokeLinejoin="round" /></Glow>
|
|
355
|
+
<g className="dca-fx-node" strokeLinejoin="round" strokeLinecap="round">
|
|
356
|
+
<path d={teslaArc} stroke="#fff" strokeWidth="34" />
|
|
357
|
+
<path d={teslaArc} stroke="#4de3ff" strokeWidth="20" />
|
|
358
|
+
<path d={teslaArc} stroke="#fff" strokeWidth="6" />
|
|
359
|
+
</g>
|
|
360
|
+
</>
|
|
361
|
+
case 'great-wave': return <Water masses={[curl, swell]} />
|
|
362
|
+
case 'cloud-dragon': return <CloudDragon />
|
|
121
363
|
default: return null
|
|
122
364
|
}
|
|
123
365
|
}
|
|
@@ -128,7 +370,10 @@ export function BackFrontDecoration({ id }: { id: BackId }) {
|
|
|
128
370
|
<OrbitArc side="front" />
|
|
129
371
|
<OrbitBodies side="front" />
|
|
130
372
|
</g>
|
|
373
|
+
case 'koi-orbit': return <g data-back-front={id} transform={koiOrbit}><KoiBodies side="front" /></g>
|
|
374
|
+
case 'donut-floatie': return <g data-back-front={id}><Floatie side="front" /></g>
|
|
375
|
+
case 'cloud-dragon': return <g data-back-front={id}><CloudDragonFront /></g>
|
|
131
376
|
default: return null
|
|
132
377
|
}
|
|
133
378
|
}
|
|
134
|
-
export function hasFrontPart(id: BackId): boolean { return id === 'orbit-ring' }
|
|
379
|
+
export function hasFrontPart(id: BackId): boolean { return id === 'orbit-ring' || id === 'koi-orbit' || id === 'donut-floatie' || id === 'cloud-dragon' }
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { useId, type CSSProperties, type ReactNode } from 'react'
|
|
2
|
+
import { Plane } from './DecorationShapes.js'
|
|
3
|
+
import { along, lock, mirror, oval, ridge, talon, tube, type Pt } from './spine.js'
|
|
4
|
+
|
|
5
|
+
const jade = '#2f9e7a', deep = '#1f7a5e', mint = '#a7e8cf', gold = '#f6b500', eyeLight = '#eafff6', spirit = '#6dffb8'
|
|
6
|
+
const both = (d: string) => `${d}${mirror(d)}`
|
|
7
|
+
const pivot = (x: number, y: number, delay = 0): CSSProperties => ({ transformOrigin: `${x}px ${y}px`, animationDelay: `${delay}s` })
|
|
8
|
+
|
|
9
|
+
// A gradient, not a blur filter: the halo moves with the head, and a filter would re-render every frame.
|
|
10
|
+
function Halo({ cx, cy, rx, ry }: { cx: number; cy: number; rx: number; ry: number }) {
|
|
11
|
+
const id = `dca-glow-${useId().replace(/:/g, '')}`
|
|
12
|
+
return <>
|
|
13
|
+
<defs><radialGradient id={id}><stop offset="0" stopColor={spirit} stopOpacity=".9" /><stop offset=".55" stopColor={spirit} stopOpacity=".4" /><stop offset="1" stopColor={spirit} stopOpacity="0" /></radialGradient></defs>
|
|
14
|
+
<ellipse className="dca-fx dca-fx-eye-flare" cx={cx} cy={cy} rx={rx} ry={ry} fill={`url(#${id})`} />
|
|
15
|
+
</>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const taper = 'M12-86C-22-72-56-64-80-56L-98-44C-92-22-80-2-70 12C-60 30-50 48-46 66C-44 84-40 100-26 110C-16 116-6 118 12 118Z'
|
|
19
|
+
const antler = 'M-30-70C-40-110-58-140-86-170M-52-112C-58-132-54-150-44-162M-70-144C-88-150-104-150-120-144'
|
|
20
|
+
const frills = [lock(-86, -34, 206, 84, 36, .7), lock(-74, 8, 166, 92, 38, .5)]
|
|
21
|
+
const eye = 'M-20-14L-86-40C-78-18-56-2-26 0Z'
|
|
22
|
+
const brow = 'M-16-24L-92-52L-90-40L-20-14Z'
|
|
23
|
+
const whiskerSpine: Pt[] = [[-40, 86], [-78, 98], [-110, 84], [-122, 54], [-110, 30]]
|
|
24
|
+
const whiskerWidth = (t: number) => 12 - 8 * t
|
|
25
|
+
|
|
26
|
+
function Whisker({ side }: { side: 1 | -1 }) {
|
|
27
|
+
const spine = side === 1 ? whiskerSpine : whiskerSpine.map(([x, y]) => [-x, y] as Pt)
|
|
28
|
+
const joint = along(spine, .45)
|
|
29
|
+
const root = tube(spine, whiskerWidth, 0, .5, 0, 1, 10), tip = tube(spine, whiskerWidth, .42, 1, 0, 1, 10)
|
|
30
|
+
const delay = side === 1 ? 0 : -1.4
|
|
31
|
+
const pass = (outline: boolean) => {
|
|
32
|
+
const paint = (d: string) => outline ? <path d={d} stroke="#fff" strokeWidth="10" strokeLinejoin="round" /> : <path d={d} fill={mint} />
|
|
33
|
+
return <g className="dca-fx-whisker" style={pivot(spine[0][0], spine[0][1], delay)}>
|
|
34
|
+
{paint(root)}
|
|
35
|
+
<g className="dca-fx-whisker-tip" style={pivot(joint.x, joint.y, delay - .5)}>{paint(tip)}</g>
|
|
36
|
+
</g>
|
|
37
|
+
}
|
|
38
|
+
return <>{pass(true)}{pass(false)}</>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function Head() {
|
|
42
|
+
return <g transform="translate(210 290) rotate(4)"><g className="dca-fx-rear">
|
|
43
|
+
<g className="dca-fx-frill-flare" style={pivot(0, -20)}>
|
|
44
|
+
<g className="dca-fx-frill-l" style={pivot(-80, -20)}><Plane d={frills} fill={mint} /></g>
|
|
45
|
+
<g className="dca-fx-frill-r" style={pivot(80, -20)}><Plane d={frills.map(mirror)} fill={mint} /></g>
|
|
46
|
+
</g>
|
|
47
|
+
<g strokeLinecap="round" strokeLinejoin="round"><path d={both(antler)} stroke="#fff" strokeWidth="42" /><path d={both(antler)} stroke={gold} strokeWidth="22" /></g>
|
|
48
|
+
<Plane d={[taper, mirror(taper)]} fill={jade} />
|
|
49
|
+
<path d={both(brow)} fill={deep} />
|
|
50
|
+
<Halo cx={-50} cy={-22} rx={58} ry={34} /><Halo cx={50} cy={-22} rx={58} ry={34} />
|
|
51
|
+
<path d={both(eye)} fill={eyeLight} />
|
|
52
|
+
<Whisker side={1} /><Whisker side={-1} />
|
|
53
|
+
</g></g>
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function Cloud({ x, y, s, flip = false, delay = 0 }: { x: number; y: number; s: number; flip?: boolean; delay?: number }) {
|
|
57
|
+
return <g transform={`translate(${x} ${y}) scale(${flip ? -s : s} ${s})`}><g className="dca-fx-drift" style={{ animationDelay: `${delay}s` }}>
|
|
58
|
+
<path d="M-100 24C-118 24-122-2-104-10C-108-38-78-50-60-34C-54-64-10-70 6-42C16-62 50-60 58-34C82-44 106-24 96 0C114 4 112 26 94 26Z" fill="#dff4ec" stroke="#fff" strokeWidth="16" strokeLinejoin="round" paintOrder="stroke fill" />
|
|
59
|
+
<path d="M-100 22C-60 32 60 32 94 22C80 10-80 10-100 22Z" fill="#b9e6d4" />
|
|
60
|
+
<path d="M-66-6C-66-22-44-24-42-10M-8-28C-8-46 20-48 22-30M36-10C38-24 58-24 60-12" stroke="#5fbf9b" strokeWidth="9" strokeLinecap="round" />
|
|
61
|
+
</g></g>
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Past `still`, each joint nests in the one before and swings on its own delay, so the tail whips.
|
|
65
|
+
const spine: Pt[] = [[240, 300], [300, 214], [392, 150], [512, 118], [632, 126], [742, 178], [828, 268], [888, 386], [918, 524], [922, 664], [900, 784], [934, 870], [996, 874]]
|
|
66
|
+
const width = (t: number) => 96 - 58 * t
|
|
67
|
+
const still = .6, joints = [still, .69, .78, .86, .93, 1], overlap = .012
|
|
68
|
+
const foreleg = [tube([[318, 206], [352, 262], [396, 330], [404, 356]], t => 48 - 14 * t), oval(404, 352, 34, 26)]
|
|
69
|
+
const tailTip = [lock(996, 874, -94, 84, 40, -.5), lock(996, 874, -60, 108, 48, .3), lock(996, 874, -24, 80, 36, .6)]
|
|
70
|
+
|
|
71
|
+
interface Piece { body: string; fins: string; belly: string }
|
|
72
|
+
function piece(from: number, to: number, finFrom = from, finTo = to): Piece {
|
|
73
|
+
const steps = Math.max(6, Math.round((to - from) * 120))
|
|
74
|
+
return {
|
|
75
|
+
body: tube(spine, width, from, to, 0, 1, steps),
|
|
76
|
+
fins: ridge(spine, width, finFrom, finTo, Math.max(1, Math.round((finTo - finFrom) * 40)), 46, -1, 1.1),
|
|
77
|
+
belly: tube(spine, width, Math.max(from, .03), to, .26, .36, steps),
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const bare = piece(0, still, .03)
|
|
81
|
+
const rest = { ...bare, body: bare.body + foreleg.join('') }
|
|
82
|
+
const swinging = joints.slice(0, -1).map((a, k) => ({ a, piece: piece(Math.max(0, a - overlap), Math.min(1, joints[k + 1] + overlap), a, joints[k + 1]), amp: 1.2 + k * 1.1, delay: -k * .32 }))
|
|
83
|
+
|
|
84
|
+
function Joints({ paint, tip }: { paint: (p: Piece) => ReactNode; tip?: ReactNode }) {
|
|
85
|
+
return swinging.reduceRight<ReactNode>((inner, joint) => {
|
|
86
|
+
const at = along(spine, joint.a)
|
|
87
|
+
return <g className="dca-fx-coil" style={{ ...pivot(at.x, at.y, joint.delay), '--dca-coil': joint.amp } as CSSProperties}>{paint(joint.piece)}{inner}</g>
|
|
88
|
+
}, tip)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Outlines, bodies, then belly stripes: each pass repeats the same joints so no piece paints over a neighbour's stripe.
|
|
92
|
+
export function CloudDragon() {
|
|
93
|
+
const outline = (p: Piece) => <><path d={p.fins} stroke="#fff" strokeWidth="16" strokeLinejoin="round" /><path d={p.body} stroke="#fff" strokeWidth="22" strokeLinejoin="round" /></>
|
|
94
|
+
const body = (p: Piece) => <><path d={p.fins} fill={jade} /><path d={p.body} fill={jade} /></>
|
|
95
|
+
const belly = (p: Piece) => <path d={p.belly} fill={mint} />
|
|
96
|
+
return <>
|
|
97
|
+
<Cloud x={804} y={150} s={.95} flip delay={-2} /><Cloud x={962} y={600} s={.9} delay={-4} />
|
|
98
|
+
{outline(rest)}<Joints paint={outline} />
|
|
99
|
+
{body(rest)}<Joints paint={body} />
|
|
100
|
+
{belly(rest)}<Joints paint={belly} tip={<Plane d={tailTip} fill={mint} />} />
|
|
101
|
+
<Head />
|
|
102
|
+
</>
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function CloudDragonFront() {
|
|
106
|
+
return <>
|
|
107
|
+
<Plane d={[talon(378, 371, 112, 36, 20), talon(404, 372, 92, 40, 20), talon(430, 373, 72, 36, 20)]} fill={gold} contour={12} />
|
|
108
|
+
<Cloud x={200} y={812} s={1.05} />
|
|
109
|
+
</>
|
|
110
|
+
}
|
package/src/DecorationShapes.tsx
CHANGED
|
@@ -3,6 +3,10 @@ import { useId, type ReactNode } from 'react'
|
|
|
3
3
|
export function Silhouette({ d, fill, stroke = 18, children }: { d: string; fill: string; stroke?: number; children?: ReactNode }) {
|
|
4
4
|
return <><path d={d} fill={fill} stroke="#fff" strokeWidth={stroke} strokeLinejoin="round" paintOrder="stroke fill" />{children}</>
|
|
5
5
|
}
|
|
6
|
+
// Every contour is laid before every fill, so overlapping parts read as one sticker.
|
|
7
|
+
export function Plane({ d, fill, contour = 20 }: { d: string[]; fill: string; contour?: number }) {
|
|
8
|
+
return <>{d.map((p, i) => <path key={`o${i}`} d={p} stroke="#fff" strokeWidth={contour} strokeLinejoin="round" />)}{d.map((p, i) => <path key={i} d={p} fill={fill} />)}</>
|
|
9
|
+
}
|
|
6
10
|
export function Detail({ children }: { children: ReactNode }) {
|
|
7
11
|
return <g className="dca-decoration-detail">{children}</g>
|
|
8
12
|
}
|
package/src/Decorations.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useId } from 'react'
|
|
2
|
-
import type { ItemId, HeadId, SkinId } from './items.js'
|
|
2
|
+
import type { ItemId, HeadId, SkinId, Zone } from './items.js'
|
|
3
3
|
import { itemOf } from './items.js'
|
|
4
4
|
import { HatDecoration } from './HatDecorations.js'
|
|
5
5
|
import { ObjectDecoration } from './ObjectDecorations.js'
|
|
@@ -42,13 +42,14 @@ export function SlotDecoration({ id }: { id: ItemId }) {
|
|
|
42
42
|
const swatchViewBox: Record<ReturnType<typeof itemOf>['slot'], string> = {
|
|
43
43
|
head: '265 105 494 360',
|
|
44
44
|
face: '300 280 424 324',
|
|
45
|
-
hand: '
|
|
45
|
+
hand: '10 330 460 460',
|
|
46
46
|
back: '20 120 984 800',
|
|
47
47
|
skin: '203 368 618 506',
|
|
48
48
|
}
|
|
49
49
|
export function DecorationSwatch({ id, size = 112 }: { id: ItemId; size?: number }) {
|
|
50
50
|
const item = itemOf(id)
|
|
51
|
-
const
|
|
51
|
+
const zones: readonly Zone[] = item.zones
|
|
52
|
+
const viewBox = zones.includes('screen') ? '255 404 514 436' : zones.includes('rim') ? '346 300 546 590' : swatchViewBox[item.slot]
|
|
52
53
|
return <svg width={size} height={size} viewBox={viewBox} fill="none" role="img" aria-label={`${decorationOf(id).name} specimen`}
|
|
53
54
|
className="dca-swatch dca-part-robot" data-expression="neutral" data-slot={item.slot} data-effects="static">
|
|
54
55
|
<SlotDecoration id={id} />
|