217 lines
11 KiB
JavaScript

/**
* Coloring Page Generator
* Creates themed coloring pages with pre-defined SVG shapes
*/
// Library of coloring illustrations (outlines only)
const illustrations = {
// Animals
cat: `<g transform='translate(150, 100) scale(3)'>
<ellipse cx='25' cy='35' rx='20' ry='25' fill='none' stroke='#000' stroke-width='2'/>
<ellipse cx='25' cy='15' rx='18' ry='15' fill='none' stroke='#000' stroke-width='2'/>
<polygon points='8,5 12,18 3,15' fill='none' stroke='#000' stroke-width='2'/>
<polygon points='42,5 38,18 47,15' fill='none' stroke='#000' stroke-width='2'/>
<circle cx='18' cy='12' r='3' fill='none' stroke='#000' stroke-width='1.5'/>
<circle cx='32' cy='12' r='3' fill='none' stroke='#000' stroke-width='1.5'/>
<ellipse cx='25' cy='20' rx='4' ry='3' fill='none' stroke='#000' stroke-width='1.5'/>
<path d='M25,23 L25,28' stroke='#000' stroke-width='1.5'/>
<path d='M21,26 Q25,30 29,26' fill='none' stroke='#000' stroke-width='1.5'/>
<path d='M8,15 L-5,12' stroke='#000' stroke-width='1'/>
<path d='M8,18 L-5,18' stroke='#000' stroke-width='1'/>
<path d='M42,15 L55,12' stroke='#000' stroke-width='1'/>
<path d='M42,18 L55,18' stroke='#000' stroke-width='1'/>
</g>`,
dog: `<g transform='translate(150, 100) scale(3)'>
<ellipse cx='25' cy='40' rx='22' ry='28' fill='none' stroke='#000' stroke-width='2'/>
<ellipse cx='25' cy='12' rx='18' ry='14' fill='none' stroke='#000' stroke-width='2'/>
<ellipse cx='8' cy='8' rx='8' ry='12' fill='none' stroke='#000' stroke-width='2'/>
<ellipse cx='42' cy='8' rx='8' ry='12' fill='none' stroke='#000' stroke-width='2'/>
<circle cx='18' cy='10' r='3' fill='none' stroke='#000' stroke-width='1.5'/>
<circle cx='32' cy='10' r='3' fill='none' stroke='#000' stroke-width='1.5'/>
<ellipse cx='25' cy='18' rx='6' ry='4' fill='none' stroke='#000' stroke-width='1.5'/>
<path d='M22,24 Q25,28 28,24' fill='none' stroke='#000' stroke-width='1.5'/>
<path d='M47,55 Q55,65 48,70' fill='none' stroke='#000' stroke-width='2'/>
</g>`,
butterfly: `<g transform='translate(120, 80) scale(3.5)'>
<ellipse cx='25' cy='25' rx='20' ry='18' fill='none' stroke='#000' stroke-width='2'/>
<ellipse cx='55' cy='25' rx='20' ry='18' fill='none' stroke='#000' stroke-width='2'/>
<ellipse cx='25' cy='50' rx='15' ry='12' fill='none' stroke='#000' stroke-width='2'/>
<ellipse cx='55' cy='50' rx='15' ry='12' fill='none' stroke='#000' stroke-width='2'/>
<ellipse cx='40' cy='35' rx='4' ry='25' fill='none' stroke='#000' stroke-width='2'/>
<path d='M38,10 Q30,0 25,5' fill='none' stroke='#000' stroke-width='1.5'/>
<path d='M42,10 Q50,0 55,5' fill='none' stroke='#000' stroke-width='1.5'/>
<circle cx='25' cy='25' r='5' fill='none' stroke='#000' stroke-width='1'/>
<circle cx='55' cy='25' r='5' fill='none' stroke='#000' stroke-width='1'/>
</g>`,
fish: `<g transform='translate(100, 120) scale(4)'>
<ellipse cx='40' cy='25' rx='35' ry='20' fill='none' stroke='#000' stroke-width='2'/>
<polygon points='5,25 -15,10 -15,40' fill='none' stroke='#000' stroke-width='2'/>
<circle cx='55' cy='20' r='4' fill='none' stroke='#000' stroke-width='1.5'/>
<path d='M40,5 Q50,0 55,8' fill='none' stroke='#000' stroke-width='2'/>
<path d='M40,45 Q50,50 55,42' fill='none' stroke='#000' stroke-width='2'/>
<path d='M25,15 Q30,25 25,35' fill='none' stroke='#000' stroke-width='1'/>
<path d='M35,13 Q40,25 35,37' fill='none' stroke='#000' stroke-width='1'/>
</g>`,
flower: `<g transform='translate(150, 80) scale(3.5)'>
<circle cx='25' cy='30' r='12' fill='none' stroke='#000' stroke-width='2'/>
<ellipse cx='25' cy='10' rx='10' ry='12' fill='none' stroke='#000' stroke-width='2'/>
<ellipse cx='45' cy='22' rx='10' ry='12' transform='rotate(60, 45, 22)' fill='none' stroke='#000' stroke-width='2'/>
<ellipse cx='40' cy='45' rx='10' ry='12' transform='rotate(120, 40, 45)' fill='none' stroke='#000' stroke-width='2'/>
<ellipse cx='10' cy='45' rx='10' ry='12' transform='rotate(-120, 10, 45)' fill='none' stroke='#000' stroke-width='2'/>
<ellipse cx='5' cy='22' rx='10' ry='12' transform='rotate(-60, 5, 22)' fill='none' stroke='#000' stroke-width='2'/>
<path d='M25,42 L25,80' stroke='#000' stroke-width='3'/>
<path d='M25,55 Q35,50 40,55' fill='none' stroke='#000' stroke-width='2'/>
<path d='M25,65 Q15,60 10,65' fill='none' stroke='#000' stroke-width='2'/>
</g>`,
star: `<g transform='translate(125, 80) scale(5)'>
<polygon points='25,0 31,18 50,18 35,30 41,50 25,38 9,50 15,30 0,18 19,18' fill='none' stroke='#000' stroke-width='2'/>
<circle cx='20' cy='22' r='2' fill='none' stroke='#000' stroke-width='1'/>
<circle cx='30' cy='22' r='2' fill='none' stroke='#000' stroke-width='1'/>
<path d='M22,28 Q25,32 28,28' fill='none' stroke='#000' stroke-width='1'/>
</g>`,
rocket: `<g transform='translate(150, 60) scale(3)'>
<path d='M25,0 Q35,20 35,50 L35,70 L15,70 L15,50 Q15,20 25,0' fill='none' stroke='#000' stroke-width='2'/>
<circle cx='25' cy='30' r='8' fill='none' stroke='#000' stroke-width='2'/>
<path d='M15,55 L5,70 L15,65' fill='none' stroke='#000' stroke-width='2'/>
<path d='M35,55 L45,70 L35,65' fill='none' stroke='#000' stroke-width='2'/>
<path d='M18,70 Q25,90 32,70' fill='none' stroke='#000' stroke-width='2'/>
<path d='M20,75 Q25,85 30,75' fill='none' stroke='#000' stroke-width='1.5'/>
</g>`,
sun: `<g transform='translate(125, 80) scale(4)'>
<circle cx='30' cy='30' r='20' fill='none' stroke='#000' stroke-width='2'/>
<line x1='30' y1='0' x2='30' y2='-10' stroke='#000' stroke-width='2'/>
<line x1='30' y1='60' x2='30' y2='70' stroke='#000' stroke-width='2'/>
<line x1='0' y1='30' x2='-10' y2='30' stroke='#000' stroke-width='2'/>
<line x1='60' y1='30' x2='70' y2='30' stroke='#000' stroke-width='2'/>
<line x1='9' y1='9' x2='2' y2='2' stroke='#000' stroke-width='2'/>
<line x1='51' y1='9' x2='58' y2='2' stroke='#000' stroke-width='2'/>
<line x1='9' y1='51' x2='2' y2='58' stroke='#000' stroke-width='2'/>
<line x1='51' y1='51' x2='58' y2='58' stroke='#000' stroke-width='2'/>
<circle cx='22' cy='25' r='3' fill='none' stroke='#000' stroke-width='1'/>
<circle cx='38' cy='25' r='3' fill='none' stroke='#000' stroke-width='1'/>
<path d='M22,38 Q30,45 38,38' fill='none' stroke='#000' stroke-width='1.5'/>
</g>`,
house: `<g transform='translate(100, 100) scale(3)'>
<rect x='15' y='40' width='70' height='55' fill='none' stroke='#000' stroke-width='2'/>
<polygon points='50,5 10,40 90,40' fill='none' stroke='#000' stroke-width='2'/>
<rect x='40' y='65' width='20' height='30' fill='none' stroke='#000' stroke-width='2'/>
<circle cx='55' cy='80' r='2' fill='none' stroke='#000' stroke-width='1'/>
<rect x='22' y='50' width='15' height='15' fill='none' stroke='#000' stroke-width='2'/>
<line x1='29.5' y1='50' x2='29.5' y2='65' stroke='#000' stroke-width='1'/>
<line x1='22' y1='57.5' x2='37' y2='57.5' stroke='#000' stroke-width='1'/>
<rect x='63' y='50' width='15' height='15' fill='none' stroke='#000' stroke-width='2'/>
<line x1='70.5' y1='50' x2='70.5' y2='65' stroke='#000' stroke-width='1'/>
<line x1='63' y1='57.5' x2='78' y2='57.5' stroke='#000' stroke-width='1'/>
</g>`,
unicorn: `<g transform='translate(100, 80) scale(3)'>
<ellipse cx='50' cy='50' rx='35' ry='25' fill='none' stroke='#000' stroke-width='2'/>
<ellipse cx='20' cy='35' rx='15' ry='20' fill='none' stroke='#000' stroke-width='2'/>
<polygon points='20,15 25,0 15,0' fill='none' stroke='#000' stroke-width='2'/>
<path d='M8,25 Q0,20 5,30' fill='none' stroke='#000' stroke-width='2'/>
<circle cx='15' cy='30' r='2' fill='none' stroke='#000' stroke-width='1'/>
<path d='M10,40 Q15,45 20,42' fill='none' stroke='#000' stroke-width='1'/>
<path d='M25,75 L25,95' stroke='#000' stroke-width='3'/>
<path d='M40,75 L40,95' stroke='#000' stroke-width='3'/>
<path d='M60,75 L60,95' stroke='#000' stroke-width='3'/>
<path d='M75,75 L75,95' stroke='#000' stroke-width='3'/>
<path d='M85,50 Q100,45 95,55 Q105,60 90,65' fill='none' stroke='#000' stroke-width='2'/>
<path d='M5,20 Q-5,15 0,25' fill='none' stroke='#000' stroke-width='1.5'/>
<path d='M35,25 Q45,20 38,30' fill='none' stroke='#000' stroke-width='1.5'/>
</g>`
};
const illustrationNames = Object.keys(illustrations);
/**
* Pick an illustration based on theme keywords with randomization
*/
function pickIllustration(theme, llmSuggestion, age) {
// Try LLM suggestion first if valid
if (llmSuggestion && illustrations[llmSuggestion]) {
return llmSuggestion;
}
if (!theme) {
return illustrationNames[Math.floor(Math.random() * illustrationNames.length)];
}
const themeLower = theme.toLowerCase();
// Direct matches
for (const name of illustrationNames) {
if (themeLower.includes(name)) {
return name;
}
}
// Keyword associations
const associations = {
cat: ['kitten', 'kitty', 'meow', 'feline', 'pet', 'animal'],
dog: ['puppy', 'doggy', 'woof', 'canine', 'pet', 'animal'],
butterfly: ['insect', 'bug', 'garden', 'spring', 'animal'],
fish: ['ocean', 'sea', 'water', 'aquarium', 'swim', 'animal'],
flower: ['garden', 'plant', 'nature', 'spring', 'rose'],
star: ['night', 'sky', 'space', 'twinkle', 'magic'],
rocket: ['space', 'astronaut', 'moon', 'planet', 'fly'],
sun: ['summer', 'sunny', 'bright', 'happy', 'day'],
house: ['home', 'building', 'family', 'cozy'],
unicorn: ['magic', 'fairy', 'princess', 'rainbow', 'fantasy', 'horse', 'animal']
};
// Collect ALL matching illustrations
const matchingIllustrations = [];
for (const [illustration, keywords] of Object.entries(associations)) {
if (keywords.some(keyword => themeLower.includes(keyword))) {
matchingIllustrations.push(illustration);
}
}
// If we found matches, pick one randomly for variety
if (matchingIllustrations.length > 0) {
return matchingIllustrations[Math.floor(Math.random() * matchingIllustrations.length)];
}
// Random fallback
return illustrationNames[Math.floor(Math.random() * illustrationNames.length)];
}
/**
* Generate complete coloring page
* @param {Object} params
* @param {string} params.theme - Theme for the coloring page
* @param {string} params.illustration - LLM-suggested illustration (optional)
* @param {number} params.age - Child's age (optional)
*/
export function generateColoringPage({ theme, illustration, age }) {
const selectedIllustration = pickIllustration(theme, illustration, age);
const illustrationSvg = illustrations[selectedIllustration] || illustrations.cat;
const displayName = selectedIllustration.charAt(0).toUpperCase() + selectedIllustration.slice(1);
const svg = `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 500 500'>
<!-- Main illustration -->
${illustrationSvg}
</svg>`;
return {
title: `${displayName} Coloring Page`,
subtitle: theme ? `${theme} themed coloring fun!` : 'Color and have fun!',
sections: [{
id: 'coloring-main',
type: 'coloring',
content_svg: svg
}]
};
}
export default { generateColoringPage };