fix(blobbi): reposition dirt marks to lower body edges, avoid facial zones
Protected zones (dirt marks NEVER appear here): - Eyes, mouth, eyebrows - Tears, saliva/drool, blush marks, sparkles - Upper-center body area where face elements live Preferred dirt placement zones: - Lower-left edge of body silhouette - Lower-right edge of body silhouette - Bottom edge (well below face region) Variant differences: - Egg: dust at lower outer shell edges only, no center-front placement - Baby (100x100): safe zone y > 72, prefer x < 35 or x > 65 - Adult (200x200): safe zone y > 120, prefer x < 85 or x > 115 Also updated dust particle positions to follow same rules.
This commit is contained in:
Generated
-25
@@ -4706,7 +4706,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4720,7 +4719,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4734,7 +4732,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4748,7 +4745,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4762,7 +4758,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4776,7 +4771,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4790,7 +4784,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4804,7 +4797,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4818,7 +4810,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4832,7 +4823,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4846,7 +4836,6 @@
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4860,7 +4849,6 @@
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4874,7 +4862,6 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4888,7 +4875,6 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4902,7 +4888,6 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4916,7 +4901,6 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4930,7 +4914,6 @@
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4944,7 +4927,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4958,7 +4940,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4972,7 +4953,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4986,7 +4966,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5000,7 +4979,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5014,7 +4992,6 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5028,7 +5005,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5042,7 +5018,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
||||
@@ -702,15 +702,20 @@ export const EggGraphic: React.FC<EggGraphicProps> = ({
|
||||
|
||||
{/* ── Status Effects ─────────────────────────────────────────────── */}
|
||||
|
||||
{/* Dirty effect: sweat droplet + dust particles (front + back layers) */}
|
||||
{/* Dirty effect: sweat droplet + dust at lower shell edges
|
||||
Placement rules for egg:
|
||||
- Sweat droplet stays at upper-left (outside egg, not on face)
|
||||
- Dust particles only at lower outer shell edges
|
||||
- Avoid center-front placement entirely
|
||||
*/}
|
||||
{statusEffects?.dirty && (
|
||||
<>
|
||||
{/* Sweat droplet - upper left of egg */}
|
||||
{/* Sweat droplet - upper left outside egg shell */}
|
||||
<div
|
||||
className="absolute animate-egg-sweat-drop"
|
||||
style={{
|
||||
top: '15%',
|
||||
left: '8%',
|
||||
left: '5%',
|
||||
width: '0.6em',
|
||||
height: '0.9em',
|
||||
background: 'linear-gradient(180deg, rgba(147, 197, 253, 0.9) 0%, rgba(59, 130, 246, 0.7) 100%)',
|
||||
@@ -718,85 +723,84 @@ export const EggGraphic: React.FC<EggGraphicProps> = ({
|
||||
zIndex: 20,
|
||||
}}
|
||||
/>
|
||||
{/* Dust particles underneath (back layer) */}
|
||||
{/* Dust particles underneath (back layer) - at lower edges */}
|
||||
<div
|
||||
className="absolute animate-egg-dust"
|
||||
style={{
|
||||
bottom: '-5%',
|
||||
left: '25%',
|
||||
width: '50%',
|
||||
left: '20%',
|
||||
width: '60%',
|
||||
height: '0.4em',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-around',
|
||||
justifyContent: 'space-between',
|
||||
zIndex: 5,
|
||||
}}
|
||||
>
|
||||
{[0, 1, 2].map((i) => (
|
||||
<div
|
||||
key={i}
|
||||
style={{
|
||||
width: '0.3em',
|
||||
height: '0.3em',
|
||||
background: 'rgba(87, 83, 78, 0.7)', // Stronger brown color
|
||||
borderRadius: '50%',
|
||||
animationDelay: `${i * 0.3}s`,
|
||||
}}
|
||||
className="animate-egg-dust-particle"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{/* Front-layer dust particles - floating in front of egg */}
|
||||
<div
|
||||
className="absolute"
|
||||
style={{
|
||||
top: '60%',
|
||||
left: '15%',
|
||||
width: '70%',
|
||||
height: '30%',
|
||||
pointerEvents: 'none',
|
||||
zIndex: 25, // In front of egg
|
||||
}}
|
||||
>
|
||||
{/* Front dust particle 1 - lower left */}
|
||||
{/* Left edge particle */}
|
||||
<div
|
||||
className="absolute animate-egg-dust-particle"
|
||||
style={{
|
||||
bottom: '10%',
|
||||
left: '5%',
|
||||
width: '0.25em',
|
||||
height: '0.25em',
|
||||
background: 'rgba(68, 64, 60, 0.75)', // Darker, more visible
|
||||
borderRadius: '50%',
|
||||
animationDelay: '0.1s',
|
||||
}}
|
||||
/>
|
||||
{/* Front dust particle 2 - center */}
|
||||
<div
|
||||
className="absolute animate-egg-dust-particle"
|
||||
style={{
|
||||
bottom: '25%',
|
||||
left: '45%',
|
||||
width: '0.2em',
|
||||
height: '0.2em',
|
||||
width: '0.3em',
|
||||
height: '0.3em',
|
||||
background: 'rgba(87, 83, 78, 0.7)',
|
||||
borderRadius: '50%',
|
||||
animationDelay: '0.5s',
|
||||
}}
|
||||
className="animate-egg-dust-particle"
|
||||
/>
|
||||
{/* Front dust particle 3 - lower right */}
|
||||
{/* Right edge particle */}
|
||||
<div
|
||||
className="absolute animate-egg-dust-particle"
|
||||
style={{
|
||||
bottom: '5%',
|
||||
right: '10%',
|
||||
width: '0.22em',
|
||||
height: '0.22em',
|
||||
background: 'rgba(68, 64, 60, 0.7)',
|
||||
width: '0.28em',
|
||||
height: '0.28em',
|
||||
background: 'rgba(87, 83, 78, 0.65)',
|
||||
borderRadius: '50%',
|
||||
animationDelay: '0.8s',
|
||||
animationDelay: '0.4s',
|
||||
}}
|
||||
className="animate-egg-dust-particle"
|
||||
/>
|
||||
</div>
|
||||
{/* Front-layer dust - ONLY at lower-left and lower-right edges */}
|
||||
{/* Lower-left edge particle */}
|
||||
<div
|
||||
className="absolute animate-egg-dust-particle"
|
||||
style={{
|
||||
bottom: '12%',
|
||||
left: '8%',
|
||||
width: '0.22em',
|
||||
height: '0.22em',
|
||||
background: 'rgba(68, 64, 60, 0.7)',
|
||||
borderRadius: '50%',
|
||||
zIndex: 25,
|
||||
animationDelay: '0.1s',
|
||||
}}
|
||||
/>
|
||||
{/* Lower-right edge particle */}
|
||||
<div
|
||||
className="absolute animate-egg-dust-particle"
|
||||
style={{
|
||||
bottom: '15%',
|
||||
right: '6%',
|
||||
width: '0.2em',
|
||||
height: '0.2em',
|
||||
background: 'rgba(68, 64, 60, 0.65)',
|
||||
borderRadius: '50%',
|
||||
zIndex: 25,
|
||||
animationDelay: '0.5s',
|
||||
}}
|
||||
/>
|
||||
{/* Very bottom left particle */}
|
||||
<div
|
||||
className="absolute animate-egg-dust-particle"
|
||||
style={{
|
||||
bottom: '5%',
|
||||
left: '15%',
|
||||
width: '0.18em',
|
||||
height: '0.18em',
|
||||
background: 'rgba(87, 83, 78, 0.6)',
|
||||
borderRadius: '50%',
|
||||
zIndex: 25,
|
||||
animationDelay: '0.8s',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
@@ -69,41 +69,63 @@ function estimatePathBounds(pathD: string): { minY: number; maxY: number } {
|
||||
|
||||
// ─── Dirt Marks ───────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Protected facial zones (where dirt marks should NEVER appear):
|
||||
* - Eyes, mouth, eyebrows
|
||||
* - Tears, saliva/drool, blush marks
|
||||
* - Sparkles and other facial extras
|
||||
* - Upper-center body area where face elements live
|
||||
*
|
||||
* Preferred dirt placement zones:
|
||||
* - Lower-left edge of body silhouette
|
||||
* - Lower-right edge of body silhouette
|
||||
* - Bottom edge (below face region)
|
||||
* - Side contours in lower half of body
|
||||
*
|
||||
* Baby (100x100 viewBox):
|
||||
* - Face region: x: 30-70, y: 35-70 (AVOID)
|
||||
* - Safe lower edges: y > 72, prefer x < 35 or x > 65
|
||||
*
|
||||
* Adult (200x200 viewBox):
|
||||
* - Face region: x: 80-120, y: 70-115 (AVOID)
|
||||
* - Safe lower edges: y > 120, prefer x < 85 or x > 115
|
||||
*/
|
||||
|
||||
/**
|
||||
* Dirt mark positions for baby variant (100x100 viewBox).
|
||||
* Baby body path: roughly x: 25-75, y: 15-88, center at x=50
|
||||
* Positioned at lower-left and lower-right edges, avoiding face region.
|
||||
*/
|
||||
const BABY_DIRT_POSITIONS = [
|
||||
// Primary marks - well within the droplet body silhouette
|
||||
{ x: 40, y: 60, angle: 12, length: 3 }, // left-center
|
||||
{ x: 55, y: 55, angle: -8, length: 2.5 }, // right-center
|
||||
{ x: 48, y: 50, angle: 5, length: 2.5 }, // center, mid
|
||||
// Additional marks for higher counts
|
||||
{ x: 52, y: 68, angle: -5, length: 2 }, // center, lower
|
||||
{ x: 42, y: 45, angle: 15, length: 2 }, // left, upper-mid
|
||||
// Primary marks - lower side edges, well below face
|
||||
{ x: 30, y: 76, angle: 25, length: 2.5 }, // lower-left edge
|
||||
{ x: 68, y: 74, angle: -20, length: 2.5 }, // lower-right edge
|
||||
{ x: 32, y: 82, angle: 15, length: 2 }, // very low left
|
||||
// Additional marks for higher counts - still at edges
|
||||
{ x: 66, y: 80, angle: -15, length: 2 }, // very low right
|
||||
{ x: 50, y: 84, angle: 5, length: 2 }, // bottom center (safe - well below face)
|
||||
];
|
||||
|
||||
/**
|
||||
* Dirt mark positions for adult variant (200x200 viewBox).
|
||||
* Adult body varies by form, but generally centered around x=100.
|
||||
* Using conservative positions that work across most forms.
|
||||
* Positioned at lower side edges, avoiding face region entirely.
|
||||
*/
|
||||
const ADULT_DIRT_POSITIONS = [
|
||||
// Primary marks - centered in the main body area
|
||||
{ x: 88, y: 95, angle: 12, length: 5 }, // left of center
|
||||
{ x: 108, y: 90, angle: -8, length: 4.5 }, // right of center
|
||||
{ x: 98, y: 85, angle: 5, length: 4 }, // center
|
||||
// Primary marks - lower side edges
|
||||
{ x: 78, y: 125, angle: 30, length: 4 }, // lower-left side
|
||||
{ x: 122, y: 122, angle: -25, length: 4 }, // lower-right side
|
||||
{ x: 80, y: 138, angle: 20, length: 3.5 }, // very low left
|
||||
// Additional marks for higher counts
|
||||
{ x: 102, y: 100, angle: -5, length: 3.5 }, // center, lower
|
||||
{ x: 92, y: 80, angle: 15, length: 3.5 }, // left, upper-mid
|
||||
{ x: 120, y: 135, angle: -20, length: 3.5 }, // very low right
|
||||
{ x: 100, y: 145, angle: 5, length: 3 }, // bottom center (safe - well below face)
|
||||
];
|
||||
|
||||
/**
|
||||
* Generate dirt marks/scratches on the body.
|
||||
*
|
||||
* Positions are constrained to the Blobbi body silhouette:
|
||||
* - Baby (100x100): centered around x=50, within ~38-62 range
|
||||
* - Adult (200x200): centered around x=100, scaled appropriately
|
||||
* Placement rules:
|
||||
* - AVOID face region (eyes, mouth, eyebrows, tears, drool, blush, sparkles)
|
||||
* - PREFER lower-left and lower-right edges of body silhouette
|
||||
* - Bottom edge placement is safe (well below face elements)
|
||||
*
|
||||
* @param config - Dirt marks configuration including variant
|
||||
* @returns SVG markup for dirt marks
|
||||
@@ -148,38 +170,39 @@ export function generateDirtMarks(config: DirtMarksConfig): string {
|
||||
|
||||
/**
|
||||
* Dust particle positions for baby variant (100x100 viewBox).
|
||||
* Front-layer particles are positioned in front of the body for stronger dirty read.
|
||||
* All particles positioned at lower edges, avoiding face region.
|
||||
*/
|
||||
const BABY_DUST_POSITIONS = {
|
||||
// Back layer - below the body
|
||||
back: [
|
||||
{ x: 40, y: 90, r: 1.5, delay: 0 },
|
||||
{ x: 35, y: 90, r: 1.5, delay: 0 },
|
||||
{ x: 50, y: 92, r: 1.2, delay: 0.3 },
|
||||
{ x: 58, y: 89, r: 1.3, delay: 0.6 },
|
||||
{ x: 65, y: 89, r: 1.3, delay: 0.6 },
|
||||
],
|
||||
// Front layer - floating in front of body
|
||||
// Front layer - at lower side edges, NOT in face region
|
||||
front: [
|
||||
{ x: 38, y: 70, r: 1.2, delay: 0.1 },
|
||||
{ x: 58, y: 65, r: 1.0, delay: 0.4 },
|
||||
{ x: 48, y: 75, r: 0.9, delay: 0.7 },
|
||||
{ x: 28, y: 78, r: 1.0, delay: 0.1 }, // lower-left edge
|
||||
{ x: 70, y: 76, r: 0.9, delay: 0.4 }, // lower-right edge
|
||||
{ x: 32, y: 84, r: 0.8, delay: 0.7 }, // very low left
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Dust particle positions for adult variant (200x200 viewBox).
|
||||
* All particles positioned at lower edges, avoiding face region.
|
||||
*/
|
||||
const ADULT_DUST_POSITIONS = {
|
||||
// Back layer - below the body
|
||||
back: [
|
||||
{ x: 85, y: 180, r: 2.5, delay: 0 },
|
||||
{ x: 100, y: 183, r: 2.2, delay: 0.3 },
|
||||
{ x: 115, y: 178, r: 2.3, delay: 0.6 },
|
||||
{ x: 80, y: 175, r: 2.5, delay: 0 },
|
||||
{ x: 100, y: 180, r: 2.2, delay: 0.3 },
|
||||
{ x: 120, y: 173, r: 2.3, delay: 0.6 },
|
||||
],
|
||||
// Front layer - floating in front of body
|
||||
// Front layer - at lower side edges, NOT in face region
|
||||
front: [
|
||||
{ x: 82, y: 105, r: 2.0, delay: 0.1 },
|
||||
{ x: 115, y: 95, r: 1.8, delay: 0.4 },
|
||||
{ x: 98, y: 110, r: 1.6, delay: 0.7 },
|
||||
{ x: 75, y: 130, r: 1.8, delay: 0.1 }, // lower-left side
|
||||
{ x: 125, y: 128, r: 1.6, delay: 0.4 }, // lower-right side
|
||||
{ x: 78, y: 142, r: 1.4, delay: 0.7 }, // very low left
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user