From ef64668facd20135e77dc120a7d8a9af5641b89e Mon Sep 17 00:00:00 2001 From: filemon Date: Tue, 21 Apr 2026 23:30:42 -0300 Subject: [PATCH] Add Pandi color customizer: tinted-white body, dark-tinted patches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pandi now applies baseColor and secondaryColor instead of staying hardcoded black and white. Light areas (body, head): a very soft tinted-white derived from baseColor's hue at L=95 S=min(baseSat,30). Clearly not pure white, but stays close — preserves the hue family without going full- strength. Stroke uses the same hue at L=90 S=20. Dark areas (ear patches, eye patches, inner ears, arms, legs, nose, mouth): derived from secondaryColor's hue forced to L=20 S=30 (primary dark) and L=27 S=20 (lighter dark for gradients and inner fills). Maintains proper panda light-vs-dark contrast. Eye color: unchanged — still applied via pandiPupil3D gradient replacement in the existing applyPupilGradient path. No other adult form customizers were modified. --- .../adult-blobbi/lib/adult-svg-customizer.ts | 67 ++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/src/blobbi/adult-blobbi/lib/adult-svg-customizer.ts b/src/blobbi/adult-blobbi/lib/adult-svg-customizer.ts index 58001dff..c0c5c741 100644 --- a/src/blobbi/adult-blobbi/lib/adult-svg-customizer.ts +++ b/src/blobbi/adult-blobbi/lib/adult-svg-customizer.ts @@ -11,6 +11,7 @@ */ import type { Blobbi } from '@/blobbi/core/types/blobbi'; +import { hexToHsl, hslToHex } from '@/blobbi/core/lib/color-guardrails'; import { lightenColor, darkenColor, uniquifySvgIds, ensureSvgFillsContainer } from '@/blobbi/ui/lib/svg'; import type { AdultForm, AdultSvgCustomization } from '../types/adult.types'; @@ -507,6 +508,70 @@ function customizeOwli(svgText: string, baseColor: string, secondaryColor?: stri type FormCustomizer = (svgText: string, baseColor: string, secondaryColor?: string) => string; +/** + * Pandi: Light areas get a soft tinted-white from baseColor; + * dark areas (ears, eye patches, arms, legs) get a dark tint from secondaryColor. + * + * The tinted white preserves the hue of baseColor at very high lightness (L=95) + * so Pandi looks subtly colored rather than pure white, while the dark areas + * use secondaryColor's hue at panda-appropriate darkness (L=20/27) to maintain + * the characteristic light-vs-dark panda contrast. + */ +function customizePandi(svgText: string, baseColor: string, secondaryColor?: string): string { + let svg = svgText; + + // ── Derive tinted-white from baseColor ── + const baseHsl = hexToHsl(baseColor); + const tintFill = hslToHex(baseHsl.h, Math.min(baseHsl.s, 30), 95); + const tintStroke = hslToHex(baseHsl.h, Math.min(baseHsl.s, 20), 90); + + // ── Derive dark tints from secondaryColor (or baseColor if no secondary) ── + const darkSource = secondaryColor ?? baseColor; + const darkHsl = hexToHsl(darkSource); + const darkPrimary = hslToHex(darkHsl.h, 30, 20); // replaces #1f2937 + const darkLight = hslToHex(darkHsl.h, 20, 27); // replaces #374151 + + // ── Light areas: body & head (flat fills + strokes) ── + // Original: fill="#f8fafc" stroke="#e2e8f0" + svg = svg.replaceAll('fill="#f8fafc"', `fill="${tintFill}"`); + svg = svg.replaceAll('stroke="#e2e8f0"', `stroke="${tintStroke}"`); + + // ── Dark areas with data-blobbi-skip: ear patches, inner ears, eye patches ── + // These use data-blobbi-skip="true" to prevent eye-animation from touching them. + // Original dark: fill="#1f2937", lighter dark: fill="#374151" + svg = svg.replaceAll('fill="#1f2937" data-blobbi-skip="true"', `fill="${darkPrimary}" data-blobbi-skip="true"`); + svg = svg.replaceAll('fill="#374151" data-blobbi-skip="true"', `fill="${darkLight}" data-blobbi-skip="true"`); + + // ── Arm & leg gradients ── + svg = replaceGradient(svg, 'pandiArm3D', ` + + + `); + svg = replaceGradient(svg, 'pandiLeg3D', ` + + + `); + + // ── Nose gradient ── + svg = replaceGradient(svg, 'pandiNose3D', ` + + + `); + + // ── Mouth gradient ── + svg = replaceGradient(svg, 'pandiMouth3D', ` + + + + `); + + // ── Sleeping variant: closed-eye strokes and mouth dot use #1e293b ── + svg = svg.replaceAll('stroke="#1e293b"', `stroke="${darkPrimary}"`); + svg = svg.replaceAll('fill="#1e293b"', `fill="${darkPrimary}"`); + + return svg; +} + const FORM_CUSTOMIZERS: Partial> = { bloomi: customizeBloomi, breezy: customizeBreezy, @@ -520,10 +585,10 @@ const FORM_CUSTOMIZERS: Partial> = { leafy: customizeLeafy, mushie: customizeMushie, owli: customizeOwli, + pandi: customizePandi, rocky: customizeRocky, rosey: customizeRosey, starri: customizeStarri, - // pandi keeps original colors - it's a panda with black/white coloring by design }; // ─── Main Customization ───────────────────────────────────────────────────────