feat(worksheets): implement true RGB color interpolation for custom difficulty

Previously was using binary selection (pick closer preset's color).
Now performs smooth linear RGB interpolation based on pythagorean
distance in 2D difficulty space.

Implementation:
- Added DIFFICULTY_RGB with actual RGB values for each preset
- Created interpolateRGB() to blend colors channel-by-channel
- getInterpolatedColor() now returns rgb() strings instead of tokens
- Panda CSS accepts both tokens (presets) and RGB strings (custom)

When moving slider between presets, the button color now smoothly
transitions through the color spectrum (green → blue → yellow →
orange → red) based on exact position in difficulty space.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock 2025-11-07 18:16:25 -06:00
parent a7412adbee
commit 952cffa2d1
1 changed files with 45 additions and 4 deletions

View File

@ -364,9 +364,21 @@ export interface DifficultyProfile {
* Each profile balances problem complexity (regrouping) with scaffolding support
*/
/**
* Color palette for difficulty levels
* Color palette for difficulty levels (RGB values for interpolation)
* Subtle progression from green (easy) to red (hard)
*/
const DIFFICULTY_RGB = {
beginner: { bg: [220, 252, 231], border: [34, 197, 94], text: [22, 101, 52] }, // green
earlyLearner: { bg: [219, 234, 254], border: [59, 130, 246], text: [30, 64, 175] }, // blue
intermediate: { bg: [254, 249, 195], border: [234, 179, 8], text: [133, 77, 14] }, // yellow
advanced: { bg: [255, 237, 213], border: [249, 115, 22], text: [154, 52, 18] }, // orange
expert: { bg: [254, 226, 226], border: [239, 68, 68], text: [153, 27, 27] }, // red
} as const
/**
* Color palette for difficulty levels (Panda CSS tokens)
* Used for preset buttons in dropdown menu
*/
export const DIFFICULTY_COLORS = {
beginner: { bg: 'green.100', border: 'green.500', text: 'green.800' },
earlyLearner: { bg: 'blue.100', border: 'blue.500', text: 'blue.800' },
@ -375,6 +387,24 @@ export const DIFFICULTY_COLORS = {
expert: { bg: 'red.100', border: 'red.500', text: 'red.800' },
} as const
/**
* Linearly interpolate between two RGB colors
*/
function interpolateRGB(color1: number[], color2: number[], weight: number): number[] {
return [
Math.round(color1[0] + (color2[0] - color1[0]) * weight),
Math.round(color1[1] + (color2[1] - color1[1]) * weight),
Math.round(color1[2] + (color2[2] - color1[2]) * weight),
]
}
/**
* Convert RGB array to CSS string
*/
function rgbToString(rgb: number[]): string {
return `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`
}
/**
* Get interpolated color between two presets based on distance
* Uses pythagorean distance in 2D difficulty space to blend colors
@ -413,9 +443,20 @@ export function getInterpolatedColor(
const totalDistance = distanceToEasier + distanceToHarder
const weight = totalDistance > 0 ? distanceToEasier / totalDistance : 0.5
// For now, use discrete color based on which is closer
// (True color interpolation would require RGB conversion)
return weight < 0.5 ? DIFFICULTY_COLORS[nearestEasier] : DIFFICULTY_COLORS[nearestHarder]
// Get RGB values for both presets
const easierRGB = DIFFICULTY_RGB[nearestEasier]
const harderRGB = DIFFICULTY_RGB[nearestHarder]
// Interpolate each color channel
const bgRGB = interpolateRGB(easierRGB.bg, harderRGB.bg, weight)
const borderRGB = interpolateRGB(easierRGB.border, harderRGB.border, weight)
const textRGB = interpolateRGB(easierRGB.text, harderRGB.text, weight)
return {
bg: rgbToString(bgRGB),
border: rgbToString(borderRGB),
text: rgbToString(textRGB),
}
}
export const DIFFICULTY_PROFILES: Record<string, DifficultyProfile> = {