@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
package/dist/palette.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"palette.js","sourceRoot":"","sources":["../src/palette.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"palette.js","sourceRoot":"","sources":["../src/palette.ts"],"names":[],"mappings":"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"}
|
package/dist/react.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { Avatar, AppearanceAvatar, type AvatarProps, type AppearanceAvatarProps } from './Avatar.js';
|
|
2
|
-
export { DecorationSwatch,
|
|
1
|
+
export { Avatar, AppearanceAvatar, sizeTier, type AvatarProps, type AppearanceAvatarProps, type SizeTier } from './Avatar.js';
|
|
2
|
+
export { DecorationSwatch, SlotDecoration, HeadDecoration } from './Decorations.js';
|
|
3
3
|
export { ComposerMascot } from './composer/ComposerMascot.js';
|
|
4
4
|
export type { ComposerMascotProps, ComposerMascotContext, MascotExpression, MascotLight } from './composer/types.js';
|
|
5
5
|
export { ComposerMascotShadow } from './composer/ComposerMascotShadow.js';
|
package/dist/react.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,KAAK,WAAW,EAAE,KAAK,qBAAqB,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,KAAK,WAAW,EAAE,KAAK,qBAAqB,EAAE,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAA;AAC7H,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAEnF,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAC7D,YAAY,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACpH,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAA;AACzE,OAAO,EAAE,yBAAyB,EAAE,MAAM,yCAAyC,CAAA;AACnF,OAAO,EAAE,mBAAmB,EAAE,KAAK,0BAA0B,EAAE,KAAK,sBAAsB,EAAE,KAAK,uBAAuB,EAAE,MAAM,mCAAmC,CAAA;AACnK,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAA;AAC/D,YAAY,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA"}
|
package/dist/react.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { Avatar, AppearanceAvatar } from './Avatar.js';
|
|
2
|
-
export { DecorationSwatch,
|
|
1
|
+
export { Avatar, AppearanceAvatar, sizeTier } from './Avatar.js';
|
|
2
|
+
export { DecorationSwatch, SlotDecoration, HeadDecoration } from './Decorations.js';
|
|
3
3
|
export { ComposerMascot } from './composer/ComposerMascot.js';
|
|
4
4
|
export { ComposerMascotShadow } from './composer/ComposerMascotShadow.js';
|
|
5
5
|
export { useComposerAvatarBehavior } from './composer/useComposerAvatarBehavior.js';
|
package/dist/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAA+D,MAAM,aAAa,CAAA;AAC7H,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAEnF,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAE7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAA;AACzE,OAAO,EAAE,yBAAyB,EAAE,MAAM,yCAAyC,CAAA;AACnF,OAAO,EAAE,mBAAmB,EAA8F,MAAM,mCAAmC,CAAA;AACnK,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAA"}
|
package/dist/rig.css
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
/*
|
|
2
|
-
|
|
3
|
-
lockstep, so transform-box: view-box resolves one shared origin for both. */
|
|
1
|
+
/* Arm outlines, material clips and held props move in lockstep.
|
|
2
|
+
transform-box: view-box resolves the same pivot for each layer. */
|
|
4
3
|
.dca-part-eyes,
|
|
5
4
|
.dca-part-glow,
|
|
6
5
|
.dca-part-light,
|
|
@@ -40,6 +39,7 @@
|
|
|
40
39
|
a prop is never left floating. Every rule that ENTERS a posed arm state overrides
|
|
41
40
|
the delay back to 0 so raises stay immediate. */
|
|
42
41
|
.dca-part-arm-l-w,
|
|
42
|
+
.dca-part-arm-l-b,
|
|
43
43
|
.dca-part-arm-l {
|
|
44
44
|
transform-box: view-box;
|
|
45
45
|
transform-origin: 233px 514px;
|
|
@@ -103,9 +103,7 @@
|
|
|
103
103
|
/* Props are always-mounted `dca-part-prop-*` groups inside MascotRobot at opacity 0,
|
|
104
104
|
revealed by these composer root classes. */
|
|
105
105
|
|
|
106
|
-
/*
|
|
107
|
-
arms only tuck inward by pure translate — a rotation would trigger the solid-colour
|
|
108
|
-
swap and break the gradient's continuity with the torso. */
|
|
106
|
+
/* The 1200ms delay keeps quick turns from flashing the mini terminal. */
|
|
109
107
|
.dca-part-prop-laptop {
|
|
110
108
|
opacity: 0;
|
|
111
109
|
transition: opacity 200ms ease;
|
|
@@ -117,6 +115,7 @@
|
|
|
117
115
|
}
|
|
118
116
|
|
|
119
117
|
.dca-action-prop-laptop .dca-part-arm-l-w,
|
|
118
|
+
.dca-action-prop-laptop .dca-part-arm-l-b,
|
|
120
119
|
.dca-action-prop-laptop .dca-part-arm-l {
|
|
121
120
|
transform: translate(18px, 0);
|
|
122
121
|
transition: transform 320ms cubic-bezier(0.34, 1.56, 0.64, 1) 1200ms;
|
|
@@ -166,10 +165,6 @@
|
|
|
166
165
|
transition-delay: 0ms;
|
|
167
166
|
}
|
|
168
167
|
|
|
169
|
-
.dca-action-hold-sign .dca-part-arm-r-b {
|
|
170
|
-
fill: var(--dca-part-raised-arm-right, #7a96fb);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
168
|
.dca-part-prop-sign {
|
|
174
169
|
opacity: 0;
|
|
175
170
|
transition: opacity 140ms ease;
|
|
@@ -242,6 +237,7 @@
|
|
|
242
237
|
/* The cheer reuses the wave's slide-then-rotate hinge grammar, so the outline wraps
|
|
243
238
|
the band and the colour stays seam-continuous. */
|
|
244
239
|
.dca-action-celebrate .dca-part-arm-l-w,
|
|
240
|
+
.dca-action-celebrate .dca-part-arm-l-b,
|
|
245
241
|
.dca-action-celebrate .dca-part-arm-l {
|
|
246
242
|
transform: translate(48px, 88px) rotate(135deg) scaleY(0.82);
|
|
247
243
|
transition-delay: 0ms;
|
|
@@ -252,14 +248,3 @@
|
|
|
252
248
|
transform: translate(-48px, 88px) rotate(-135deg) scaleY(0.82);
|
|
253
249
|
transition-delay: 0ms;
|
|
254
250
|
}
|
|
255
|
-
|
|
256
|
-
/* Raised arms swap the body gradient for a solid hinge colour, so a rotated arm has
|
|
257
|
-
no gradient to diverge from the torso's field. White halo capsules stay white. */
|
|
258
|
-
.dca-action-wave .dca-part-arm-r-b,
|
|
259
|
-
.dca-action-celebrate .dca-part-arm-r-b {
|
|
260
|
-
fill: var(--dca-part-raised-arm-right, #7a96fb);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
.dca-action-celebrate .dca-part-arm-l-b {
|
|
264
|
-
fill: var(--dca-part-raised-arm-left, #3161f7);
|
|
265
|
-
}
|
package/dist/styles.css
CHANGED
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { useEffect, useRef } from 'react'
|
|
2
|
-
import type {
|
|
2
|
+
import type { HeadId } from './items.js'
|
|
3
3
|
import type { AvatarPose } from './characters.js'
|
|
4
|
-
import {
|
|
4
|
+
import { HeadDecoration } from './Decorations.js'
|
|
5
5
|
import { advanceDecoration, decorationMotionProfiles, frameTransform, requestDecorationEvent, restFrame, type DecorationClock, type DecorationFrame } from './decorationMotion.js'
|
|
6
6
|
|
|
7
7
|
export function AnimatedDecoration({ id, pose, sequence, enabled, paused }: {
|
|
8
|
-
id:
|
|
8
|
+
id: HeadId; pose: AvatarPose; sequence: number; enabled: boolean; paused: boolean
|
|
9
9
|
}) {
|
|
10
10
|
const art = useRef<SVGGElement>(null)
|
|
11
11
|
const shadow = useRef<SVGEllipseElement>(null)
|
|
@@ -38,6 +38,6 @@ export function AnimatedDecoration({ id, pose, sequence, enabled, paused }: {
|
|
|
38
38
|
}, [id, pose, sequence, enabled, paused])
|
|
39
39
|
return <g data-decoration-motion={profile.kind}>
|
|
40
40
|
{profile.shadow > 0 && <ellipse ref={shadow} cx={profile.x} cy="404" rx={profile.shadow} ry="7" fill="#243344" opacity=".12" />}
|
|
41
|
-
<g ref={art} transform={frameTransform(profile, restFrame)}><
|
|
41
|
+
<g ref={art} transform={frameTransform(profile, restFrame)}><HeadDecoration id={id} /></g>
|
|
42
42
|
</g>
|
|
43
43
|
}
|
package/src/AppearanceRig.tsx
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import type { ReactNode } from 'react'
|
|
2
2
|
import type { AvatarPose, AvatarExpression } from './characters.js'
|
|
3
3
|
import type { Appearance } from './appearanceModel.js'
|
|
4
|
-
import { MascotRig, type MascotExpression } from './MascotRig.js'
|
|
5
|
-
export function AppearanceRig({ appearance, pose, expression: override, top, accessory, held }: {
|
|
6
|
-
appearance: Appearance; pose: AvatarPose; expression?: AvatarExpression
|
|
4
|
+
import { MascotRig, type BodyPaint, type Faceplate, type MascotExpression } from './MascotRig.js'
|
|
5
|
+
export function AppearanceRig({ appearance, pose, expression: override, top, accessory, faceplate, held, back, front, surface, paint }: {
|
|
6
|
+
appearance: Appearance; pose: AvatarPose; expression?: AvatarExpression
|
|
7
|
+
top?: ReactNode; accessory?: ReactNode; faceplate?: Faceplate; held?: ReactNode; back?: ReactNode; front?: ReactNode; surface?: ReactNode; paint?: BodyPaint
|
|
7
8
|
}) {
|
|
8
9
|
const derived: MascotExpression = pose === 'sleep' ? 'sleep' : ['done', 'greeting', 'acknowledge'].includes(pose) ? 'happy'
|
|
9
10
|
: ['working', 'waiting', 'blocked'].includes(pose) ? 'operator' : 'neutral'
|
|
10
11
|
const expression = override === 'base' ? 'neutral' : override ?? derived
|
|
11
|
-
return <MascotRig size={1024} expression={expression} top={top} accessory={accessory} held={held}
|
|
12
|
+
return <MascotRig size={1024} expression={expression} top={top} accessory={accessory} faceplate={faceplate} held={held} back={back} front={front} surface={surface} paint={paint}
|
|
12
13
|
baseFace={appearance.baseFace}
|
|
13
14
|
light={pose === 'blocked' ? 'error' : pose === 'done' ? 'success' : 'default'}
|
|
14
15
|
avatar={appearance.palette === -1 ? undefined : { palette: appearance.palette }} />
|
package/src/Avatar.tsx
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { useEffect, useRef, useState, type CSSProperties } from 'react'
|
|
2
2
|
import { AppearanceRig } from './AppearanceRig.js'
|
|
3
3
|
import type { AvatarPose, MotionMode, AvatarExpression, AvatarGesture } from './characters.js'
|
|
4
|
-
import { deriveAppearance,
|
|
5
|
-
|
|
6
|
-
import { SecondaryDecoration } from './Decorations.js'
|
|
4
|
+
import { deriveAppearance, type Appearance } from './appearanceModel.js'
|
|
7
5
|
import { AnimatedDecoration } from './AnimatedDecoration.js'
|
|
6
|
+
import { FaceDecoration, faceplateOf } from './FaceDecorations.js'
|
|
7
|
+
import { HandDecoration } from './HandDecorations.js'
|
|
8
|
+
import { BackDecoration, BackFrontDecoration, hasFrontPart } from './BackDecorations.js'
|
|
9
|
+
import { SkinOverlay, SkinPaintSurface, skinPaint } from './SkinDecorations.js'
|
|
8
10
|
import { useMotionEnvironment, useOnscreen, useTransitionPause } from './environment.js'
|
|
9
11
|
import { useGesture } from './useGesture.js'
|
|
10
12
|
import { useEventReplay } from './useEventReplay.js'
|
|
11
13
|
|
|
14
|
+
export type SizeTier = 'compact' | 'standard' | 'full'
|
|
15
|
+
export function sizeTier(size: number): SizeTier { return size <= 20 ? 'compact' : size < 44 ? 'standard' : 'full' }
|
|
12
16
|
|
|
13
17
|
export interface AppearanceAvatarProps {
|
|
14
18
|
appearance: Appearance
|
|
@@ -26,17 +30,18 @@ export interface AppearanceAvatarProps {
|
|
|
26
30
|
}
|
|
27
31
|
export function AppearanceAvatar({ appearance, state = 'idle', size = 44, motion = 'off', paused = false, label, eventSequence = 0, className, expression, gesture, gestureSequence = 0, onGestureComplete }: AppearanceAvatarProps) {
|
|
28
32
|
const ref = useRef<HTMLSpanElement>(null)
|
|
29
|
-
const
|
|
30
|
-
const
|
|
33
|
+
const tier = sizeTier(size)
|
|
34
|
+
const compact = tier === 'compact'
|
|
35
|
+
const visible = useOnscreen(ref, motion !== 'off' && !compact)
|
|
36
|
+
const environment = useMotionEnvironment(motion, !compact)
|
|
31
37
|
useTransitionPause(ref, paused || !visible)
|
|
32
38
|
const [displayed, setDisplayed] = useState(state)
|
|
33
39
|
const [displayedSequence, setDisplayedSequence] = useState(eventSequence)
|
|
34
40
|
const [exiting, setExiting] = useState(false)
|
|
35
41
|
const identity = JSON.stringify(appearance)
|
|
36
42
|
const previousIdentity = useRef(identity)
|
|
37
|
-
const compact = size <= 20
|
|
38
43
|
const animated = environment && !compact
|
|
39
|
-
const held = !compact &&
|
|
44
|
+
const held = !compact && appearance.hand !== 'none'
|
|
40
45
|
const [shownExpression, setShownExpression] = useState(expression)
|
|
41
46
|
useEffect(() => { if (!animated || (!paused && visible)) setShownExpression(expression) }, [expression, animated, paused, visible])
|
|
42
47
|
useGesture(ref, gesture, gestureSequence, animated, paused || !visible, onGestureComplete)
|
|
@@ -58,6 +63,7 @@ export function AppearanceAvatar({ appearance, state = 'idle', size = 44, motion
|
|
|
58
63
|
}, [state, displayed, animated, paused, visible, identity, eventSequence, displayedSequence])
|
|
59
64
|
|
|
60
65
|
const pose = animated ? displayed : state
|
|
66
|
+
const effects = compact ? 'off' : tier === 'full' && animated ? 'live' : 'static'
|
|
61
67
|
const classes = [
|
|
62
68
|
'dca-rig',
|
|
63
69
|
!compact && pose === 'waiting' ? 'dca-action-hold-sign' : '',
|
|
@@ -65,17 +71,25 @@ export function AppearanceAvatar({ appearance, state = 'idle', size = 44, motion
|
|
|
65
71
|
!compact && pose === 'done' ? 'dca-action-celebrate' : '',
|
|
66
72
|
!compact && pose === 'greeting' && !exiting ? 'dca-action-wave' : '',
|
|
67
73
|
].filter(Boolean).join(' ')
|
|
74
|
+
const paint = appearance.skin === 'none' ? undefined : skinPaint(appearance.skin)
|
|
75
|
+
const overlay = compact || appearance.skin === 'none' ? undefined
|
|
76
|
+
: paint ? <SkinPaintSurface id={appearance.skin} /> : <SkinOverlay id={appearance.skin} />
|
|
77
|
+
const faceplate = faceplateOf(appearance.face)
|
|
78
|
+
const back = appearance.back !== 'none' ? <g className="dca-part-back" data-back={appearance.back}><BackDecoration id={appearance.back} /></g> : undefined
|
|
79
|
+
const front = appearance.back !== 'none' && hasFrontPart(appearance.back) ? <g className="dca-part-front"><BackFrontDecoration id={appearance.back} /></g> : undefined
|
|
68
80
|
return <span ref={ref} className={`dca-robot${className ? ` ${className}` : ''}`} style={{ width: size, height: size, '--dca-loop': '2.6s' } as CSSProperties}
|
|
69
81
|
data-pose={pose} data-mode={motion} data-motion={animated ? 'on' : 'off'} data-paused={paused || !visible} data-exiting={exiting} data-compact={compact}
|
|
70
|
-
data-
|
|
82
|
+
data-size-tier={tier} data-effects={effects}
|
|
83
|
+
data-head={appearance.head} data-face={appearance.face} data-hand={appearance.hand} data-back={appearance.back} data-skin={appearance.skin} data-base-face={appearance.baseFace}
|
|
71
84
|
data-gesture={animated ? gesture : undefined} data-held-state={held ? (pose === 'working' || pose === 'waiting' ? 'stowed' : 'holding') : undefined}>
|
|
72
85
|
<svg className="dca-canvas" width={size} height={size} viewBox="0 0 1024 1024" fill="none" role={label ? 'img' : undefined} aria-hidden={label ? undefined : true}
|
|
73
86
|
aria-label={label}>
|
|
74
87
|
<g className="dca-body-motion"><g className={classes}>
|
|
75
88
|
<AppearanceRig appearance={appearance} pose={pose} expression={animated ? shownExpression : expression}
|
|
76
|
-
top={appearance.
|
|
77
|
-
held={
|
|
78
|
-
accessory={!compact && !
|
|
89
|
+
top={appearance.head === 'none' ? undefined : <AnimatedDecoration id={appearance.head} pose={state} sequence={eventSequence} enabled={animated} paused={paused || !visible} />}
|
|
90
|
+
held={held && appearance.hand !== 'none' ? <g data-accessory={appearance.hand} className="dca-part-held"><HandDecoration id={appearance.hand} /></g> : undefined}
|
|
91
|
+
accessory={!compact && appearance.face !== 'none' && !faceplate ? <g data-accessory={appearance.face}><FaceDecoration id={appearance.face} /></g> : undefined}
|
|
92
|
+
faceplate={faceplate} back={back} front={front} surface={overlay} paint={paint} />
|
|
79
93
|
</g></g>
|
|
80
94
|
</svg>
|
|
81
95
|
</span>
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import type { BackId } from './items.js'
|
|
3
|
+
import { Glow } from './DecorationShapes.js'
|
|
4
|
+
|
|
5
|
+
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
|
+
return <g className={flap} style={{ transformOrigin: '272px 560px' }}>
|
|
7
|
+
<path d={outer} fill={fill} stroke={outline} strokeWidth="18" strokeLinejoin="round" paintOrder="stroke fill" />
|
|
8
|
+
{inner && <path d={inner} fill={lining} />}
|
|
9
|
+
{children}
|
|
10
|
+
</g>
|
|
11
|
+
}
|
|
12
|
+
function Wings(props: Parameters<typeof Wing>[0]) {
|
|
13
|
+
return <><Wing {...props} /><g transform="matrix(-1 0 0 1 1024 0)"><Wing {...props} /></g></>
|
|
14
|
+
}
|
|
15
|
+
const batWing = {
|
|
16
|
+
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',
|
|
17
|
+
inner: 'M262 540c-60-40-120-96-166-146 20 60 6 100-16 128 56 16 76 50 56 84 56 10 82 44 74 88h52Z',
|
|
18
|
+
}
|
|
19
|
+
const butterflyWing = {
|
|
20
|
+
outer: 'M272 540C190 380 60 340 20 420c-14 56 50 96 110 120-80 12-120 70-80 130 56 56 160 26 232-40Z',
|
|
21
|
+
inner: 'M262 560c-70-120-170-150-196-90-8 40 44 70 96 90-60 14-90 60-60 100 46 40 130 14 180-44Z',
|
|
22
|
+
}
|
|
23
|
+
const angelWing = {
|
|
24
|
+
outer: 'M272 520C180 440 80 420 40 470c20 50 70 70 120 80-60 20-90 60-70 100 50 20 110 0 160-40-30 40-30 80 0 100 40-10 80-60 100-120Z',
|
|
25
|
+
inner: 'M262 540C190 480 110 462 78 492c16 36 56 52 96 60-46 16-70 46-56 76 40 14 90-4 130-36-20 30-16 58 8 70 30-10 58-46 72-92Z',
|
|
26
|
+
}
|
|
27
|
+
const dragonWing = {
|
|
28
|
+
outer: 'M272 500C160 380 60 300 20 340c0 60 60 100 100 130-60 30-100 90-80 150 60 10 130-20 190-70-40 60-40 120 0 150 60-20 100-90 110-160Z',
|
|
29
|
+
inner: 'M262 530C170 430 90 370 60 396c4 44 50 76 84 100-50 24-84 74-70 122 50 4 110-24 160-66-30 48-30 96 0 122 44-18 78-76 86-134Z',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function SunRays() {
|
|
33
|
+
const rays = Array.from({ length: 12 }, (_, index) => {
|
|
34
|
+
const a = (index * 30 * Math.PI) / 180, w = (9 * Math.PI) / 180
|
|
35
|
+
const p = (r: number, t: number) => `${(512 + r * Math.cos(t)).toFixed(0)} ${(640 + r * Math.sin(t)).toFixed(0)}`
|
|
36
|
+
return <path key={index} d={`M${p(300, a - w)} L${p(560, a)} L${p(300, a + w)}Z`} fill={index % 2 ? '#ffcf11' : '#f6b500'} stroke="#fff" strokeWidth="14" strokeLinejoin="round" paintOrder="stroke fill" />
|
|
37
|
+
})
|
|
38
|
+
return <g className="dca-fx-rotate" style={{ transformOrigin: '512px 640px' }}>{rays}</g>
|
|
39
|
+
}
|
|
40
|
+
const balloon = (cx: number, cy: number, fill: string, delay?: string) => <g className="dca-fx-hover" style={{ transformOrigin: `${cx}px ${cy}px`, animationDelay: delay }}>
|
|
41
|
+
<path d={`M${cx} ${cy + 52}L${cx - 8} ${cy + 66}h16Z`} fill={fill} />
|
|
42
|
+
<ellipse cx={cx} cy={cy} rx="46" ry="54" fill={fill} stroke="#fff" strokeWidth="14" paintOrder="stroke fill" />
|
|
43
|
+
<ellipse cx={cx - 14} cy={cy - 18} rx="10" ry="16" fill="#fff" opacity=".6" />
|
|
44
|
+
</g>
|
|
45
|
+
|
|
46
|
+
const orbit = { transform: 'translate(512 760) rotate(-12)', rx: 450, ry: 104 }
|
|
47
|
+
const orbitBodies: { key: string; delay?: string; art: ReactNode }[] = [
|
|
48
|
+
{ key: 'a', art: <><ellipse rx="64" ry="18" fill="none" stroke="#fff" strokeWidth="22" transform="rotate(-18)" /><ellipse rx="64" ry="18" fill="none" stroke="#ffe08a" strokeWidth="9" transform="rotate(-18)" /><circle r="44" fill="#fff" /><circle r="34" fill="#f6b500" /><path d="M-64 0a64 18 0 0 0 128 0" fill="none" stroke="#ffe08a" strokeWidth="9" transform="rotate(-18)" /></> },
|
|
49
|
+
{ key: 'b', delay: '-2.67s', art: <><circle r="34" fill="#fff" /><circle r="25" fill="#c9d2ff" /></> },
|
|
50
|
+
{ key: 'c', delay: '-5.33s', art: <><circle r="26" fill="#fff" /><circle r="17" fill="#ff9ad9" /></> },
|
|
51
|
+
]
|
|
52
|
+
function OrbitBodies({ side }: { side: 'front' | 'back' }) {
|
|
53
|
+
return <>{orbitBodies.map(body => <g key={body.key} className={`dca-fx dca-fx-orbit dca-fx-orbit-${side} dca-fx-orbit-${body.key}`} style={body.delay ? { animationDelay: body.delay } : undefined}>{body.art}</g>)}</>
|
|
54
|
+
}
|
|
55
|
+
function OrbitArc({ side }: { side: 'front' | 'back' }) {
|
|
56
|
+
const sweep = side === 'back' ? 1 : 0
|
|
57
|
+
const outer = `M${-orbit.rx} 0A${orbit.rx} ${orbit.ry} 0 0 ${sweep} ${orbit.rx} 0`
|
|
58
|
+
return <>
|
|
59
|
+
<path d={outer} fill="none" stroke="#fff" strokeWidth="46" />
|
|
60
|
+
<path d={outer} fill="none" stroke="#b9c4ff" strokeWidth="30" />
|
|
61
|
+
</>
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const halo = { cx: 512, cy: 229, r: 168 }
|
|
65
|
+
|
|
66
|
+
export function BackDecoration({ id }: { id: BackId }) {
|
|
67
|
+
switch (id) {
|
|
68
|
+
case 'cape': return <g className="dca-fx-sway" style={{ transformOrigin: '512px 452px' }}>
|
|
69
|
+
<path d="M262 452C190 600 130 780 104 930c80-26 160-26 240 0 56-20 112-30 168-30s112 10 168 30c80-26 160-26 240 0-26-150-86-330-158-478Z" fill="#b23a48" stroke="#fff" strokeWidth="18" strokeLinejoin="round" paintOrder="stroke fill" />
|
|
70
|
+
<path d="M296 470C236 600 190 760 176 896c56-16 108-22 168-16 56-14 112-22 168-22s112 8 168 22c60-6 112 0 168 16-14-136-60-296-120-426Z" fill="#d65a68" />
|
|
71
|
+
</g>
|
|
72
|
+
case 'jetpack': return <>
|
|
73
|
+
<g className="dca-fx dca-fx-flame" style={{ transformOrigin: '159px 800px' }}>
|
|
74
|
+
<path d="M159 892c-26-30-34-56-26-90h52c8 34 0 60-26 90Z" fill="#ffb347" />
|
|
75
|
+
<path d="M159 866c-12-18-16-34-10-52h20c6 18 2 34-10 52Z" fill="#ffe08a" />
|
|
76
|
+
</g>
|
|
77
|
+
<g className="dca-fx dca-fx-flame" style={{ transformOrigin: '865px 800px', animationDelay: '.35s' }}>
|
|
78
|
+
<path d="M865 892c-26-30-34-56-26-90h52c8 34 0 60-26 90Z" fill="#ffb347" />
|
|
79
|
+
<path d="M865 866c-12-18-16-34-10-52h20c6 18 2 34-10 52Z" fill="#ffe08a" />
|
|
80
|
+
</g>
|
|
81
|
+
<rect x="118" y="486" width="82" height="290" rx="40" fill="#8b95a5" stroke="#fff" strokeWidth="18" paintOrder="stroke fill" />
|
|
82
|
+
<rect x="824" y="486" width="82" height="290" rx="40" fill="#8b95a5" stroke="#fff" strokeWidth="18" paintOrder="stroke fill" />
|
|
83
|
+
<rect x="126" y="486" width="66" height="44" rx="20" fill="#e8654f" />
|
|
84
|
+
<rect x="832" y="486" width="66" height="44" rx="20" fill="#e8654f" />
|
|
85
|
+
<path d="M134 776h50l10 30h-70Z" fill="#3c4658" stroke="#fff" strokeWidth="12" strokeLinejoin="round" paintOrder="stroke fill" />
|
|
86
|
+
<path d="M840 776h50l10 30h-70Z" fill="#3c4658" stroke="#fff" strokeWidth="12" strokeLinejoin="round" paintOrder="stroke fill" />
|
|
87
|
+
</>
|
|
88
|
+
case 'bat-wings': return <Wings {...batWing} fill="#5b3a8c" lining="#3d2563" />
|
|
89
|
+
case 'butterfly-wings': return <Wings {...butterflyWing} fill="#f2a0b4" lining="#fff3c4" />
|
|
90
|
+
case 'angel-wings': return <>
|
|
91
|
+
<Glow blur={22} className="dca-fx-pulse"><path d={angelWing.outer} fill="#fff7dc" opacity=".6" /><path d={angelWing.outer} fill="#fff7dc" opacity=".6" transform="matrix(-1 0 0 1 1024 0)" /></Glow>
|
|
92
|
+
<Wings {...angelWing} fill="#fff" lining="#dfe4ff" outline="#b9c4ff" flap="dca-fx-flap-slow" />
|
|
93
|
+
</>
|
|
94
|
+
case 'dragon-wings': return <Wings {...dragonWing} fill="#b23a48" lining="#7f2634">
|
|
95
|
+
<g className="dca-fx dca-fx-pulse" fill="#ffb347"><circle cx="30" cy="350" r="14" /><circle cx="48" cy="618" r="12" /><circle cx="236" cy="704" r="12" /></g>
|
|
96
|
+
</Wings>
|
|
97
|
+
case 'crescent-moon': return <>
|
|
98
|
+
<path d="M330 130a190 190 0 1 0 176 254a150 150 0 0 1-176-254Z" fill="#f6d365" stroke="#fff" strokeWidth="18" strokeLinejoin="round" paintOrder="stroke fill" />
|
|
99
|
+
<path className="dca-fx dca-fx-sparkle-solo" d="M720 150l12 30 30 12-30 12-12 30-12-30-30-12 30-12Z" fill="#fff3c4" stroke="#fff" strokeWidth="10" strokeLinejoin="round" paintOrder="stroke fill" />
|
|
100
|
+
</>
|
|
101
|
+
case 'star-trail': return <g className="dca-fx-sparkle" fill="#f6b500" stroke="#fff" strokeWidth="14" strokeLinejoin="round" paintOrder="stroke fill">
|
|
102
|
+
<path d="M130 330l20 48 48 20-48 20-20 48-20-48-48-20 48-20Z" />
|
|
103
|
+
<path d="M900 250l16 38 38 16-38 16-16 38-16-38-38-16 38-16Z" style={{ animationDelay: '.6s' }} />
|
|
104
|
+
<path d="M920 690l12 30 30 12-30 12-12 30-12-30-30-12 30-12Z" style={{ animationDelay: '1.1s' }} />
|
|
105
|
+
</g>
|
|
106
|
+
case 'balloons': return <>
|
|
107
|
+
<path d="M150 384 262 560M206 306 262 560M112 484 262 560" stroke="#fff" strokeWidth="16" strokeLinecap="round" /><path d="M150 384 262 560M206 306 262 560M112 484 262 560" stroke="#8b95a5" strokeWidth="6" strokeLinecap="round" />
|
|
108
|
+
{balloon(150, 330, '#e8654f')}{balloon(206, 250, '#f6b500', '.5s')}{balloon(112, 430, '#4f7cf6', '1s')}
|
|
109
|
+
</>
|
|
110
|
+
case 'sun-rays': return <SunRays />
|
|
111
|
+
case 'halo': return <>
|
|
112
|
+
<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>
|
|
113
|
+
<circle cx={halo.cx} cy={halo.cy} r={halo.r} fill="none" stroke="#fff" strokeWidth="54" />
|
|
114
|
+
<circle cx={halo.cx} cy={halo.cy} r={halo.r} fill="none" stroke="#f6b500" strokeWidth="34" />
|
|
115
|
+
</>
|
|
116
|
+
case 'orbit-ring': return <g transform={orbit.transform}>
|
|
117
|
+
<Glow blur={22} className="dca-fx-pulse"><ellipse rx={orbit.rx} ry={orbit.ry} fill="none" stroke="#b9c4ff" strokeWidth="60" opacity=".55" /></Glow>
|
|
118
|
+
<OrbitArc side="back" />
|
|
119
|
+
<OrbitBodies side="back" />
|
|
120
|
+
</g>
|
|
121
|
+
default: return null
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function BackFrontDecoration({ id }: { id: BackId }) {
|
|
126
|
+
switch (id) {
|
|
127
|
+
case 'orbit-ring': return <g data-back-front={id} transform={orbit.transform}>
|
|
128
|
+
<OrbitArc side="front" />
|
|
129
|
+
<OrbitBodies side="front" />
|
|
130
|
+
</g>
|
|
131
|
+
default: return null
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
export function hasFrontPart(id: BackId): boolean { return id === 'orbit-ring' }
|
package/src/DecorationShapes.tsx
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { useId, type ReactNode } from 'react'
|
|
2
2
|
|
|
3
|
-
export function Silhouette({ d, fill, children }: { d: string; fill: string; children?: ReactNode }) {
|
|
4
|
-
return <><path d={d} fill={fill} stroke="#fff" strokeWidth=
|
|
3
|
+
export function Silhouette({ d, fill, stroke = 18, children }: { d: string; fill: string; stroke?: number; children?: ReactNode }) {
|
|
4
|
+
return <><path d={d} fill={fill} stroke="#fff" strokeWidth={stroke} strokeLinejoin="round" paintOrder="stroke fill" />{children}</>
|
|
5
5
|
}
|
|
6
6
|
export function Detail({ children }: { children: ReactNode }) {
|
|
7
7
|
return <g className="dca-decoration-detail">{children}</g>
|
|
8
8
|
}
|
|
9
|
+
export function Glow({ children, blur = 18, className = '' }: { children: ReactNode; blur?: number; className?: string }) {
|
|
10
|
+
const id = `dca-fx-blur-${useId().replace(/:/g, '')}`
|
|
11
|
+
return <>
|
|
12
|
+
<defs><filter id={id} x="-40%" y="-40%" width="180%" height="180%"><feGaussianBlur stdDeviation={blur} /></filter></defs>
|
|
13
|
+
<g className={`dca-fx dca-fx-glow ${className}`.trim()} filter={`url(#${id})`}>{children}</g>
|
|
14
|
+
</>
|
|
15
|
+
}
|
|
16
|
+
export function useClipId() { return `dca-clip-${useId().replace(/:/g, '')}` }
|
package/src/Decorations.tsx
CHANGED
|
@@ -1,31 +1,56 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { useId } from 'react'
|
|
2
|
+
import type { ItemId, HeadId, SkinId } from './items.js'
|
|
3
|
+
import { itemOf } from './items.js'
|
|
4
4
|
import { HatDecoration } from './HatDecorations.js'
|
|
5
5
|
import { ObjectDecoration } from './ObjectDecorations.js'
|
|
6
|
+
import { FaceDecoration, FaceplateEyes, FaceplatePlate, isFaceplate } from './FaceDecorations.js'
|
|
7
|
+
import { HandDecoration } from './HandDecorations.js'
|
|
8
|
+
import { BackDecoration, BackFrontDecoration } from './BackDecorations.js'
|
|
9
|
+
import { SkinOverlay, SkinPaintSurface, skinPaint } from './SkinDecorations.js'
|
|
6
10
|
import { decorationOf } from './decorationCatalog.js'
|
|
7
11
|
|
|
8
|
-
export function
|
|
9
|
-
if (isHeld(id)) return <HeldDecoration id={id} />
|
|
10
|
-
switch (id) {
|
|
11
|
-
case 'forehead-goggles': return <g strokeLinejoin="round">
|
|
12
|
-
<path d="M374 419h109v46H374Zm167 0h109v46H541Z" fill="#a2c5d1" stroke="#8b7568" strokeWidth="14" />
|
|
13
|
-
<path d="M483 442q29 20 58 0" stroke="#8b7568" strokeWidth="11" fill="none" />
|
|
14
|
-
<path d="m389 429 18 21m149-21 18 21" stroke="#e0eff1" strokeWidth="8" />
|
|
15
|
-
</g>
|
|
16
|
-
default: return null
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
export function PrimaryDecoration({ id }: { id: PrimaryId }) {
|
|
12
|
+
export function HeadDecoration({ id }: { id: HeadId }) {
|
|
20
13
|
return <g data-decoration={id}><HatDecoration id={id} /><ObjectDecoration id={id} /></g>
|
|
21
14
|
}
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
|
|
16
|
+
function SkinSpecimen({ id }: { id: SkinId }) {
|
|
17
|
+
const uid = useId().replace(/:/g, '')
|
|
18
|
+
const paint = skinPaint(id)
|
|
19
|
+
return <g data-skin={id}>
|
|
20
|
+
<defs>
|
|
21
|
+
{paint?.render(`dca-swatch-paint-${uid}`)}
|
|
22
|
+
<clipPath id={`dca-swatch-clip-${uid}`}><rect x="243" y="408" width="538" height="426" rx="113" /></clipPath>
|
|
23
|
+
</defs>
|
|
24
|
+
<rect x="243" y="408" width="538" height="426" rx="113" fill={paint ? `url(#dca-swatch-paint-${uid})` : '#4f7cf6'} stroke="#fff" strokeWidth="18" paintOrder="stroke fill" />
|
|
25
|
+
<g clipPath={`url(#dca-swatch-clip-${uid})`}>{paint ? <SkinPaintSurface id={id} /> : <SkinOverlay id={id} />}</g>
|
|
26
|
+
</g>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function SlotDecoration({ id }: { id: ItemId }) {
|
|
30
|
+
const item = itemOf(id)
|
|
31
|
+
switch (item.slot) {
|
|
32
|
+
case 'head': return <HeadDecoration id={item.id} />
|
|
33
|
+
case 'face': return isFaceplate(item.id)
|
|
34
|
+
? <g data-accessory={item.id}><rect x="295" y="464" width="434" height="315" rx="78" fill="#fff" /><FaceplatePlate id={item.id} /><FaceplateEyes id={item.id} /></g>
|
|
35
|
+
: <g data-accessory={item.id}><FaceDecoration id={item.id} /></g>
|
|
36
|
+
case 'hand': return <g data-accessory={item.id}><HandDecoration id={item.id} /></g>
|
|
37
|
+
case 'back': return <g data-back={item.id}><BackDecoration id={item.id} /><BackFrontDecoration id={item.id} /></g>
|
|
38
|
+
case 'skin': return <SkinSpecimen id={item.id} />
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const swatchViewBox: Record<ReturnType<typeof itemOf>['slot'], string> = {
|
|
43
|
+
head: '265 105 494 360',
|
|
44
|
+
face: '300 280 424 324',
|
|
45
|
+
hand: '40 330 400 400',
|
|
46
|
+
back: '20 120 984 800',
|
|
47
|
+
skin: '203 368 618 506',
|
|
24
48
|
}
|
|
25
|
-
export function DecorationSwatch({ id, size = 112 }: { id:
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
return <svg width={size} height={size} viewBox={
|
|
29
|
-
|
|
49
|
+
export function DecorationSwatch({ id, size = 112 }: { id: ItemId; size?: number }) {
|
|
50
|
+
const item = itemOf(id)
|
|
51
|
+
const viewBox = item.slot === 'face' && isFaceplate(item.id) ? '255 404 514 436' : swatchViewBox[item.slot]
|
|
52
|
+
return <svg width={size} height={size} viewBox={viewBox} fill="none" role="img" aria-label={`${decorationOf(id).name} specimen`}
|
|
53
|
+
className="dca-swatch dca-part-robot" data-expression="neutral" data-slot={item.slot} data-effects="static">
|
|
54
|
+
<SlotDecoration id={id} />
|
|
30
55
|
</svg>
|
|
31
56
|
}
|