From 5630c6cee7eafe5c9bda4e4e1a7aaf0c9720bad8 Mon Sep 17 00:00:00 2001 From: francy Date: Sat, 7 Feb 2026 15:36:17 +0100 Subject: [PATCH] feat: Extend worksheet generation age range to 10 years, refine generator parameters, and add PDF watermarks. --- server/gallery.json | 124 ++++++++++++++++---------------- server/generators/coloring.js | 2 +- server/generators/counting.js | 28 +++++++- server/generators/maze.js | 10 ++- server/generators/writing.js | 2 +- server/index.js | 17 ++--- server/prompts.js | 131 ++++++++++++++-------------------- src/App.jsx | 5 +- src/components/Hero.jsx | 13 +++- 9 files changed, 172 insertions(+), 160 deletions(-) diff --git a/server/gallery.json b/server/gallery.json index 16a7d3c1..73e41eef 100644 --- a/server/gallery.json +++ b/server/gallery.json @@ -1,92 +1,92 @@ [ { - "title": "Pirate Parrot's Treasure!", - "subtitle": "Color the parrot and his hidden treasure!", + "title": "Friendly Turtle", + "subtitle": "Color the turtle and the sea!", "sections": [ { "id": "coloring-main", "type": "coloring", - "content_svg": "" + "content_svg": "" } ], - "age": 5, + "age": 10, "activityType": "coloring", - "id": 1770458040593, - "createdAt": "2026-02-07T09:54:00.593Z" + "id": 1770474964556, + "createdAt": "2026-02-07T14:36:04.556Z" }, { - "title": "Parrot's Treasure!", - "subtitle": "Color the parrot and his treasure hunt!", + "title": "Cool Robot!", + "subtitle": "Color the awesome robot with your favorite colors!", "sections": [ { "id": "coloring-main", "type": "coloring", - "content_svg": "" + "content_svg": "" } ], - "age": 5, + "age": 10, "activityType": "coloring", - "id": 1770458028752, - "createdAt": "2026-02-07T09:53:48.752Z" + "id": 1770474951297, + "createdAt": "2026-02-07T14:35:51.297Z" }, { - "title": "Pirate Parrot's Treasure!", - "subtitle": "Color the parrot and his treasure hunt!", + "title": "Trace: PLANETS", + "subtitle": "Practice writing letters!", + "sections": [ + { + "id": "writing-main", + "type": "writing", + "content_svg": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n" + } + ], + "age": 10, + "activityType": "writing", + "id": 1770474941876, + "createdAt": "2026-02-07T14:35:41.876Z" + }, + { + "title": "Space Rocket Fun!", + "subtitle": "Color the awesome rocket ship zooming through space!", "sections": [ { "id": "coloring-main", "type": "coloring", - "content_svg": "" + "content_svg": "" + } + ], + "age": 10, + "activityType": "coloring", + "id": 1770474920894, + "createdAt": "2026-02-07T14:35:20.894Z" + }, + { + "title": "Rocket Adventure!", + "subtitle": "Color the rocket and prepare for blastoff!", + "sections": [ + { + "id": "coloring-main", + "type": "coloring", + "content_svg": "" + } + ], + "age": 10, + "activityType": "coloring", + "id": 1770474873872, + "createdAt": "2026-02-07T14:34:33.872Z" + }, + { + "title": "Rocket Ride!", + "subtitle": "Color the rocket ship!", + "sections": [ + { + "id": "coloring-main", + "type": "coloring", + "content_svg": "" } ], "age": 5, "activityType": "coloring", - "id": 1770457963325, - "createdAt": "2026-02-07T09:52:43.325Z" - }, - { - "title": "Count the Stars!", - "subtitle": "Count each group and write the number", - "sections": [ - { - "id": "counting-main", - "type": "counting", - "content_svg": "\n \n 1.=2.=3.=4.=5.=6.=7.=8.=\n" - } - ], - "age": 5, - "activityType": "counting", - "id": 1770457944132, - "createdAt": "2026-02-07T09:52:24.132Z" - }, - { - "title": "Count the Fishs!", - "subtitle": "Count each group and write the number", - "sections": [ - { - "id": "counting-main", - "type": "counting", - "content_svg": "\n \n 1.=2.=3.=4.=5.=6.=7.=8.=\n" - } - ], - "age": 5, - "activityType": "counting", - "id": 1770457941396, - "createdAt": "2026-02-07T09:52:21.396Z" - }, - { - "title": "Pirate Unicorn Treasure Hunt Maze Adventure", - "subtitle": "Find the path from start to finish!", - "sections": [ - { - "id": "find-the-way-main", - "type": "find-the-way", - "content_svg": "\n \n \n \n \n \n \n \n \n \n \n START\n \n \n \n GOAL\n \n" - } - ], - "age": 5, - "activityType": "find-way", - "id": 1770457938911, - "createdAt": "2026-02-07T09:52:18.911Z" + "id": 1770474863832, + "createdAt": "2026-02-07T14:34:23.832Z" } ] \ No newline at end of file diff --git a/server/generators/coloring.js b/server/generators/coloring.js index f09c0729..5b9a3d12 100644 --- a/server/generators/coloring.js +++ b/server/generators/coloring.js @@ -120,7 +120,7 @@ const illustrations = { - + diff --git a/server/generators/counting.js b/server/generators/counting.js index ab5d7bab..88eee71c 100644 --- a/server/generators/counting.js +++ b/server/generators/counting.js @@ -19,6 +19,19 @@ const themedObjects = { const objectNames = Object.keys(themedObjects); +/** + * Pluralize object names correctly + */ +function pluralize(word) { + const irregulars = { + 'fish': 'Fish', + 'butterfly': 'Butterflies', + 'moon': 'Moons' + }; + if (irregulars[word]) return irregulars[word]; + return word.charAt(0).toUpperCase() + word.slice(1) + 's'; +} + /** * Generate random quantities that are all different */ @@ -151,7 +164,16 @@ export function generateCountingWorksheet({ theme, shape, quantities, age }) { const objectType = pickObjectForTheme(theme, shape); // ALWAYS generate random quantities for variety (LLM is deterministic) - const maxValue = age <= 4 ? 5 : 10; + let maxValue; + if (age <= 4) { + maxValue = 5; + } else if (age <= 6) { + maxValue = 10; + } else if (age <= 8) { + maxValue = 15; + } else { + maxValue = 20; + } const rowCount = 8; const finalQuantities = generateRandomQuantities(rowCount, maxValue); @@ -169,7 +191,7 @@ export function generateCountingWorksheet({ theme, shape, quantities, age }) { // Dotted separator line between rows (except first row) if (i > 0) { - objectRows += ``; + objectRows += ``; } // Row number indicator - vertically centered in row @@ -191,7 +213,7 @@ export function generateCountingWorksheet({ theme, shape, quantities, age }) { `; return { - title: `Count the ${objectType.charAt(0).toUpperCase() + objectType.slice(1)}s!`, + title: `Count the ${pluralize(objectType)}!`, subtitle: 'Count each group and write the number', sections: [{ id: 'counting-main', diff --git a/server/generators/maze.js b/server/generators/maze.js index 1da3ffa7..5abdfa7f 100644 --- a/server/generators/maze.js +++ b/server/generators/maze.js @@ -44,7 +44,7 @@ function generateMazeGrid(rows, cols, startEdge, endEdge) { startRow = 0; startCol = 0; break; case 'bottom': - startRow = rows - 1; startCol = rows - 1; + startRow = rows - 1; startCol = cols - 1; break; case 'left': startRow = 0; startCol = 0; @@ -169,9 +169,15 @@ export function generateMazeWorksheet({ theme, difficulty = 1, age }) { } else if (age <= 5 || difficulty === 2) { rows = 5; cols = 5; - } else { + } else if (age <= 7) { rows = 6; cols = 6; + } else if (age <= 9) { + rows = 7; + cols = 7; + } else { + rows = 8; + cols = 8; } // Randomly select start and end edges - favor opposite edges for better gameplay diff --git a/server/generators/writing.js b/server/generators/writing.js index 36251a5c..a350f844 100644 --- a/server/generators/writing.js +++ b/server/generators/writing.js @@ -96,7 +96,7 @@ function generateCharacterRow(text, y, baseScale = 1, color = '#000000') { */ function generateBaseline(y, width = 400) { const startX = (500 - width) / 2; - return ``; + return ``; } /** diff --git a/server/index.js b/server/index.js index 40314526..361511ca 100644 --- a/server/index.js +++ b/server/index.js @@ -10,8 +10,7 @@ import { GoogleGenerativeAI } from '@google/generative-ai'; import { generateWritingWorksheet, generateCountingWorksheet, - generateMazeWorksheet, - generateColoringPage + generateMazeWorksheet } from './generators/index.js'; dotenv.config(); @@ -75,9 +74,9 @@ function addToGallery(item) { const parameterPrompts = { writing: (theme, age) => { const randomSeed = Math.floor(Math.random() * 1000); - const wordLength = `${age} letters`; - const numberRange = age <= 4 ? '1-5' : '1-9'; - const numberCount = age <= 4 ? '3-4' : '4-5'; + const wordLength = age <= 4 ? '3-4 letters' : age <= 6 ? '4-5 letters' : age <= 8 ? '5-6 letters' : '6-8 letters'; + const numberRange = age <= 4 ? '1-5' : age <= 6 ? '1-9' : age <= 8 ? '1-15' : '1-20'; + const numberCount = age <= 4 ? '3-4' : age <= 6 ? '4-5' : '5-6'; return ` Create a children's tracing worksheet for theme "${theme}". @@ -109,7 +108,7 @@ CRITICAL: You MUST return JSON with BOTH "shape" AND "quantities": {"shape": "star", "quantities": [3, 1, 5, 2, 7, 4, 8, 6]} - "shape": Choose ONE shape that matches "${theme}". Use the random seed ${randomSeed} to vary your choice - don't always pick the same shape for this theme. -- "quantities": Array of exactly 8 different numbers (1-${age <= 4 ? 5 : 10}) +- "quantities": Array of exactly 8 different numbers (1-${age <= 4 ? 5 : age <= 6 ? 10 : age <= 8 ? 15 : 20}) Return ONLY valid JSON. No explanation. `; @@ -126,7 +125,9 @@ Return ONLY a JSON object: Rules: - difficulty 1 = easy (4x4 grid) for age 3-4 - difficulty 2 = medium (5x5 grid) for age 5-6 -- difficulty 3 = hard (6x6 grid) for age 7+ +- difficulty 3 = hard (6x6 grid) for age 7-8 +- difficulty 4 = challenging (7x7 grid) for age 9 +- difficulty 5 = expert (8x8 grid) for age 10 Return ONLY valid JSON, no explanation. `, @@ -183,7 +184,7 @@ app.post('/api/surprise', async (req, res) => { Current timestamp: ${timestamp} Generate ONE creative, fun, specific theme for a children's worksheet (e.g. "Space-Rex Coding", "Underwater Hamster Tea Party"). - Pick a RANDOM age between 3 and 7. + Pick a RANDOM age between 3 and 10. Pick a RANDOM activity type with EQUAL PROBABILITY from these 4 options: 1. "coloring" - coloring pages diff --git a/server/prompts.js b/server/prompts.js index 4c81d146..42a0c169 100644 --- a/server/prompts.js +++ b/server/prompts.js @@ -50,96 +50,69 @@ Age: ${age} years old 🎯 YOUR GOAL: Create a coloring page with ONE clear subject that a 3-year-old can immediately identify as "${theme}". -🚨 ABSOLUTE SVG REQUIREMENTS (CRITICAL - NON-NEGOTIABLE): -- EVERY single path MUST have fill="none" -- EVERY single path MUST have stroke="black" or stroke="#000000" -- Use stroke-width="2" or "3" for all outlines -- NO filled shapes allowed - children need empty white areas to color -- Example: +🚨 ABSOLUTE QUALITY RULES (CRITICAL - NON-NEGOTIABLE): +1. USE WHITE FILLS: Every closed shape (head, body, legs, eyes) MUST have fill="#FFFFFF". This hides lines behind it. +2. DRAWING ORDER: Draw from BACK to FRONT. Background parts first (tail, back legs), then main body, then front details (eyes, nose). +3. CLOSED PATHS: All shapes must be fully closed paths so they can be filled with white. +4. NO OVERLAPPING MESS: White fills solve this. If you draw a leg over a body, the white fill of the leg must hide the body line behind it. -📐 STEP-BY-STEP SVG CONSTRUCTION METHOD: +🚨 SVG REQUIREMENTS (CRITICAL): +- Background: (Start with white canvas) +- Stroke: stroke="black" or stroke="#000000" +- Stroke Width: stroke-width="2" for main outlines, "2" for details +- Fill: fill="#FFFFFF" for all object parts (to block lines behind) +- Fill: fill="none" ONLY for open paths like whiskers or smiles +- Example Closed Shape: +- Example Open Line: -For ANIMALS (cat, dog, panda, unicorn, etc.): -1. HEAD: Draw a large circle or oval for head (100-150px diameter), centered around x=250, y=200 -2. EARS: Add 2 rounded ears on top of head (circles, triangles, or ovals) -3. EYES: 2 circles (30-40px diameter) positioned symmetrically -4. NOSE: Small circle or triangle in center below eyes -5. MOUTH: Simple curved line (smile) below nose -6. BODY: Large oval or rounded rectangle below head (150-200px wide, 180-220px tall) -7. LEGS: 4 rounded rectangles or ovals extending down from body -8. TAIL: Curved path on one side -9. DETAILS: Add characteristic features (whiskers, spots, horn, etc.) +📐 STEP-BY-STEP CONSTRUCTION METHOD (Layer by Layer): -For OBJECTS (car, rocket, house, etc.): -1. MAIN BODY: Start with basic shape (rectangle for car/house, elongated triangle for rocket) -2. MAJOR COMPONENTS: Add 2-4 large recognizable parts (wheels, windows, door, roof) -3. DETAILS: Add smaller features that make it identifiable +For ANIMALS (Draw in this order): +1. BACK LEGS/TAIL (Furthest back): Draw simple shapes, fill="#FFFFFF", stroke="black" +2. BODY (Middle): Large oval/shape, fill="#FFFFFF", stroke="black" (Hides the top of back legs) +3. HEAD (Front): Large circle/shape, fill="#FFFFFF", stroke="black" (Hides neck/body connection) +4. FRONT LEGS (Front): Shapes overlapping body, fill="#FFFFFF", stroke="black" +5. FACE DETAILS (Topmost): Eyes, nose, mouth. Eyes can have black pupils. -For PLANTS (flower, tree, etc.): -1. CENTER/TRUNK: Main central element -2. PETALS/LEAVES: 5-8 symmetrical rounded shapes around center -3. STEM: Vertical line or path -4. BASE: Ground line or pot +For OBJECTS (Car, Rocket, etc): +1. MAIN CHASSIS/BODY: Large filled shape (fill="#FFFFFF") +2. WHEELS/WINGS: Draw on top or behind as needed +3. WINDOWS/DOORS: Draw on top of body (fill="#FFFFFF") +4. DETAILS: Lines, handles, bolts -🎨 SPECIFIC THEME EXAMPLES: +For PLANTS: +1. STEM/BRANCHES: Main lines +2. LEAVES BEHIND: Fill="#FFFFFF" +3. FLOWER CENTER: Fill="#FFFFFF" (Draw last so it's on top) +4. PETALS: Fill="#FFFFFF" -UNICORN: -- Circle head (cx="250" cy="180", r="70") -- 2 triangle ears -- HORN: Triangle pointing up from forehead (30-40px tall) -- 2 circle eyes -- Small nose circle -- Curved smile -- Oval body below head -- 4 rounded rectangle legs -- Flowing curved tail on right side -- Mane: 3-4 curved strokes on neck +🎨 SPECIFIC THEME GUIDES: -PANDA: -- Large circle head (r="80") -- 2 small circle ears on top -- 2 LARGE circle eye patches (r="25", OUTLINED not filled) -- 2 smaller circle eyes inside patches -- Small circle nose -- Smile below nose -- Large oval body -- 4 thick rounded legs -- Optional: bamboo stick to the side (2 parallel lines) +UNICORN: +- Body: Oval (fill="#FFFFFF") +- Legs: 4 rectangles (fill="#FFFFFF") - make sure they attach naturally +- Head: Oval/Horse shape (fill="#FFFFFF") +- START with separate shapes, but ensure they LOOK connected +- Horn: Triangle on forehead +- Mane/Tail: Flowing shapes -CAT: -- Circle head (r="60") -- 2 triangle ears pointing up -- 2 circle eyes -- Small triangle nose -- Whiskers (3 lines each side) -- Curved smile -- Oval body -- 4 legs -- Curved S-shaped tail +ROBOT: +- Use geometric shapes (rectangles, circles) +- Fill ALL shapes with #FFFFFF to look solid +- Add buttons/panels on top -FLOWER: -- Center circle (r="40") -- 6-8 rounded petal paths around center (use ellipses or bezier curves) -- Straight stem line down from center -- 2 leaf shapes on stem (ellipses at angle) -- Simple ground line - -🔍 QUALITY CHECKLIST - YOUR SVG MUST: -✓ Have 8-15 large closed shapes ready for coloring -✓ Be IMMEDIATELY recognizable as "${theme}" -✓ Use ONLY stroke outlines (fill="none" on ALL paths) -✓ Have bold stroke-width="2" or "3" -✓ Be 500x500 viewBox with subject centered -✓ Include characteristic features (horn for unicorn, eye patches for panda, etc.) -✓ Have simple, rounded, child-friendly shapes -✓ Look like it came from a store-bought coloring book +🔍 QUALITY CHECKLIST: +✓ Is the main subject clearly visible? +✓ Are lines hidden where shapes overlap? (e.g. body line not visible through arm) +✓ Is everything filled with white (except open lines)? +✓ Is the stroke width consistent (bold 3px)? +✓ Is the image centered in 500x500? ❌ AVOID: -- Abstract or geometric art -- Filled/colored areas -- Tiny details (too hard for kids) -- Complex overlapping shapes -- Unrecognizable forms +- fill="none" on main shapes (causes messy overlaps) +- Translucent/opacity effects +- Tiny dust/specks +- Disconnected lines that should be closed ${baseRules} ${ageRules(age)} diff --git a/src/App.jsx b/src/App.jsx index 2ebe4765..79ea669a 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -110,7 +110,10 @@ function App() { {/* Footer */}
-

© 2024 WonderSheets. All rights reserved.

+

+ wondersheets.art +

+

© {new Date().getFullYear()} WonderSheets. All rights reserved.

diff --git a/src/components/Hero.jsx b/src/components/Hero.jsx index c5aa1b31..a6c00235 100644 --- a/src/components/Hero.jsx +++ b/src/components/Hero.jsx @@ -135,6 +135,13 @@ export const Hero = ({ onGenerate, generatedSheet, isGenerating }) => { const pdfHeight = (imgProperties.height * pdfWidth) / imgProperties.width; pdf.addImage(data, 'PNG', 0, 0, pdfWidth, pdfHeight); + + // Add watermark at bottom center + const pageHeight = pdf.internal.pageSize.getHeight(); + pdf.setFontSize(10); + pdf.setTextColor(150, 150, 150); // Light gray + pdf.text('wondersheets.art', pdfWidth / 2, pageHeight - 5, { align: 'center' }); + return pdf; }; @@ -190,19 +197,19 @@ export const Hero = ({ onGenerate, generatedSheet, isGenerating }) => {
- 3 to 7 years + 3 to 10 years
setAge(parseInt(e.target.value))} className="w-full h-3 bg-slate-100 rounded-lg appearance-none cursor-pointer accent-primary-600 disabled:opacity-50" />
- 34567 + 345678910