feat(kids): Add new interactive elements for kids levels

This commit is contained in:
Fatih Kadir Akın
2026-01-13 00:33:34 +03:00
parent 5098bb8352
commit 1b710df7dc
129 changed files with 8967 additions and 726 deletions

View File

@@ -0,0 +1,201 @@
# New Interactive Elements for Kids Levels
Add 5 new interactive components to enhance kids learning, inspired by key concepts from the prompting book.
---
## Current State
### Existing Components
| Component | Purpose | Used In |
|-----------|---------|---------|
| `PromptVsMistake` | Choose between good/bad prompts | All worlds |
| `MagicWords` | Fill-in-the-blank prompts | All worlds |
| `DragDropPrompt` | Arrange prompt pieces in order | World 2-5 |
| `Panel/StoryScene` | Story dialogue with Promi | All worlds |
| `LevelComplete` | Celebration with stars | All worlds |
### Book Concepts Not Yet Covered for Kids
1. **Few-shot learning** - Teaching by example
2. **Iterative refinement** - Improving prompts step-by-step
3. **Prompt anatomy** - Understanding prompt parts (role, context, task, format)
4. **Common pitfalls** - Recognizing and avoiding mistakes
5. **Chain of thought** - Step-by-step reasoning
---
## Proposed New Components
### 1. `<PromptLab />` - Interactive Prompt Tester
**Concept**: Kids build a prompt and see a simulated AI response (pre-defined).
**Props**:
```tsx
<PromptLab
scenario="Ask about a pet"
basePrompt="Tell me about dogs"
improvements={[
{ add: "that are good with kids", effect: "Now mentions family-friendly breeds!" },
{ add: "in 3 sentences", effect: "Response is shorter and clearer!" }
]}
finalResponse="Golden Retrievers are great with kids because..."
/>
```
**UX**:
- Shows base prompt with "add detail" buttons
- Each addition shows immediate simulated response change
- Teaches iterative refinement concept
**Book Reference**: Chapter 8 (Iterative Refinement)
---
### 2. `<PromptParts />` - Anatomy Highlighter
**Concept**: Interactive visualization of prompt components.
**Props**:
```tsx
<PromptParts
prompt="You are a friendly teacher. Explain fractions to a 10-year-old using pizza examples. Keep it under 50 words."
parts={[
{ text: "You are a friendly teacher", type: "role", color: "purple" },
{ text: "Explain fractions to a 10-year-old", type: "task", color: "blue" },
{ text: "using pizza examples", type: "example", color: "green" },
{ text: "Keep it under 50 words", type: "constraint", color: "orange" }
]}
/>
```
**UX**:
- Colored highlights on prompt parts
- Tap a part to see explanation
- Legend shows what each color means
**Book Reference**: Chapter 2 (Anatomy of a Prompt)
---
### 3. `<ExampleMatcher />` - Few-Shot Learning Game
**Concept**: Match examples to teach AI patterns.
**Props**:
```tsx
<ExampleMatcher
title="Teach the Pattern!"
examples={[
{ input: "happy", output: "😊" },
{ input: "sad", output: "😢" },
{ input: "angry", output: "???" }
]}
correctAnswer="😠"
options={["😠", "😊", "🎉", "😴"]}
explanation="The AI learns: words → matching emoji!"
/>
```
**UX**:
- Shows pattern with examples
- Kids choose what comes next
- Teaches few-shot learning concept
**Book Reference**: Chapter 7 (Few-Shot Learning)
---
### 4. `<PromptDoctor />` - Fix the Prompt
**Concept**: Identify and fix problems in broken prompts.
**Props**:
```tsx
<PromptDoctor
brokenPrompt="Write something"
problems={[
{ issue: "Too vague", fix: "Write a poem" },
{ issue: "No topic", fix: "Write a poem about friendship" },
{ issue: "No length", fix: "Write a short poem about friendship" }
]}
healedPrompt="Write a short poem about friendship"
/>
```
**UX**:
- Shows "sick" prompt with symptoms
- Kids tap problems to apply fixes
- Prompt "heals" as problems are fixed
- Fun medical/doctor theme
**Book Reference**: Chapter 15 (Common Pitfalls)
---
### 5. `<StepByStep />` - Chain of Thought Builder
**Concept**: Teach kids to ask AI to show its work.
**Props**:
```tsx
<StepByStep
problem="How many legs do 3 dogs and 2 cats have?"
wrongAnswer="20 legs (AI just guessed!)"
steps={[
"Dogs have 4 legs each",
"3 dogs × 4 legs = 12 legs",
"Cats have 4 legs each",
"2 cats × 4 legs = 8 legs",
"12 + 8 = 20 legs total"
]}
rightAnswer="20 legs (and we can check the work!)"
magicWords="Let's think step by step"
/>
```
**UX**:
- Shows problem with wrong answer first
- Kids add "magic words" to unlock step-by-step
- Steps reveal one at a time
- Teaches chain of thought prompting
**Book Reference**: Chapter 6 (Chain of Thought)
---
## Implementation Priority
| Priority | Component | Complexity | Impact |
|----------|-----------|------------|--------|
| 1 | `PromptParts` | Medium | High - visual learning |
| 2 | `ExampleMatcher` | Low | High - gamification |
| 3 | `PromptDoctor` | Medium | High - error recognition |
| 4 | `StepByStep` | Low | Medium - advanced concept |
| 5 | `PromptLab` | High | Medium - complex interactions |
---
## Level Integration
### Where to Add Components
| World | Level | New Component | Concept |
|-------|-------|---------------|---------|
| 2 | 2-4 Detail Detective | `PromptParts` | See prompt anatomy |
| 3 | 3-2 Show Don't Tell | `ExampleMatcher` | Teach by example |
| 5 | 5-2 Fix It Up | `PromptDoctor` | Fix broken prompts |
| 5 | 5-1 Perfect Prompt | `StepByStep` | Complex reasoning |
| 5 | 5-3 Prompt Remix | `PromptLab` | Iterate and improve |
---
## Estimated Work
- **Component Development**: ~2-3 hours per component
- **Level Content Updates**: ~30 min per level
- **i18n**: Add translation keys for new component labels
- **Testing**: Visual review on mobile/desktop
---
## Questions for User
1. **Priority**: Should I implement all 5, or start with top 2-3?
2. **Theming**: Any specific visual style preferences (medical theme for PromptDoctor, etc.)?
3. **Complexity**: Is `PromptLab` too complex for MVP, or should it be simpler?

View File

@@ -1,7 +1,7 @@
import type { MDXComponents } from "mdx/types";
import type { ComponentPropsWithoutRef } from "react";
import { BeforeAfterEditor, BookPartsNav, BREAKFramework, Callout, ChainErrorDemo, ChainExample, ChainFlowDemo, Checklist, CodeEditor, Collapsible, Compare, ContentPipelineDemo, ContextPlayground, ContextWindowDemo, CostCalculatorDemo, CRISPEFramework, DiffView, EmbeddingsDemo, FallbackDemo, FewShotDemo, FillInTheBlank, IconCheck, IconClipboard, IconLightbulb, IconLock, IconSettings, IconStar, IconTarget, IconUser, IconX, InfoGrid, InteractiveChecklist, IterativeRefinementDemo, JailbreakDemo, JsonYamlDemo, LLMCapabilitiesDemo, NavButton, NavFooter, PrinciplesSummary, PromptAnalyzer, PromptBreakdown, PromptBuilder, PromptChallenge, PromptDebugger, Quiz, RTFFramework, SpecificitySpectrum, StructuredOutputDemo, SummarizationDemo, TemperatureDemo, TextToImageDemo, TextToVideoDemo, TokenizerDemo, TokenPredictionDemo, TryIt, ValidationDemo, VersionDiff } from "@/components/book/interactive";
import { PromiCharacter, PromiWithMessage, Panel, StoryScene, PromptVsMistake, MagicWords, DragDropPrompt, LevelComplete } from "@/components/kids/elements";
import { PromiCharacter, PromiWithMessage, Panel, StoryScene, PromptVsMistake, MagicWords, DragDropPrompt, LevelComplete, Section, PromptParts, ExampleMatcher, PromptDoctor, StepByStep, PromptLab, WordPredictor } from "@/components/kids/elements";
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
@@ -92,5 +92,12 @@ export function useMDXComponents(components: MDXComponents): MDXComponents {
MagicWords,
DragDropPrompt,
LevelComplete,
Section,
PromptParts,
ExampleMatcher,
PromptDoctor,
StepByStep,
PromptLab,
WordPredictor,
};
}

View File

@@ -1399,7 +1399,14 @@
"stars": { "title": "اجمع النجوم", "description": "اجمع النجوم وافتح مستويات جديدة" }
},
"startButton": "ابدأ اللعب!",
"ageNote": "الأفضل للأطفال من 8-14 سنة الذين يعرفون القراءة والكتابة"
"ageNote": "الأفضل للأطفال من 8-14 سنة الذين يعرفون القراءة والكتابة",
"whatYouLearn": "ماذا ستتعلم",
"readyTitle": "مستعد للبدء؟",
"readyMessage": "لننطلق في مغامرة ونتعلم كيف نتحدث مع الذكاء الاصطناعي!"
},
"navigation": {
"back": "رجوع",
"next": "التالي"
},
"map": { "title": "خريطة العالم", "subtitle": "اختر مستوى وابدأ مغامرتك!", "worldLevels": "{count} مستويات", "levelNumber": "المستوى {number}", "locked": "أكمل المستوى السابق للفتح" },
"worlds": { "1": { "title": "قرية البداية" }, "2": { "title": "قلعة الوضوح" }, "3": { "title": "كهوف السياق" }, "4": { "title": "وادي الإبداع" }, "5": { "title": "جبل الإتقان" } },

View File

@@ -1399,7 +1399,14 @@
"stars": { "title": "Ulduz Qazan", "description": "Ulduzlar topla və yeni səviyyələri aç" }
},
"startButton": "Oynamağa Başla!",
"ageNote": "Oxumaq və yazmaq bilən 8-14 yaşlı uşaqlar üçün ən uyğun"
"ageNote": "Oxumaq və yazmaq bilən 8-14 yaşlı uşaqlar üçün ən uyğun",
"whatYouLearn": "Nə öyrənəcəksən",
"readyTitle": "Başlamağa hazırsan?",
"readyMessage": "Gəl macəraya çıxaq və AI ilə danışmağı öyrənək!"
},
"navigation": {
"back": "Geri",
"next": "İrəli"
},
"map": { "title": "Dünya Xəritəsi", "subtitle": "Bir səviyyə seç və macərana başla!", "worldLevels": "{count} səviyyə", "levelNumber": "Səviyyə {number}", "locked": "Açmaq üçün əvvəlki səviyyəni tamamla" },
"worlds": { "1": { "title": "Başlanğıc Kəndi" }, "2": { "title": "Aydınlıq Qalası" }, "3": { "title": "Kontekst Mağaraları" }, "4": { "title": "Yaradıcılıq Kanyonu" }, "5": { "title": "Ustad Dağı" } },

View File

@@ -1399,7 +1399,14 @@
"stars": { "title": "Sterne sammeln", "description": "Sammle Sterne und schalte neue Level frei" }
},
"startButton": "Jetzt spielen!",
"ageNote": "Am besten für Kinder von 8-14 Jahren, die lesen und schreiben können"
"ageNote": "Am besten für Kinder von 8-14 Jahren, die lesen und schreiben können",
"whatYouLearn": "Was du lernst",
"readyTitle": "Bereit zum Starten?",
"readyMessage": "Lass uns auf ein Abenteuer gehen und lernen, mit KI zu sprechen!"
},
"navigation": {
"back": "Zurück",
"next": "Weiter"
},
"map": { "title": "Weltkarte", "subtitle": "Wähle ein Level und starte dein Abenteuer!", "worldLevels": "{count} Level", "levelNumber": "Level {number}", "locked": "Schließe das vorherige Level ab zum Freischalten" },
"worlds": { "1": { "title": "Startdorf" }, "2": { "title": "Klarheitsschloss" }, "3": { "title": "Kontext-Höhlen" }, "4": { "title": "Kreativ-Canyon" }, "5": { "title": "Meisterberg" } },

View File

@@ -1399,7 +1399,14 @@
"stars": { "title": "Κέρδισε Αστέρια", "description": "Συλλέξτε αστέρια και ξεκλειδώστε νέα επίπεδα" }
},
"startButton": "Ξεκίνα να Παίζεις!",
"ageNote": "Ιδανικό για παιδιά 8-14 ετών που ξέρουν να διαβάζουν και να γράφουν"
"ageNote": "Ιδανικό για παιδιά 8-14 ετών που ξέρουν να διαβάζουν και να γράφουν",
"whatYouLearn": "Τι θα μάθεις",
"readyTitle": "Έτοιμος να ξεκινήσεις;",
"readyMessage": "Πάμε σε μια περιπέτεια και μάθουμε να μιλάμε με την AI!"
},
"navigation": {
"back": "Πίσω",
"next": "Επόμενο"
},
"map": { "title": "Χάρτης Κόσμου", "subtitle": "Επέλεξε ένα επίπεδο και ξεκίνα την περιπέτειά σου!", "worldLevels": "{count} επίπεδα", "levelNumber": "Επίπεδο {number}", "locked": "Ολοκλήρωσε το προηγούμενο επίπεδο για ξεκλείδωμα" },
"worlds": { "1": { "title": "Χωριό Εκκίνησης" }, "2": { "title": "Κάστρο Σαφήνειας" }, "3": { "title": "Σπηλιές Πλαισίου" }, "4": { "title": "Φαράγγι Δημιουργικότητας" }, "5": { "title": "Βουνό Μαεστρίας" } },

View File

@@ -1417,7 +1417,14 @@
}
},
"startButton": "Start Playing!",
"ageNote": "Best for kids ages 8-14 who can read and write"
"ageNote": "Best for kids ages 8-14 who can read and write",
"whatYouLearn": "What You'll Learn",
"readyTitle": "Ready to Start?",
"readyMessage": "Let's go on an adventure and learn how to talk to AI!"
},
"navigation": {
"back": "Back",
"next": "Next"
},
"map": {
"title": "World Map",
@@ -1455,6 +1462,70 @@
"1_3_being_clear": {
"title": "Being Clear",
"description": "Learn why clear instructions work better"
},
"2_1_missing_details": {
"title": "The Missing Details",
"description": "Discover why details matter - vague vs specific prompts"
},
"2_2_who_and_what": {
"title": "Who & What",
"description": "Add characters and objects to make prompts come alive"
},
"2_3_when_and_where": {
"title": "When & Where",
"description": "Learn to add time and place to your prompts"
},
"2_4_detail_detective": {
"title": "The Detail Detective",
"description": "Become a master of adding all the right details"
},
"3_1_setting_the_scene": {
"title": "Setting the Scene",
"description": "Learn why background info helps AI understand you"
},
"3_2_show_dont_tell": {
"title": "Show, Don't Tell",
"description": "Use examples to show AI exactly what you want"
},
"3_3_format_finder": {
"title": "The Format Finder",
"description": "Ask for lists, stories, poems, and more!"
},
"3_4_context_champion": {
"title": "Context Champion",
"description": "Combine all context techniques like a pro"
},
"4_1_pretend_time": {
"title": "Pretend Time!",
"description": "Learn role-play prompts - 'Act as...'"
},
"4_2_story_starters": {
"title": "Story Starters",
"description": "Create amazing stories with AI as your co-author"
},
"4_3_character_creator": {
"title": "Character Creator",
"description": "Give AI a personality and watch it come alive"
},
"4_4_world_builder": {
"title": "World Builder",
"description": "Create imaginative worlds and scenarios"
},
"5_1_perfect_prompt": {
"title": "The Perfect Prompt",
"description": "Combine clarity, details, and context together"
},
"5_2_fix_it_up": {
"title": "Fix It Up!",
"description": "Find and improve weak prompts"
},
"5_3_prompt_remix": {
"title": "Prompt Remix",
"description": "Rewrite prompts for different outcomes"
},
"5_4_graduation_day": {
"title": "Graduation Day",
"description": "The final challenge - become a Prompt Master!"
}
},
"level": {
@@ -1470,6 +1541,105 @@
"nextLevel": "Next Level",
"backToMap": "Back to Map",
"allDone": "Back to Map"
},
"quiz": {
"goodLabel": "Great prompt!",
"badLabel": "Not the best",
"correct": "You got it!",
"incorrect": "Good try!",
"tryAgain": "Try Again"
},
"magicWords": {
"title": "Drag the magic words! ✨",
"dragOrTap": "🎯 Drag or tap words:",
"check": "Check!",
"retry": "Retry",
"correct": "correct",
"tryAgain": "Try again!"
},
"dragDrop": {
"title": "Build the prompt!",
"instruction": "Use arrows to move pieces, or tap two pieces to swap!",
"result": "Result",
"check": "Check!",
"retry": "Retry",
"success": "Perfect! You built a great prompt!",
"almost": "Almost! Keep reordering.",
"tapToSwap": "Tap another piece to swap positions!"
},
"promptParts": {
"title": "Sort the Prompt Parts!",
"instruction": "Tap each piece, then pick which type it is!",
"score": "Score",
"pickCategory": "What type is this?",
"success": "You sorted all the parts correctly!",
"retry": "Try Again",
"types": {
"role": "Role",
"task": "Task",
"context": "Context",
"constraint": "Constraint"
}
},
"exampleMatcher": {
"title": "Pattern Matcher",
"instruction": "Look at the pattern and pick what comes next!",
"pattern": "The Pattern:",
"check": "Check!",
"retry": "Try Again",
"correct": "You got it! 🎉",
"tryAgain": "Not quite - look at the pattern again!"
},
"promptDoctor": {
"title": "Prompt Doctor",
"health": "Prompt Health",
"sick": "Sick Prompt",
"healthy": "Healthy Prompt!",
"diagnose": "Click a problem to fix it:",
"success": "The prompt is all better now!",
"retry": "Start Over"
},
"stepByStep": {
"title": "Think Step by Step",
"problem": "The Problem:",
"withoutMagic": "Without magic words:",
"addMagicWords": "Add the Magic Words!",
"magicWordsActive": "Magic words added!",
"nextStep": "Reveal Next Step",
"withMagic": "With step-by-step thinking:",
"retry": "Try Again"
},
"promptLab": {
"title": "Prompt Lab",
"progress": "Improvements",
"yourPrompt": "Your Prompt:",
"aiSays": "AI Response:",
"addDetails": "Add improvements:",
"success": "Your prompt is now super specific!",
"retry": "Start Over"
},
"wordPredictor": {
"title": "How AI Thinks",
"instruction": "AI guesses the most likely next word. Can you think like AI?",
"aiThinks": "AI is reading:",
"thinkingDefault": "Hmm, what word would make the most sense here?",
"check": "Check My Guess!",
"correct": "You think like AI!",
"tryAgain": "Not quite! AI picks the most likely word.",
"retry": "Try Again"
},
"settings": {
"title": "Settings",
"language": "Language",
"progress": "Your Progress",
"stars": "Stars",
"completed": "Completed",
"resetTitle": "Reset Progress",
"resetButton": "Reset All Progress",
"resetWarning": "This will delete all your stars and progress. Are you sure?",
"resetConfirm": "Yes, Reset Everything",
"resetComplete": "Progress reset! Reloading...",
"cancel": "Cancel"
}
}
}

View File

@@ -1399,7 +1399,14 @@
"stars": { "title": "Gana Estrellas", "description": "Colecciona estrellas y desbloquea nuevos niveles" }
},
"startButton": "¡Empezar a Jugar!",
"ageNote": "Ideal para niños de 8-14 años que saben leer y escribir"
"ageNote": "Ideal para niños de 8-14 años que saben leer y escribir",
"whatYouLearn": "Lo Que Aprenderás",
"readyTitle": "¿Listo para Empezar?",
"readyMessage": "¡Vamos a una aventura y aprendamos a hablar con la IA!"
},
"navigation": {
"back": "Atrás",
"next": "Siguiente"
},
"map": { "title": "Mapa del Mundo", "subtitle": "¡Elige un nivel y comienza tu aventura!", "worldLevels": "{count} niveles", "levelNumber": "Nivel {number}", "locked": "Completa el nivel anterior para desbloquear" },
"worlds": { "1": { "title": "Aldea Inicial" }, "2": { "title": "Castillo de Claridad" }, "3": { "title": "Cuevas de Contexto" }, "4": { "title": "Cañón Creativo" }, "5": { "title": "Montaña Maestra" } },

View File

@@ -1399,7 +1399,14 @@
"stars": { "title": "ستاره جمع کن", "description": "ستاره جمع کن و مراحل جدید را باز کن" }
},
"startButton": "شروع بازی!",
"ageNote": "مناسب برای کودکان ۸-۱۴ ساله که می‌توانند بخوانند و بنویسند"
"ageNote": "مناسب برای کودکان ۸-۱۴ ساله که می‌توانند بخوانند و بنویسند",
"whatYouLearn": "چه چیزی یاد می‌گیری",
"readyTitle": "آماده شروع هستی؟",
"readyMessage": "بیا به ماجراجویی برویم و یاد بگیریم چطور با هوش مصنوعی صحبت کنیم!"
},
"navigation": {
"back": "برگشت",
"next": "بعدی"
},
"map": { "title": "نقشه جهان", "subtitle": "یک مرحله انتخاب کن و ماجراجویی‌ات را شروع کن!", "worldLevels": "{count} مرحله", "levelNumber": "مرحله {number}", "locked": "برای باز کردن، مرحله قبلی را کامل کن" },
"worlds": { "1": { "title": "دهکده شروع" }, "2": { "title": "قلعه شفافیت" }, "3": { "title": "غارهای زمینه" }, "4": { "title": "دره خلاقیت" }, "5": { "title": "کوه استادی" } },

View File

@@ -1399,7 +1399,14 @@
"stars": { "title": "Gagne des Étoiles", "description": "Collectionne des étoiles et débloque de nouveaux niveaux" }
},
"startButton": "Commencer à Jouer !",
"ageNote": "Idéal pour les enfants de 8-14 ans qui savent lire et écrire"
"ageNote": "Idéal pour les enfants de 8-14 ans qui savent lire et écrire",
"whatYouLearn": "Ce que tu vas apprendre",
"readyTitle": "Prêt à commencer ?",
"readyMessage": "Partons à l'aventure et apprenons à parler avec l'IA !"
},
"navigation": {
"back": "Retour",
"next": "Suivant"
},
"map": { "title": "Carte du Monde", "subtitle": "Choisis un niveau et commence ton aventure !", "worldLevels": "{count} niveaux", "levelNumber": "Niveau {number}", "locked": "Termine le niveau précédent pour débloquer" },
"worlds": { "1": { "title": "Village de Départ" }, "2": { "title": "Château de la Clarté" }, "3": { "title": "Grottes du Contexte" }, "4": { "title": "Canyon Créatif" }, "5": { "title": "Montagne du Maître" } },

View File

@@ -1399,7 +1399,14 @@
"stars": { "title": "אסוף כוכבים", "description": "אסוף כוכבים ופתח שלבים חדשים" }
},
"startButton": "התחל לשחק!",
"ageNote": "מתאים לילדים בגילאי 8-14 שיודעים לקרוא ולכתוב"
"ageNote": "מתאים לילדים בגילאי 8-14 שיודעים לקרוא ולכתוב",
"whatYouLearn": "מה תלמד",
"readyTitle": "מוכן להתחיל?",
"readyMessage": "בוא נצא להרפתקה ונלמד איך לדבר עם AI!"
},
"navigation": {
"back": "אחורה",
"next": "הבא"
},
"map": { "title": "מפת העולם", "subtitle": "בחר שלב והתחל את ההרפתקה שלך!", "worldLevels": "{count} שלבים", "levelNumber": "שלב {number}", "locked": "השלם את השלב הקודם לפתיחה" },
"worlds": { "1": { "title": "כפר ההתחלה" }, "2": { "title": "טירת הבהירות" }, "3": { "title": "מערות ההקשר" }, "4": { "title": "קניון היצירתיות" }, "5": { "title": "הר המומחיות" } },

View File

@@ -1399,7 +1399,14 @@
"stars": { "title": "Guadagna Stelle", "description": "Raccogli stelle e sblocca nuovi livelli" }
},
"startButton": "Inizia a Giocare!",
"ageNote": "Ideale per bambini di 8-14 anni che sanno leggere e scrivere"
"ageNote": "Ideale per bambini di 8-14 anni che sanno leggere e scrivere",
"whatYouLearn": "Cosa imparerai",
"readyTitle": "Pronto per iniziare?",
"readyMessage": "Andiamo all'avventura e impariamo a parlare con l'IA!"
},
"navigation": {
"back": "Indietro",
"next": "Avanti"
},
"map": { "title": "Mappa del Mondo", "subtitle": "Scegli un livello e inizia la tua avventura!", "worldLevels": "{count} livelli", "levelNumber": "Livello {number}", "locked": "Completa il livello precedente per sbloccare" },
"worlds": { "1": { "title": "Villaggio Iniziale" }, "2": { "title": "Castello della Chiarezza" }, "3": { "title": "Caverne del Contesto" }, "4": { "title": "Canyon Creativo" }, "5": { "title": "Montagna del Maestro" } },

View File

@@ -1399,7 +1399,14 @@
"stars": { "title": "スターを集めよう", "description": "スターを集めて新しいレベルをアンロック" }
},
"startButton": "遊び始めよう!",
"ageNote": "読み書きができる8〜14歳のお子様に最適"
"ageNote": "読み書きができる8〜14歳のお子様に最適",
"whatYouLearn": "学べること",
"readyTitle": "始める準備はできた?",
"readyMessage": "冒険に出かけて、AIと話す方法を学ぼう"
},
"navigation": {
"back": "戻る",
"next": "次へ"
},
"map": { "title": "ワールドマップ", "subtitle": "レベルを選んで冒険を始めよう!", "worldLevels": "{count}レベル", "levelNumber": "レベル{number}", "locked": "前のレベルをクリアしてアンロック" },
"worlds": { "1": { "title": "はじまりの村" }, "2": { "title": "明確城" }, "3": { "title": "コンテキスト洞窟" }, "4": { "title": "クリエイティブ渓谷" }, "5": { "title": "マスター山" } },

View File

@@ -1399,7 +1399,14 @@
"stars": { "title": "별 모으기", "description": "별을 모아 새로운 레벨을 열어요" }
},
"startButton": "게임 시작!",
"ageNote": "읽고 쓸 수 있는 8-14세 어린이에게 적합해요"
"ageNote": "읽고 쓸 수 있는 8-14세 어린이에게 적합해요",
"whatYouLearn": "배울 내용",
"readyTitle": "시작할 준비됐어?",
"readyMessage": "모험을 떠나 AI와 대화하는 법을 배워보자!"
},
"navigation": {
"back": "뒤로",
"next": "다음"
},
"map": { "title": "세계 지도", "subtitle": "레벨을 선택하고 모험을 시작하세요!", "worldLevels": "{count}개 레벨", "levelNumber": "레벨 {number}", "locked": "이전 레벨을 완료하면 열립니다" },
"worlds": { "1": { "title": "시작 마을" }, "2": { "title": "명확성의 성" }, "3": { "title": "맥락 동굴" }, "4": { "title": "창의력 협곡" }, "5": { "title": "마스터 산" } },

View File

@@ -1399,7 +1399,14 @@
"stars": { "title": "Ganhe Estrelas", "description": "Colete estrelas e desbloqueie novos níveis" }
},
"startButton": "Começar a Jogar!",
"ageNote": "Ideal para crianças de 8-14 anos que sabem ler e escrever"
"ageNote": "Ideal para crianças de 8-14 anos que sabem ler e escrever",
"whatYouLearn": "O que você vai aprender",
"readyTitle": "Pronto para começar?",
"readyMessage": "Vamos em uma aventura e aprender a falar com a IA!"
},
"navigation": {
"back": "Voltar",
"next": "Próximo"
},
"map": { "title": "Mapa do Mundo", "subtitle": "Escolha um nível e comece sua aventura!", "worldLevels": "{count} níveis", "levelNumber": "Nível {number}", "locked": "Complete o nível anterior para desbloquear" },
"worlds": { "1": { "title": "Vila Inicial" }, "2": { "title": "Castelo da Clareza" }, "3": { "title": "Cavernas de Contexto" }, "4": { "title": "Cânion Criativo" }, "5": { "title": "Montanha Mestre" } },

View File

@@ -1399,7 +1399,14 @@
"stars": { "title": "Собирай звёзды", "description": "Собирай звёзды и открывай новые уровни" }
},
"startButton": "Начать играть!",
"ageNote": "Лучше всего подходит для детей 8-14 лет, умеющих читать и писать"
"ageNote": "Лучше всего подходит для детей 8-14 лет, умеющих читать и писать",
"whatYouLearn": "Что ты узнаешь",
"readyTitle": "Готов начать?",
"readyMessage": "Отправимся в приключение и научимся разговаривать с ИИ!"
},
"navigation": {
"back": "Назад",
"next": "Далее"
},
"map": { "title": "Карта мира", "subtitle": "Выбери уровень и начни своё приключение!", "worldLevels": "{count} уровней", "levelNumber": "Уровень {number}", "locked": "Пройди предыдущий уровень для разблокировки" },
"worlds": { "1": { "title": "Стартовая деревня" }, "2": { "title": "Замок ясности" }, "3": { "title": "Пещеры контекста" }, "4": { "title": "Каньон творчества" }, "5": { "title": "Гора мастерства" } },

View File

@@ -1417,7 +1417,14 @@
}
},
"startButton": "Oynamaya Başla!",
"ageNote": "Okuma yazma bilen 8-14 yaş çocuklar için en uygun"
"ageNote": "Okuma yazma bilen 8-14 yaş çocuklar için en uygun",
"whatYouLearn": "Ne Öğreneceksin",
"readyTitle": "Başlamaya Hazır mısın?",
"readyMessage": "Haydi bir maceraya çıkalım ve yapay zeka ile nasıl konuşulacağını öğrenelim!"
},
"navigation": {
"back": "Geri",
"next": "İleri"
},
"map": {
"title": "Dünya Haritası",
@@ -1436,7 +1443,23 @@
"levels": {
"1_1_meet_promi": { "title": "Promi ile Tanış!", "description": "Robot arkadaşınla tanış ve yapay zekanın ne olduğunu öğren" },
"1_2_first_words": { "title": "Promi'in İlk Sözleri", "description": "İlk promptunu yazarak Promi'in anlamasına yardım et" },
"1_3_being_clear": { "title": "Net Olmak", "description": "Net talimatların neden daha iyi çalıştığını öğren" }
"1_3_being_clear": { "title": "Net Olmak", "description": "Net talimatların neden daha iyi çalıştığını öğren" },
"2_1_missing_details": { "title": "Eksik Detaylar", "description": "Detayların neden önemli olduğunu keşfet" },
"2_2_who_and_what": { "title": "Kim ve Ne", "description": "Promptlarına karakter ve nesne ekle" },
"2_3_when_and_where": { "title": "Ne Zaman ve Nerede", "description": "Promptlarına zaman ve yer eklemeyi öğren" },
"2_4_detail_detective": { "title": "Detay Dedektifi", "description": "Doğru detayları ekleme ustası ol" },
"3_1_setting_the_scene": { "title": "Sahneyi Hazırlamak", "description": "Arka plan bilgisinin yapay zekaya nasıl yardımcı olduğunu öğren" },
"3_2_show_dont_tell": { "title": "Göster, Anlatma", "description": "Örnekler kullanarak tam olarak ne istediğini göster" },
"3_3_format_finder": { "title": "Format Bulucu", "description": "Liste, hikaye, şiir ve daha fazlasını iste!" },
"3_4_context_champion": { "title": "Bağlam Şampiyonu", "description": "Tüm bağlam tekniklerini bir profesyonel gibi birleştir" },
"4_1_pretend_time": { "title": "Hayal Zamanı!", "description": "Rol yapma promptlarını öğren" },
"4_2_story_starters": { "title": "Hikaye Başlatıcılar", "description": "Yapay zeka ile harika hikayeler oluştur" },
"4_3_character_creator": { "title": "Karakter Yaratıcı", "description": "Yapay zekaya kişilik ver" },
"4_4_world_builder": { "title": "Dünya Oluşturucu", "description": "Hayal gücüyle dolu dünyalar yarat" },
"5_1_perfect_prompt": { "title": "Mükemmel Prompt", "description": "Netlik, detay ve bağlamı birleştir" },
"5_2_fix_it_up": { "title": "Düzelt!", "description": "Zayıf promptları bul ve geliştir" },
"5_3_prompt_remix": { "title": "Prompt Remix", "description": "Farklı sonuçlar için promptları yeniden yaz" },
"5_4_graduation_day": { "title": "Mezuniyet Günü", "description": "Son mücadele - Prompt Ustası ol!" }
},
"level": {
"backToMap": "Haritaya Dön",
@@ -1451,6 +1474,105 @@
"nextLevel": "Sonraki Seviye",
"backToMap": "Haritaya Dön",
"allDone": "Haritaya Dön"
},
"quiz": {
"goodLabel": "Harika prompt!",
"badLabel": "Pek iyi değil",
"correct": "Doğru bildin!",
"incorrect": "İyi deneme!",
"tryAgain": "Tekrar Dene"
},
"magicWords": {
"title": "Sihirli kelimeleri sürükle! ✨",
"dragOrTap": "🎯 Kelimeleri sürükle veya tıkla:",
"check": "Kontrol Et!",
"retry": "Tekrar Dene",
"correct": "doğru",
"tryAgain": "Tekrar dene!"
},
"dragDrop": {
"title": "Promptu oluştur!",
"instruction": "Okları kullanarak taşı veya iki parçaya tıklayarak yer değiştir!",
"result": "Sonuç",
"check": "Kontrol Et!",
"retry": "Tekrar Dene",
"success": "Mükemmel! Harika bir prompt oluşturdun!",
"almost": "Neredeyse! Sıralamaya devam et.",
"tapToSwap": "Yer değiştirmek için başka bir parçaya tıkla!"
},
"promptParts": {
"title": "Prompt Parçalarını Sırala!",
"instruction": "Her parçaya tıkla, sonra türünü seç!",
"score": "Puan",
"pickCategory": "Bu ne türü?",
"success": "Tüm parçaları doğru sıraladın!",
"retry": "Tekrar Dene",
"types": {
"role": "Rol",
"task": "Görev",
"context": "Bağlam",
"constraint": "Kısıtlama"
}
},
"exampleMatcher": {
"title": "Desen Eşleştirici",
"instruction": "Desene bak ve sıradaki ne olmalı seç!",
"pattern": "Desen:",
"check": "Kontrol Et!",
"retry": "Tekrar Dene",
"correct": "Doğru bildin! 🎉",
"tryAgain": "Tam değil - desene tekrar bak!"
},
"promptDoctor": {
"title": "Prompt Doktoru",
"health": "Prompt Sağlığı",
"sick": "Hasta Prompt",
"healthy": "Sağlıklı Prompt!",
"diagnose": "Düzeltmek için bir soruna tıkla:",
"success": "Prompt artık tamamen iyileşti!",
"retry": "Baştan Başla"
},
"stepByStep": {
"title": "Adım Adım Düşün",
"problem": "Problem:",
"withoutMagic": "Sihirli kelimeler olmadan:",
"addMagicWords": "Sihirli Kelimeleri Ekle!",
"magicWordsActive": "Sihirli kelimeler eklendi!",
"nextStep": "Sonraki Adımı Göster",
"withMagic": "Adım adım düşünme ile:",
"retry": "Tekrar Dene"
},
"promptLab": {
"title": "Prompt Laboratuvarı",
"progress": "İyileştirmeler",
"yourPrompt": "Promptun:",
"aiSays": "Yapay Zeka Yanıtı:",
"addDetails": "İyileştirme ekle:",
"success": "Promptun artık süper detaylı!",
"retry": "Baştan Başla"
},
"wordPredictor": {
"title": "Yapay Zeka Nasıl Düşünür",
"instruction": "Yapay zeka en mantıklı kelimeyi tahmin eder. Sen de yapay zeka gibi düşünebilir misin?",
"aiThinks": "Yapay zeka okuyor:",
"thinkingDefault": "Hmm, buraya hangi kelime en çok uyar?",
"check": "Tahminimi Kontrol Et!",
"correct": "Yapay zeka gibi düşünüyorsun!",
"tryAgain": "Tam değil! Yapay zeka en olası kelimeyi seçer.",
"retry": "Tekrar Dene"
},
"settings": {
"title": "Ayarlar",
"language": "Dil",
"progress": "İlerlemeniz",
"stars": "Yıldız",
"completed": "Tamamlanan",
"resetTitle": "İlerlemeyi Sıfırla",
"resetButton": "Tüm İlerlemeyi Sıfırla",
"resetWarning": "Bu tüm yıldızlarını ve ilerlemenizi silecek. Emin misiniz?",
"resetConfirm": "Evet, Her Şeyi Sıfırla",
"resetComplete": "İlerleme sıfırlandı! Yeniden yükleniyor...",
"cancel": "İptal"
}
}
}

View File

@@ -1399,7 +1399,14 @@
"stars": { "title": "赚取星星", "description": "收集星星,解锁新关卡" }
},
"startButton": "开始游戏!",
"ageNote": "适合8-14岁会读写的儿童"
"ageNote": "适合8-14岁会读写的儿童",
"whatYouLearn": "你将学到什么",
"readyTitle": "准备好开始了吗?",
"readyMessage": "让我们踏上冒险学习如何与AI对话"
},
"navigation": {
"back": "返回",
"next": "下一步"
},
"map": { "title": "世界地图", "subtitle": "选择一个关卡,开始你的冒险!", "worldLevels": "{count}个关卡", "levelNumber": "关卡{number}", "locked": "完成上一关以解锁" },
"worlds": { "1": { "title": "新手村" }, "2": { "title": "清晰城堡" }, "3": { "title": "上下文洞穴" }, "4": { "title": "创意峡谷" }, "5": { "title": "大师山" } },

16
public/sounds/README.md Normal file
View File

@@ -0,0 +1,16 @@
# Kids Game Background Music
Add an 8-bit dubstep/chiptune music file here named `8bit-game-music.mp3`.
## Recommended Sources (Royalty-Free)
- [OpenGameArt.org](https://opengameart.org/) - Free game assets including music
- [FreeMusicArchive.org](https://freemusicarchive.org/) - CC-licensed music
- [Incompetech.com](https://incompetech.com/) - Royalty-free music by Kevin MacLeod
## File Requirements
- **Filename:** `8bit-game-music.mp3`
- **Format:** MP3
- **Style:** 8-bit / Chiptune / Retro game music
- **Loop-friendly:** Ideally seamless loop for background music

View File

@@ -452,3 +452,260 @@
margin: 1rem 0;
}
/* Kids Map Animations */
@keyframes bounce-slow {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-8px);
}
}
@keyframes draw-path {
from {
stroke-dashoffset: 200;
}
to {
stroke-dashoffset: 0;
}
}
@keyframes float {
0%, 100% {
transform: translateY(0) rotate(0deg);
}
25% {
transform: translateY(-5px) rotate(2deg);
}
75% {
transform: translateY(3px) rotate(-2deg);
}
}
.animate-bounce-slow {
animation: bounce-slow 3s ease-in-out infinite;
}
.animate-draw-path {
animation: draw-path 2s ease-out forwards;
}
.animate-float {
animation: float 4s ease-in-out infinite;
}
/* Cloud animations for kids background */
@keyframes cloud-drift {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100vw);
}
}
.animate-cloud-slow {
animation: cloud-drift 45s linear infinite;
}
.animate-cloud-medium {
animation: cloud-drift 30s linear infinite;
}
.animate-cloud-fast {
animation: cloud-drift 20s linear infinite;
}
.animation-delay-500 {
animation-delay: 500ms;
}
.animation-delay-1000 {
animation-delay: 1000ms;
}
.animation-delay-1500 {
animation-delay: 1500ms;
}
/* Pixel Art Styles for Kids Game */
.pixel-font {
font-family: 'Courier New', Courier, monospace;
font-weight: bold;
letter-spacing: 0.5px;
}
.pixel-border {
clip-path: polygon(
0 4px, 4px 4px, 4px 0,
calc(100% - 4px) 0, calc(100% - 4px) 4px, 100% 4px,
100% calc(100% - 4px), calc(100% - 4px) calc(100% - 4px), calc(100% - 4px) 100%,
4px 100%, 4px calc(100% - 4px), 0 calc(100% - 4px)
);
}
.pixel-border-sm {
clip-path: polygon(
0 2px, 2px 2px, 2px 0,
calc(100% - 2px) 0, calc(100% - 2px) 2px, 100% 2px,
100% calc(100% - 2px), calc(100% - 2px) calc(100% - 2px), calc(100% - 2px) 100%,
2px 100%, 2px calc(100% - 2px), 0 calc(100% - 2px)
);
}
.pixel-btn {
position: relative;
border: none;
background: linear-gradient(180deg, #4A90D9 0%, #357ABD 100%);
color: white !important;
text-decoration: none !important;
color: white;
font-weight: bold;
padding: 8px 16px;
clip-path: polygon(
0 4px, 4px 4px, 4px 0,
calc(100% - 4px) 0, calc(100% - 4px) 4px, 100% 4px,
100% calc(100% - 4px), calc(100% - 4px) calc(100% - 4px), calc(100% - 4px) 100%,
4px 100%, 4px calc(100% - 4px), 0 calc(100% - 4px)
);
box-shadow: 0 4px 0 #2563EB;
transition: all 0.1s;
}
.pixel-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 0 #2563EB;
}
.pixel-btn:active {
transform: translateY(2px);
box-shadow: 0 2px 0 #2563EB;
}
.pixel-btn-green {
background: linear-gradient(180deg, #22C55E 0%, #16A34A 100%);
box-shadow: 0 4px 0 #15803D;
}
.pixel-btn-green:hover {
box-shadow: 0 6px 0 #15803D;
}
.pixel-btn-green:active {
box-shadow: 0 2px 0 #15803D;
}
.pixel-btn-amber {
background: linear-gradient(180deg, #F59E0B 0%, #D97706 100%);
box-shadow: 0 4px 0 #B45309;
}
.pixel-btn-amber:hover {
box-shadow: 0 6px 0 #B45309;
}
.pixel-btn-amber:active {
box-shadow: 0 2px 0 #B45309;
}
.pixel-panel {
background: linear-gradient(180deg, #FEF3C7 0%, #FDE68A 100%);
border: 4px solid #D97706;
clip-path: polygon(
0 8px, 8px 8px, 8px 0,
calc(100% - 8px) 0, calc(100% - 8px) 8px, 100% 8px,
100% calc(100% - 8px), calc(100% - 8px) calc(100% - 8px), calc(100% - 8px) 100%,
8px 100%, 8px calc(100% - 8px), 0 calc(100% - 8px)
);
}
.pixel-panel-blue {
background: linear-gradient(180deg, #DBEAFE 0%, #BFDBFE 100%);
border-color: #2563EB;
}
.pixel-panel-green {
background: linear-gradient(180deg, #DCFCE7 0%, #BBF7D0 100%);
border-color: #16A34A;
}
.pixel-panel-red {
background: linear-gradient(180deg, #FEE2E2 0%, #FECACA 100%);
border-color: #DC2626;
}
.pixel-text-shadow {
text-shadow: 2px 2px 0 rgba(0,0,0,0.3);
}
/* Pixelated image rendering */
.pixel-render {
image-rendering: pixelated;
image-rendering: crisp-edges;
}
/* Pixel art prose styles for kids levels */
.kids-prose-pixel {
color: #2C1810;
font-size: 1.5rem;
}
.kids-prose-pixel h1,
.kids-prose-pixel h2,
.kids-prose-pixel h3 {
color: #2C1810;
text-shadow: 2px 2px 0 rgba(0,0,0,0.2);
margin-bottom: 1rem;
border-bottom: none;
padding-bottom: 0;
}
.kids-prose-pixel h1 {
font-size: 2.75rem;
}
.kids-prose-pixel h2 {
font-size: 2.25rem;
}
.kids-prose-pixel h3 {
font-size: 1.875rem;
}
.kids-prose-pixel p {
color: #5D4037;
margin-bottom: 1.25rem;
line-height: 1.8;
font-size: 1.5rem;
}
.kids-prose-pixel ul,
.kids-prose-pixel ol {
color: #5D4037;
margin-bottom: 1.25rem;
padding-left: 2rem;
font-size: 1.5rem;
}
.kids-prose-pixel li {
margin-bottom: 0.75rem;
}
.kids-prose-pixel strong {
color: #2C1810;
font-weight: bold;
}
.kids-prose-pixel em {
color: #8B4513;
}
/* Reset margins for p tags inside interactive kids components */
.kids-prose-pixel [class*="rounded-xl"] p,
.kids-prose-pixel [class*="rounded-lg"] p,
.kids-prose-pixel .pixel-panel p {
margin: 0;
padding: 0;
}

View File

@@ -1,4 +1,31 @@
import { Schoolbell } from "next/font/google";
import { KidsHeader } from "@/components/kids/layout/kids-header";
import { BackgroundMusic } from "@/components/kids/layout/background-music";
import { LevelProvider } from "@/components/kids/providers/level-context";
const kidsFont = Schoolbell({
subsets: ["latin"],
weight: "400",
variable: "--font-kids",
});
// Pixel art cloud component for background
function PixelCloudBg({ className, style }: { className?: string; style?: React.CSSProperties }) {
return (
<svg
viewBox="0 0 32 16"
className={className}
style={{ imageRendering: "pixelated", ...style }}
>
<rect x="8" y="8" width="16" height="8" fill="white" />
<rect x="4" y="12" width="8" height="4" fill="white" />
<rect x="20" y="12" width="8" height="4" fill="white" />
<rect x="12" y="4" width="8" height="4" fill="white" />
<rect x="6" y="8" width="4" height="4" fill="white" />
<rect x="22" y="8" width="4" height="4" fill="white" />
</svg>
);
}
export default function KidsLayout({
children,
@@ -6,11 +33,50 @@ export default function KidsLayout({
children: React.ReactNode;
}) {
return (
<div className="min-h-screen bg-gradient-to-b from-sky-50 via-white to-emerald-50 dark:from-sky-950 dark:via-background dark:to-emerald-950">
<LevelProvider>
<div className={`fixed inset-0 flex flex-col text-xl light ${kidsFont.className}`} data-theme="light" style={{ colorScheme: "light" }}>
{/* Smooth gradient sky background */}
<div
className="absolute inset-0 -z-10"
style={{
background: "linear-gradient(180deg, #4A90D9 0%, #87CEEB 30%, #98D8F0 60%, #B8E8F8 100%)"
}}
/>
{/* Animated pixel clouds - drift from left to right */}
<div className="absolute inset-0 -z-5 overflow-hidden pointer-events-none">
<PixelCloudBg
className="absolute w-24 h-12 opacity-90 animate-cloud-slow"
style={{ top: "8%", left: 0, animationDelay: "0s" }}
/>
<PixelCloudBg
className="absolute w-32 h-16 opacity-80 animate-cloud-medium"
style={{ top: "15%", left: 0, animationDelay: "-10s" }}
/>
<PixelCloudBg
className="absolute w-20 h-10 opacity-85 animate-cloud-fast"
style={{ top: "5%", left: 0, animationDelay: "-5s" }}
/>
<PixelCloudBg
className="absolute w-28 h-14 opacity-75 animate-cloud-slow"
style={{ top: "22%", left: 0, animationDelay: "-20s" }}
/>
<PixelCloudBg
className="absolute w-16 h-8 opacity-70 animate-cloud-medium"
style={{ top: "12%", left: 0, animationDelay: "-15s" }}
/>
<PixelCloudBg
className="absolute w-36 h-18 opacity-60 animate-cloud-fast"
style={{ top: "28%", left: 0, animationDelay: "-8s" }}
/>
</div>
<KidsHeader />
<main className="container py-6">
<main className="flex-1 min-h-0 overflow-hidden">
{children}
</main>
<BackgroundMusic />
</div>
</LevelProvider>
);
}

View File

@@ -1,9 +1,7 @@
import { notFound } from "next/navigation";
import Link from "next/link";
import { getTranslations, getLocale } from "next-intl/server";
import { getLevelBySlug, getAdjacentLevels, getAllLevels } from "@/lib/kids/levels";
import { ChevronLeft, ChevronRight, Map } from "lucide-react";
import { Button } from "@/components/ui/button";
import { getLocale } from "next-intl/server";
import { getLevelBySlug, getAllLevels } from "@/lib/kids/levels";
import { LevelContentWrapper } from "@/components/kids/layout/level-content-wrapper";
import type { Metadata } from "next";
interface LevelPageProps {
@@ -33,15 +31,12 @@ export async function generateMetadata({ params }: LevelPageProps): Promise<Meta
export default async function LevelPage({ params }: LevelPageProps) {
const { slug } = await params;
const level = getLevelBySlug(slug);
const t = await getTranslations("kids");
const locale = await getLocale();
if (!level) {
notFound();
}
const { prev, next } = getAdjacentLevels(slug);
// Try to load locale-specific content, fall back to English
let Content;
try {
@@ -50,80 +45,13 @@ export default async function LevelPage({ params }: LevelPageProps) {
try {
Content = (await import(`@/content/kids/en/${slug}.mdx`)).default;
} catch {
Content = () => (
<div className="text-center py-12">
<p className="text-muted-foreground">
{t("level.comingSoon")}
</p>
</div>
);
Content = null;
}
}
return (
<div className="max-w-2xl mx-auto">
{/* Level Header */}
<header className="mb-8">
<Link
href="/kids/map"
className="inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-primary mb-4"
>
<Map className="h-4 w-4" />
{t("level.backToMap")}
</Link>
<div className="flex items-center gap-3 mb-2">
<span className="px-3 py-1 bg-primary/10 text-primary rounded-full text-sm font-medium">
{t("level.levelLabel", { number: `${level.world}-${level.levelNumber}` })}
</span>
</div>
<h1 className="text-3xl font-bold tracking-tight mb-2">
{level.title}
</h1>
<p className="text-muted-foreground">
{level.description}
</p>
</header>
{/* Level Content */}
<div className="prose max-w-none kids-prose">
<Content />
</div>
{/* Navigation */}
<nav className="flex items-center justify-between mt-12 pt-6 border-t">
{prev ? (
<Button variant="outline" asChild className="gap-2 rounded-xl">
<Link href={`/kids/level/${prev.slug}`}>
<ChevronLeft className="h-4 w-4" />
<span className="hidden sm:inline">{prev.title}</span>
<span className="sm:hidden">{t("level.previous")}</span>
</Link>
</Button>
) : (
<div />
)}
<Button variant="ghost" asChild>
<Link href="/kids/map">
<Map className="h-4 w-4 mr-2" />
{t("level.map")}
</Link>
</Button>
{next ? (
<Button variant="outline" asChild className="gap-2 rounded-xl">
<Link href={`/kids/level/${next.slug}`}>
<span className="hidden sm:inline">{next.title}</span>
<span className="sm:hidden">{t("level.next")}</span>
<ChevronRight className="h-4 w-4" />
</Link>
</Button>
) : (
<div />
)}
</nav>
</div>
<LevelContentWrapper levelSlug={slug} levelNumber={`${level.world}-${level.levelNumber}`}>
{Content ? <Content /> : null}
</LevelContentWrapper>
);
}

View File

@@ -11,12 +11,7 @@ export default async function KidsMapPage() {
const t = await getTranslations("kids");
return (
<div className="max-w-4xl mx-auto">
<div className="text-center mb-8">
<h1 className="text-3xl font-bold mb-2">{t("map.title")}</h1>
<p className="text-muted-foreground">{t("map.subtitle")}</p>
</div>
<div className="h-full flex flex-col overflow-hidden">
<ProgressMap />
</div>
);

View File

@@ -1,84 +1,11 @@
import Link from "next/link";
import { getTranslations } from "next-intl/server";
import { Play, Sparkles, Star, Bot } from "lucide-react";
import { Button } from "@/components/ui/button";
import { PromiCharacter } from "@/components/kids/elements/character-guide";
import type { Metadata } from "next";
import { KidsHomeContent } from "@/components/kids/layout/kids-home-content";
export const metadata: Metadata = {
title: "Learn Prompting for Kids | prompts.chat",
description: "A fun, game-based way for kids to learn how to talk to AI. Join Promi the robot on an adventure through Prompt Land!",
};
export default async function KidsHomePage() {
const t = await getTranslations("kids");
return (
<div className="max-w-2xl mx-auto text-center">
{/* Hero */}
<div className="mb-8">
<div className="inline-flex items-center gap-2 px-4 py-2 bg-primary/10 rounded-full text-primary text-sm font-medium mb-6">
<Sparkles className="h-4 w-4" />
{t("home.badge")}
</div>
<h1 className="text-4xl md:text-5xl font-bold tracking-tight mb-4 bg-gradient-to-r from-primary via-purple-500 to-pink-500 bg-clip-text text-transparent">
{t("home.title")}
</h1>
<p className="text-xl text-muted-foreground mb-8">
{t("home.subtitle")}
</p>
</div>
{/* Promi Introduction */}
<div className="mb-10 p-6 bg-white dark:bg-card rounded-2xl shadow-lg border-2 border-primary/20">
<div className="flex flex-col sm:flex-row items-center gap-6">
<div className="shrink-0">
<PromiCharacter mood="happy" size="lg" />
</div>
<div className="text-left">
<p className="text-lg font-medium mb-2">{t("home.promiIntro.greeting")}</p>
<p className="text-muted-foreground">
{t("home.promiIntro.message")}
</p>
</div>
</div>
</div>
{/* Features */}
<div className="grid sm:grid-cols-3 gap-4 mb-10">
<div className="p-4 bg-emerald-50 dark:bg-emerald-950/30 rounded-xl border border-emerald-200 dark:border-emerald-800">
<div className="text-3xl mb-2">🎮</div>
<h3 className="font-semibold mb-1">{t("home.features.games.title")}</h3>
<p className="text-sm text-muted-foreground">{t("home.features.games.description")}</p>
</div>
<div className="p-4 bg-blue-50 dark:bg-blue-950/30 rounded-xl border border-blue-200 dark:border-blue-800">
<div className="text-3xl mb-2">📖</div>
<h3 className="font-semibold mb-1">{t("home.features.stories.title")}</h3>
<p className="text-sm text-muted-foreground">{t("home.features.stories.description")}</p>
</div>
<div className="p-4 bg-purple-50 dark:bg-purple-950/30 rounded-xl border border-purple-200 dark:border-purple-800">
<div className="text-3xl mb-2"></div>
<h3 className="font-semibold mb-1">{t("home.features.stars.title")}</h3>
<p className="text-sm text-muted-foreground">{t("home.features.stars.description")}</p>
</div>
</div>
{/* CTA */}
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Button asChild size="lg" className="text-lg px-8 py-6 rounded-xl shadow-lg hover:shadow-xl transition-shadow">
<Link href="/kids/map">
<Play className="h-5 w-5 mr-2" />
{t("home.startButton")}
</Link>
</Button>
</div>
{/* Age note */}
<p className="mt-8 text-sm text-muted-foreground">
{t("home.ageNote")}
</p>
</div>
);
export default function KidsHomePage() {
return <KidsHomeContent />;
}

View File

@@ -150,6 +150,7 @@ export default async function RootLayout({
const headersList = await headers();
const pathname = headersList.get("x-pathname") || headersList.get("x-invoke-path") || "";
const isEmbedRoute = pathname.startsWith("/embed");
const isKidsRoute = pathname.startsWith("/kids");
const locale = await getLocale();
const messages = await getMessages();
@@ -198,7 +199,7 @@ export default async function RootLayout({
</>
)}
<Providers locale={locale} messages={messages} theme={config.theme} branding={{ ...config.branding, useCloneBranding: config.homepage?.useCloneBranding }}>
{isEmbedRoute ? (
{isEmbedRoute || isKidsRoute ? (
children
) : (
<>

View File

@@ -1,189 +1,290 @@
"use client";
import { useState, useCallback } from "react";
import { Check, RefreshCw, GripVertical } from "lucide-react";
import { Button } from "@/components/ui/button";
import { useState, useCallback, useEffect, useId } from "react";
import { useTranslations } from "next-intl";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { useLevelSlug } from "@/components/kids/providers/level-context";
import { getComponentState, saveComponentState } from "@/lib/kids/progress";
interface DragDropPromptProps {
title?: string;
instruction?: string;
pieces: string[];
correctOrder: number[]; // Indices of pieces in correct order
correctOrder: number[];
successMessage?: string;
}
interface SavedState {
currentOrder: number[];
submitted: boolean;
shuffledPieces: number[];
}
export function DragDropPrompt({
title = "Build the prompt! 🧩",
instruction = "Drag the pieces into the right order to make a good prompt.",
title,
instruction,
pieces,
correctOrder,
successMessage = "Perfect! You built a great prompt!",
successMessage,
}: DragDropPromptProps) {
const [shuffledPieces] = useState(() => {
// Create array of indices and shuffle
const indices = pieces.map((_, i) => i);
for (let i = indices.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[indices[i], indices[j]] = [indices[j], indices[i]];
}
return indices;
});
const t = useTranslations("kids.dragDrop");
const levelSlug = useLevelSlug();
const componentId = useId();
const displayTitle = title || t("title");
const displayInstruction = instruction || t("instruction");
const [currentOrder, setCurrentOrder] = useState<number[]>(shuffledPieces);
const [shuffledPieces, setShuffledPieces] = useState<number[]>([]);
const [currentOrder, setCurrentOrder] = useState<number[]>([]);
const [submitted, setSubmitted] = useState(false);
const [draggedIndex, setDraggedIndex] = useState<number | null>(null);
const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
const [isLoaded, setIsLoaded] = useState(false);
const isCorrect = useCallback(() => {
// Load saved state on mount
useEffect(() => {
const shuffle = () => {
const indices = pieces.map((_, i) => i);
for (let i = indices.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[indices[i], indices[j]] = [indices[j], indices[i]];
}
return indices;
};
if (!levelSlug) {
const shuffled = shuffle();
setShuffledPieces(shuffled);
setCurrentOrder(shuffled);
setIsLoaded(true);
return;
}
const saved = getComponentState<SavedState>(levelSlug, componentId);
if (saved && saved.shuffledPieces && saved.shuffledPieces.length > 0 && saved.currentOrder) {
setShuffledPieces(saved.shuffledPieces);
setCurrentOrder(saved.currentOrder);
setSubmitted(saved.submitted || false);
} else {
const shuffled = shuffle();
setShuffledPieces(shuffled);
setCurrentOrder(shuffled);
}
setIsLoaded(true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [levelSlug, componentId]);
// Save state when it changes
useEffect(() => {
if (!levelSlug || !isLoaded || currentOrder.length === 0) return;
saveComponentState<SavedState>(levelSlug, componentId, {
currentOrder,
submitted,
shuffledPieces,
});
}, [levelSlug, componentId, currentOrder, submitted, shuffledPieces, isLoaded]);
// Don't render until loaded to prevent hydration mismatch
if (!isLoaded) return null;
const isCorrect = () => {
return currentOrder.every((pieceIndex, position) => pieceIndex === correctOrder[position]);
}, [currentOrder, correctOrder]);
const handleDragStart = (index: number) => {
setDraggedIndex(index);
};
const handleDragOver = (e: React.DragEvent, index: number) => {
e.preventDefault();
if (draggedIndex === null || draggedIndex === index) return;
// Move piece left (swap with previous)
const moveLeft = (position: number) => {
if (submitted || position === 0) return;
const newOrder = [...currentOrder];
const draggedItem = newOrder[draggedIndex];
newOrder.splice(draggedIndex, 1);
newOrder.splice(index, 0, draggedItem);
[newOrder[position - 1], newOrder[position]] = [newOrder[position], newOrder[position - 1]];
setCurrentOrder(newOrder);
setDraggedIndex(index);
};
const handleDragEnd = () => {
setDraggedIndex(null);
};
const moveItem = (fromIndex: number, direction: "up" | "down") => {
const toIndex = direction === "up" ? fromIndex - 1 : fromIndex + 1;
if (toIndex < 0 || toIndex >= currentOrder.length) return;
// Move piece right (swap with next)
const moveRight = (position: number) => {
if (submitted || position === currentOrder.length - 1) return;
const newOrder = [...currentOrder];
[newOrder[fromIndex], newOrder[toIndex]] = [newOrder[toIndex], newOrder[fromIndex]];
[newOrder[position], newOrder[position + 1]] = [newOrder[position + 1], newOrder[position]];
setCurrentOrder(newOrder);
};
// Tap to select, tap another to swap
const handleTap = (position: number) => {
if (submitted) return;
if (selectedIndex === null) {
// Select this piece
setSelectedIndex(position);
} else if (selectedIndex === position) {
// Deselect
setSelectedIndex(null);
} else {
// Swap with selected piece
const newOrder = [...currentOrder];
[newOrder[selectedIndex], newOrder[position]] = [newOrder[position], newOrder[selectedIndex]];
setCurrentOrder(newOrder);
setSelectedIndex(null);
}
};
const handleSubmit = () => {
setSubmitted(true);
setSelectedIndex(null);
};
const handleReset = () => {
setCurrentOrder(shuffledPieces);
setSubmitted(false);
setSelectedIndex(null);
};
const correct = isCorrect();
return (
<div className="my-6 rounded-2xl border-2 border-blue-200 dark:border-blue-800 overflow-hidden">
<div className="my-4 p-4 bg-white rounded-xl border-4 border-[#D97706]">
{/* Header */}
<div className="px-4 py-3 bg-gradient-to-r from-blue-100 to-cyan-100 dark:from-blue-950/50 dark:to-cyan-950/50 border-b border-blue-200 dark:border-blue-800">
<p className="font-semibold m-0">{title}</p>
<p className="text-sm text-muted-foreground m-0">{instruction}</p>
<div className="flex items-center gap-3 mb-3">
<PixelPuzzleIcon />
<span className="font-bold text-2xl text-[#2C1810]">{displayTitle}</span>
</div>
<p className="text-lg text-[#8B7355] mb-4 m-0">{displayInstruction}</p>
<div className="p-4 space-y-4">
{/* Draggable pieces */}
<div className="space-y-2">
{currentOrder.map((pieceIndex, position) => (
{/* Pieces with arrow controls */}
<div className="space-y-2 mb-4">
{currentOrder.map((pieceIndex, position) => {
const isSelected = selectedIndex === position;
const isCorrectPiece = submitted && pieceIndex === correctOrder[position];
const isWrongPiece = submitted && pieceIndex !== correctOrder[position];
return (
<div
key={`${pieceIndex}-${position}`}
draggable={!submitted}
onDragStart={() => handleDragStart(position)}
onDragOver={(e) => handleDragOver(e, position)}
onDragEnd={handleDragEnd}
className={cn(
"flex items-center gap-3 p-3 rounded-xl border-2 transition-all",
!submitted && "cursor-grab active:cursor-grabbing hover:border-primary hover:shadow-md",
submitted && pieceIndex === correctOrder[position] && "border-green-500 bg-green-50 dark:bg-green-950/30",
submitted && pieceIndex !== correctOrder[position] && "border-red-400 bg-red-50 dark:bg-red-950/30",
!submitted && "border-muted-foreground/20 bg-white dark:bg-card",
draggedIndex === position && "opacity-50 scale-95"
"flex items-center gap-2 p-2 rounded-lg border-2 transition-all",
!submitted && !isSelected && "bg-white border-[#D97706] hover:bg-[#FEF3C7]",
!submitted && isSelected && "bg-[#DBEAFE] border-[#3B82F6] ring-2 ring-[#3B82F6] scale-[1.02]",
isCorrectPiece && "bg-[#DCFCE7] border-[#16A34A]",
isWrongPiece && "bg-[#FEE2E2] border-[#DC2626]"
)}
>
{!submitted && (
<div className="flex flex-col gap-0.5">
<button
onClick={() => moveItem(position, "up")}
disabled={position === 0}
className="p-0.5 hover:text-primary disabled:opacity-30"
>
</button>
<button
onClick={() => moveItem(position, "down")}
disabled={position === currentOrder.length - 1}
className="p-0.5 hover:text-primary disabled:opacity-30"
>
</button>
</div>
)}
<GripVertical className={cn("h-5 w-5 text-muted-foreground shrink-0", submitted && "opacity-0")} />
<span className="flex-1 font-mono text-sm">{pieces[pieceIndex]}</span>
<span className="w-6 h-6 rounded-full bg-muted flex items-center justify-center text-xs font-bold">
{/* Position number */}
<span className="w-8 h-8 flex items-center justify-center bg-[#D97706] text-white font-bold rounded-md text-lg">
{position + 1}
</span>
{/* Left arrow */}
<button
onClick={() => moveLeft(position)}
disabled={submitted || position === 0}
className={cn(
"w-10 h-10 flex items-center justify-center rounded-lg border-2 transition-all",
position === 0 || submitted
? "bg-gray-100 border-gray-200 text-gray-300 cursor-not-allowed"
: "bg-[#FEF3C7] border-[#D97706] text-[#D97706] hover:bg-[#D97706] hover:text-white active:scale-95"
)}
>
<ChevronLeft className="w-6 h-6" />
</button>
{/* Piece content - tappable */}
<button
onClick={() => handleTap(position)}
disabled={submitted}
className={cn(
"flex-1 px-4 py-3 text-xl font-medium text-left rounded-lg transition-all",
!submitted && "hover:bg-[#FEF3C7] cursor-pointer",
submitted && "cursor-default"
)}
>
<span className="text-[#2C1810]">{pieces[pieceIndex]}</span>
</button>
{/* Right arrow */}
<button
onClick={() => moveRight(position)}
disabled={submitted || position === currentOrder.length - 1}
className={cn(
"w-10 h-10 flex items-center justify-center rounded-lg border-2 transition-all",
position === currentOrder.length - 1 || submitted
? "bg-gray-100 border-gray-200 text-gray-300 cursor-not-allowed"
: "bg-[#FEF3C7] border-[#D97706] text-[#D97706] hover:bg-[#D97706] hover:text-white active:scale-95"
)}
>
<ChevronRight className="w-6 h-6" />
</button>
{/* Status indicator */}
{submitted && (
<span className="w-8 h-8 flex items-center justify-center text-xl">
{isCorrectPiece ? "✓" : "✗"}
</span>
)}
</div>
))}
);
})}
</div>
{/* Hint for tap-to-swap */}
{selectedIndex !== null && (
<div className="bg-[#DBEAFE] border-2 border-[#3B82F6] rounded-lg p-3 mb-4 text-center">
<p className="text-lg text-[#1E40AF] font-medium m-0">
👆 {t("tapToSwap")}
</p>
</div>
)}
{/* Preview */}
<div className="p-3 bg-muted/30 rounded-xl">
<p className="text-xs text-muted-foreground mb-2 m-0">Your prompt will look like:</p>
<pre className="whitespace-pre-wrap text-sm font-mono m-0">
{currentOrder.map((i) => pieces[i]).join(" ")}
</pre>
</div>
{/* Preview */}
<div className="bg-[#FEF3C7]/50 rounded-lg p-4 mb-4 border-2 border-[#D97706]/30">
<span className="text-lg text-[#8B7355]">{t("result")}: </span>
<span className="text-xl text-[#2C1810]">
{currentOrder.map((i) => pieces[i]).join(" ")}
</span>
</div>
{/* Result */}
{submitted && (
<div
className={cn(
"p-4 rounded-xl text-center",
correct
? "bg-green-100 dark:bg-green-950/50 border border-green-300 dark:border-green-800"
: "bg-amber-100 dark:bg-amber-950/50 border border-amber-300 dark:border-amber-800"
)}
>
{correct ? (
<>
<p className="text-2xl mb-2">🎉</p>
<p className="font-semibold text-lg m-0">{successMessage}</p>
</>
) : (
<>
<p className="font-semibold m-0">Almost there!</p>
<p className="text-sm text-muted-foreground m-0 mt-1">
Try moving the pieces around to find the best order.
</p>
</>
)}
</div>
)}
{/* Actions */}
<div className="flex gap-2">
{!submitted ? (
<Button onClick={handleSubmit} className="rounded-full">
<Check className="h-4 w-4 mr-1" />
Check my prompt!
</Button>
{/* Result feedback */}
{submitted && (
<div className={cn(
"rounded-lg p-4 mt-4 mb-4 text-center",
correct ? "bg-[#DCFCE7] border-2 border-[#16A34A]" : "bg-[#FEF3C7] border-2 border-[#D97706]"
)}>
{correct ? (
<p className="font-bold text-xl m-0 text-[#16A34A]">🎉 {successMessage || t("success")}</p>
) : (
<Button onClick={handleReset} variant="outline" className="rounded-full">
<RefreshCw className="h-4 w-4 mr-1" />
Try again
</Button>
<p className="font-bold text-lg m-0 text-[#D97706]">{t("almost")}</p>
)}
</div>
)}
{/* Actions */}
<div className="flex gap-3">
{!submitted ? (
<button
onClick={handleSubmit}
className="px-6 py-3 bg-[#22C55E] hover:bg-[#16A34A] text-white font-bold rounded-lg text-xl transition-colors"
>
{t("check")}
</button>
) : (
<button
onClick={handleReset}
className="px-6 py-3 bg-[#8B4513] hover:bg-[#A0522D] text-white font-bold rounded-lg text-xl transition-colors"
>
{t("retry")}
</button>
)}
</div>
</div>
);
}
function PixelPuzzleIcon() {
return (
<svg viewBox="0 0 16 16" className="w-5 h-5 inline-block text-[#3B82F6]" style={{ imageRendering: "pixelated" }}>
<rect x="2" y="2" width="5" height="5" fill="currentColor" />
<rect x="9" y="2" width="5" height="5" fill="currentColor" />
<rect x="2" y="9" width="5" height="5" fill="currentColor" />
<rect x="9" y="9" width="5" height="5" fill="currentColor" />
<rect x="7" y="4" width="2" height="2" fill="currentColor" />
<rect x="4" y="7" width="2" height="2" fill="currentColor" />
</svg>
);
}

View File

@@ -0,0 +1,196 @@
"use client";
import { useState, useEffect, useId } from "react";
import { useTranslations } from "next-intl";
import { cn } from "@/lib/utils";
import { useLevelSlug } from "@/components/kids/providers/level-context";
import { getComponentState, saveComponentState } from "@/lib/kids/progress";
interface Example {
input: string;
output: string;
}
interface ExampleMatcherProps {
title?: string;
instruction?: string;
examples: Example[];
question: string;
options: string[];
correctAnswer: string;
explanation?: string;
}
interface SavedState {
selectedAnswer: string | null;
submitted: boolean;
}
export function ExampleMatcher({
title,
instruction,
examples,
question,
options,
correctAnswer,
explanation,
}: ExampleMatcherProps) {
const t = useTranslations("kids.exampleMatcher");
const levelSlug = useLevelSlug();
const componentId = useId();
const displayTitle = title || t("title");
const displayInstruction = instruction || t("instruction");
const [selectedAnswer, setSelectedAnswer] = useState<string | null>(null);
const [submitted, setSubmitted] = useState(false);
const [isLoaded, setIsLoaded] = useState(false);
// Load saved state
useEffect(() => {
if (!levelSlug) {
setIsLoaded(true);
return;
}
const saved = getComponentState<SavedState>(levelSlug, componentId);
if (saved) {
setSelectedAnswer(saved.selectedAnswer);
setSubmitted(saved.submitted);
}
setIsLoaded(true);
}, [levelSlug, componentId]);
// Save state
useEffect(() => {
if (!levelSlug || !isLoaded) return;
saveComponentState<SavedState>(levelSlug, componentId, {
selectedAnswer,
submitted,
});
}, [levelSlug, componentId, selectedAnswer, submitted, isLoaded]);
if (!isLoaded) return null;
const isCorrect = selectedAnswer === correctAnswer;
const handleSelect = (option: string) => {
if (submitted) return;
setSelectedAnswer(option);
};
const handleSubmit = () => {
if (!selectedAnswer) return;
setSubmitted(true);
};
const handleReset = () => {
setSelectedAnswer(null);
setSubmitted(false);
};
return (
<div className="my-4 p-4 bg-gradient-to-br from-[#E0E7FF] to-[#C7D2FE] border-4 border-[#6366F1] rounded-xl">
{/* Title */}
<h3 className="text-xl font-bold text-[#4338CA] mb-2 flex items-center gap-2">
🧩 {displayTitle}
</h3>
<p className="text-[#5D4037] mb-4 m-0">{displayInstruction}</p>
{/* Examples Pattern */}
<div className="bg-white/80 rounded-lg p-4 mb-4 border-2 border-[#6366F1]">
<div className="text-sm font-medium text-[#4338CA] mb-2">{t("pattern")}</div>
<div className="space-y-2">
{examples.map((example, index) => (
<div key={index} className="flex items-center gap-3 text-lg">
<span className="px-3 py-1 bg-[#E0E7FF] rounded-lg font-medium">{example.input}</span>
<span className="text-[#6366F1] font-bold"></span>
<span className="px-3 py-1 bg-[#C7D2FE] rounded-lg font-medium">{example.output}</span>
</div>
))}
{/* Question row */}
<div className="flex items-center gap-3 text-lg pt-2 border-t-2 border-dashed border-[#6366F1]">
<span className="px-3 py-1 bg-[#FEF3C7] border-2 border-[#F59E0B] rounded-lg font-medium">
{question}
</span>
<span className="text-[#6366F1] font-bold"></span>
<span className="px-3 py-1 bg-gray-100 rounded-lg font-medium text-gray-400">
{submitted ? (isCorrect ? selectedAnswer : `${selectedAnswer}`) : "???"}
</span>
</div>
</div>
</div>
{/* Options */}
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2 mb-4">
{options.map((option) => {
const isSelected = selectedAnswer === option;
const showCorrect = submitted && option === correctAnswer;
const showWrong = submitted && isSelected && !isCorrect;
return (
<button
key={option}
onClick={() => handleSelect(option)}
disabled={submitted}
className={cn(
"px-4 py-3 rounded-lg border-2 text-xl font-bold transition-all",
!submitted && !isSelected && "bg-white border-gray-300 hover:border-[#6366F1] hover:bg-[#E0E7FF]",
!submitted && isSelected && "bg-[#C7D2FE] border-[#6366F1] scale-105",
showCorrect && "bg-green-100 border-green-500 text-green-700",
showWrong && "bg-red-100 border-red-400 text-red-600",
submitted && !showCorrect && !showWrong && "opacity-50"
)}
>
{option}
</button>
);
})}
</div>
{/* Submit/Reset buttons */}
<div className="flex gap-2">
{!submitted ? (
<button
onClick={handleSubmit}
disabled={!selectedAnswer}
className={cn(
"px-6 py-2 rounded-lg font-bold text-white transition-all",
selectedAnswer
? "bg-[#6366F1] hover:bg-[#4F46E5]"
: "bg-gray-300 cursor-not-allowed"
)}
>
{t("check")}
</button>
) : (
<button
onClick={handleReset}
className="px-6 py-2 rounded-lg font-bold bg-[#6366F1] hover:bg-[#4F46E5] text-white"
>
{t("retry")}
</button>
)}
</div>
{/* Result feedback */}
{submitted && (
<div className={cn(
"mt-4 p-4 rounded-lg border-2 animate-in fade-in zoom-in-95 duration-300",
isCorrect
? "bg-green-100 border-green-500"
: "bg-amber-100 border-amber-500"
)}>
<p className={cn(
"font-bold text-lg m-0",
isCorrect ? "text-green-700" : "text-amber-700"
)}>
{isCorrect ? t("correct") : t("tryAgain")}
</p>
{explanation && (
<p className="text-[#5D4037] mt-2 m-0">{explanation}</p>
)}
</div>
)}
</div>
);
}

View File

@@ -1,7 +1,14 @@
export { PromiCharacter, SpeechBubble, PromiWithMessage } from "./character-guide";
export { PromiCharacter, PromiWithMessage } from './character-guide';
export { Section, LevelSlides } from './level-slides';
export { StoryScene, Panel } from "./story-scene";
export { ProgressMap } from "./progress-map";
export { PromptVsMistake } from "./prompt-vs-mistake";
export { MagicWords } from "./magic-words";
export { DragDropPrompt } from "./drag-drop-prompt";
export { LevelComplete } from "./level-complete";
export { PromptParts } from "./prompt-parts";
export { ExampleMatcher } from "./example-matcher";
export { PromptDoctor } from "./prompt-doctor";
export { StepByStep } from "./step-by-step";
export { PromptLab } from "./prompt-lab";
export { WordPredictor } from "./word-predictor";

View File

@@ -3,16 +3,15 @@
import { useEffect, useState } from "react";
import Link from "next/link";
import { useTranslations } from "next-intl";
import { Star, ArrowRight, Map, RotateCcw } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { completeLevel, getLevelProgress } from "@/lib/kids/progress";
import { getAdjacentLevels } from "@/lib/kids/levels";
import { PromiCharacter } from "./character-guide";
import { getAdjacentLevels, getLevelBySlug } from "@/lib/kids/levels";
import { analyticsKids } from "@/lib/analytics";
import { PixelRobot, PixelStar } from "./pixel-art";
interface LevelCompleteProps {
levelSlug: string;
stars?: number; // 1-3, calculated based on performance
stars?: number;
message?: string;
}
@@ -27,58 +26,63 @@ export function LevelComplete({
const { next } = getAdjacentLevels(levelSlug);
useEffect(() => {
// Check if already completed with higher stars
const existingProgress = getLevelProgress(levelSlug);
const existingStars = existingProgress?.stars || 0;
if (stars > existingStars) {
// Save progress
completeLevel(levelSlug, stars);
setShowConfetti(true);
// Track level completion
const level = getLevelBySlug(levelSlug);
if (level) {
analyticsKids.completeLevel(levelSlug, level.world, stars);
}
}
setSavedStars(Math.max(stars, existingStars));
// Hide confetti after animation
const timer = setTimeout(() => setShowConfetti(false), 3000);
return () => clearTimeout(timer);
}, [levelSlug, stars]);
return (
<div className="my-8 relative">
{/* Confetti effect */}
{/* Pixel confetti effect */}
{showConfetti && (
<div className="absolute inset-0 pointer-events-none overflow-hidden">
{Array.from({ length: 20 }).map((_, i) => (
{Array.from({ length: 15 }).map((_, i) => (
<div
key={i}
className="absolute animate-bounce"
style={{
left: `${Math.random() * 100}%`,
top: `${Math.random() * 50}%`,
animationDelay: `${Math.random() * 0.5}s`,
animationDuration: `${0.5 + Math.random() * 0.5}s`,
left: `${10 + (i * 6)}%`,
top: `${10 + (i % 4) * 15}%`,
animationDelay: `${i * 0.1}s`,
animationDuration: `${0.5 + (i % 3) * 0.2}s`,
}}
>
{["🎉", "⭐", "🌟", "✨", "🎊"][Math.floor(Math.random() * 5)]}
<PixelStar filled className="w-6 h-6" />
</div>
))}
</div>
)}
<div className="rounded-2xl border-4 border-green-400 dark:border-green-600 bg-gradient-to-br from-green-50 to-emerald-50 dark:from-green-950/50 dark:to-emerald-950/50 overflow-hidden">
<div className="pixel-panel pixel-panel-green overflow-hidden">
{/* Header */}
<div className="p-6 text-center">
<div className="flex justify-center mb-4">
<PromiCharacter mood="celebrating" size="lg" />
<PixelRobot className="w-16 h-20 animate-bounce-slow" />
</div>
<h2 className="text-3xl font-bold mb-2">🎉 {t("levelComplete.title")}</h2>
<p className="text-lg text-muted-foreground">{message}</p>
<h2 className="text-4xl font-bold mb-3 text-[#2C1810] pixel-text-shadow">
{t("levelComplete.title")}
</h2>
<p className="text-xl text-[#5D4037] m-0">{message}</p>
</div>
{/* Stars */}
<div className="flex justify-center gap-2 pb-6">
{/* Pixel Stars */}
<div className="flex justify-center gap-3 pb-6">
{[1, 2, 3].map((star) => (
<div
key={star}
@@ -86,48 +90,74 @@ export function LevelComplete({
"transition-all duration-500",
star <= savedStars ? "scale-100" : "scale-75 opacity-30"
)}
style={{
animationDelay: `${star * 0.2}s`,
}}
style={{ animationDelay: `${star * 0.2}s` }}
>
<Star
className={cn(
"h-12 w-12",
star <= savedStars
? "fill-amber-400 text-amber-400 drop-shadow-lg"
: "text-muted-foreground/30"
)}
/>
<PixelStar filled={star <= savedStars} className="w-10 h-10" />
</div>
))}
</div>
{/* Actions */}
<div className="flex flex-col sm:flex-row gap-3 justify-center p-6 bg-white/50 dark:bg-card/50 border-t">
{/* Actions - pixel style */}
<div className="flex flex-col sm:flex-row gap-3 justify-center p-4 bg-[#4A3728] border-t-4 border-[#8B4513]">
{next ? (
<Button asChild size="lg" className="rounded-full gap-2">
<Link href={`/kids/level/${next.slug}`}>
<Link
href={`/kids/level/${next.slug}`}
className="pixel-btn pixel-btn-green px-8 py-3 text-xl text-center"
>
<span className="flex items-center justify-center gap-2">
{t("levelComplete.nextLevel")}
<ArrowRight className="h-5 w-5" />
</Link>
</Button>
<PixelArrowRight />
</span>
</Link>
) : (
<Button asChild size="lg" className="rounded-full gap-2">
<Link href="/kids/map">
{t("levelComplete.allDone")}
<Map className="h-5 w-5" />
</Link>
</Button>
<Link
href="/kids/map"
className="pixel-btn pixel-btn-green px-8 py-3 text-xl text-center"
>
{t("levelComplete.allDone")}
</Link>
)}
<Button variant="outline" asChild size="lg" className="rounded-full gap-2">
<Link href="/kids/map">
<Map className="h-5 w-5" />
<Link
href="/kids/map"
className="pixel-btn pixel-btn-amber px-8 py-3 text-xl text-center"
>
<span className="flex items-center justify-center gap-2">
<PixelMapIcon />
{t("levelComplete.backToMap")}
</Link>
</Button>
</span>
</Link>
</div>
</div>
</div>
);
}
function PixelArrowRight() {
return (
<svg viewBox="0 0 12 12" className="w-4 h-4" style={{ imageRendering: "pixelated" }}>
<rect x="2" y="5" width="6" height="2" fill="currentColor" />
<rect x="8" y="5" width="2" height="2" fill="currentColor" />
<rect x="6" y="3" width="2" height="2" fill="currentColor" />
<rect x="6" y="7" width="2" height="2" fill="currentColor" />
</svg>
);
}
function PixelMapIcon() {
return (
<svg viewBox="0 0 16 16" className="w-4 h-4" style={{ imageRendering: "pixelated" }}>
{/* Pin head - circle */}
<rect x="5" y="1" width="6" height="2" fill="currentColor" />
<rect x="4" y="2" width="8" height="2" fill="currentColor" />
<rect x="3" y="3" width="10" height="4" fill="currentColor" />
<rect x="4" y="7" width="8" height="2" fill="currentColor" />
<rect x="5" y="9" width="6" height="2" fill="currentColor" />
{/* Pin point */}
<rect x="6" y="11" width="4" height="2" fill="currentColor" />
<rect x="7" y="13" width="2" height="2" fill="currentColor" />
{/* Inner highlight */}
<rect x="5" y="4" width="2" height="2" fill="rgba(255,255,255,0.4)" />
</svg>
);
}

View File

@@ -0,0 +1,134 @@
"use client";
import { useState, Children, isValidElement, ReactNode, ReactElement } from "react";
import { ChevronLeft, ChevronRight, Map } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import Link from "next/link";
import { useTranslations } from "next-intl";
interface SectionProps {
children: ReactNode;
}
export function Section({ children }: SectionProps) {
return <>{children}</>;
}
interface LevelSlidesProps {
children: ReactNode;
levelSlug: string;
}
export function LevelSlides({ children, levelSlug }: LevelSlidesProps) {
const t = useTranslations("kids");
const [currentSection, setCurrentSection] = useState(0);
// Extract Section components from children
const sections: ReactElement[] = [];
Children.forEach(children, (child) => {
if (isValidElement(child) && child.type === Section) {
sections.push(child);
}
});
// If no sections found, wrap all content in one section
if (sections.length === 0) {
sections.push(<Section key="default">{children}</Section>);
}
const totalSections = sections.length;
const isFirstSection = currentSection === 0;
const isLastSection = currentSection === totalSections - 1;
const goToNext = () => {
if (!isLastSection) {
setCurrentSection((prev) => prev + 1);
}
};
const goToPrev = () => {
if (!isFirstSection) {
setCurrentSection((prev) => prev - 1);
}
};
return (
<div className="h-full flex flex-col">
{/* Content area */}
<div className="flex-1 overflow-hidden flex items-center justify-center p-4">
<div className="w-full max-w-2xl h-full flex flex-col justify-center">
<div
key={currentSection}
className="animate-in fade-in slide-in-from-right-4 duration-300 prose max-w-none kids-prose"
>
{sections[currentSection]}
</div>
</div>
</div>
{/* Navigation footer */}
<div className="shrink-0 border-t bg-white/50 dark:bg-background/50 backdrop-blur">
<div className="container py-4 flex items-center justify-between">
{/* Back button */}
<Button
variant="ghost"
size="lg"
onClick={goToPrev}
disabled={isFirstSection}
className={cn(
"rounded-full px-6 transition-opacity",
isFirstSection && "opacity-0 pointer-events-none"
)}
>
<ChevronLeft className="h-5 w-5 mr-1" />
{t("navigation.back")}
</Button>
{/* Progress indicators */}
<div className="flex items-center gap-2">
{Array.from({ length: totalSections }).map((_, i) => (
<button
key={i}
onClick={() => setCurrentSection(i)}
className={cn(
"w-3 h-3 rounded-full transition-all",
i === currentSection
? "bg-primary w-8"
: i < currentSection
? "bg-primary/50"
: "bg-muted-foreground/30 hover:bg-muted-foreground/50"
)}
aria-label={`Go to section ${i + 1}`}
/>
))}
</div>
{/* Next button or Map link */}
{!isLastSection ? (
<Button
size="lg"
onClick={goToNext}
className="rounded-full px-6"
>
{t("navigation.next")}
<ChevronRight className="h-5 w-5 ml-1" />
</Button>
) : (
<Button
asChild
variant="outline"
size="lg"
className="rounded-full px-6"
>
<Link href="/kids/map">
<Map className="h-5 w-5 mr-1" />
{t("level.map")}
</Link>
</Button>
)}
</div>
</div>
</div>
);
}

View File

@@ -1,55 +1,111 @@
"use client";
import { useState, useCallback } from "react";
import { useState, useCallback, useEffect, useId } from "react";
import { useTranslations } from "next-intl";
import { Check, RefreshCw, Sparkles, GripVertical } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { useLevelSlug } from "@/components/kids/providers/level-context";
import { getComponentState, saveComponentState } from "@/lib/kids/progress";
interface BlankConfig {
id: string;
id?: string;
hint: string;
answers: string[]; // Words to choose from (first one is correct)
answers: string[]; // Words to choose from (all are correct)
emoji?: string;
}
interface MagicWordsProps {
title?: string;
sentence: string; // Use {{id}} for blanks
sentence: string; // Use _ for blanks
blanks: BlankConfig[];
successMessage?: string;
}
interface SavedState {
placements: Record<string, string>;
submitted: boolean;
availableWords: { word: string; blankId: string }[];
}
export function MagicWords({
title = "Drag the magic words! ✨",
title,
sentence,
blanks,
successMessage = "Amazing! You created a great prompt!",
successMessage,
}: MagicWordsProps) {
const t = useTranslations("kids.magicWords");
const levelSlug = useLevelSlug();
const componentId = useId();
const displayTitle = title || t("title");
const [placements, setPlacements] = useState<Record<string, string>>({});
const [submitted, setSubmitted] = useState(false);
const [draggedWord, setDraggedWord] = useState<string | null>(null);
const [availableWords, setAvailableWords] = useState<{ word: string; blankId: string }[]>([]);
const [isLoaded, setIsLoaded] = useState(false);
// Get all available words from blanks (shuffled)
const [availableWords] = useState(() => {
const words = blanks.flatMap((blank) =>
blank.answers.map((answer) => ({ word: answer, blankId: blank.id }))
);
// Shuffle
for (let i = words.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[words[i], words[j]] = [words[j], words[i]];
// Generate IDs for blanks if not provided
const blanksWithIds = blanks.map((blank, index) => ({
...blank,
id: blank.id || `blank-${index}`,
}));
// Load saved state on mount
useEffect(() => {
const shuffleWords = () => {
const words = blanksWithIds.flatMap((blank) =>
blank.answers.map((answer) => ({ word: answer, blankId: blank.id! }))
);
for (let i = words.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[words[i], words[j]] = [words[j], words[i]];
}
return words;
};
if (!levelSlug) {
setPlacements({});
setAvailableWords(shuffleWords());
setIsLoaded(true);
return;
}
return words;
});
const saved = getComponentState<SavedState>(levelSlug, componentId);
if (saved && saved.placements && saved.availableWords && saved.availableWords.length > 0) {
setPlacements(saved.placements);
setSubmitted(saved.submitted || false);
setAvailableWords(saved.availableWords);
} else {
setPlacements({});
setAvailableWords(shuffleWords());
}
setIsLoaded(true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [levelSlug, componentId]);
// Save state when it changes
useEffect(() => {
if (!levelSlug || !isLoaded || availableWords.length === 0) return;
saveComponentState<SavedState>(levelSlug, componentId, {
placements,
submitted,
availableWords,
});
}, [levelSlug, componentId, placements, submitted, availableWords, isLoaded]);
const checkAnswer = useCallback((blankId: string, value: string): boolean => {
const blank = blanks.find((b) => b.id === blankId);
const blank = blanksWithIds.find((b) => b.id === blankId);
if (!blank) return false;
return blank.answers.some((answer) => answer.toLowerCase() === value.toLowerCase());
}, [blanks]);
}, [blanksWithIds]);
const allCorrect = submitted && blanks.every((blank) => checkAnswer(blank.id, placements[blank.id] || ""));
const score = blanks.filter((blank) => checkAnswer(blank.id, placements[blank.id] || "")).length;
// Don't render until loaded to prevent hydration mismatch
if (!isLoaded) return null;
const allCorrect = submitted && blanksWithIds.every((blank) => checkAnswer(blank.id, placements[blank.id] || ""));
const score = blanksWithIds.filter((blank) => checkAnswer(blank.id, placements[blank.id] || "")).length;
const usedWords = Object.values(placements);
@@ -80,7 +136,7 @@ export function MagicWords({
if (submitted) return;
// Find first empty blank
const emptyBlank = blanks.find((blank) => !placements[blank.id]);
const emptyBlank = blanksWithIds.find((blank) => !placements[blank.id]);
if (emptyBlank) {
// Remove word from previous placement
const newPlacements = { ...placements };
@@ -113,36 +169,42 @@ export function MagicWords({
// Parse sentence and render with drop zones
const renderSentence = () => {
const parts = sentence.split(/(\{\{[^}]+\}\})/g);
// Split by underscore placeholders
const parts = sentence.split(/(_+)/g);
let blankIndex = 0;
return parts.map((part, index) => {
const match = part.match(/\{\{([^}]+)\}\}/);
if (match) {
const blankId = match[1];
const blank = blanks.find((b) => b.id === blankId);
// Check if this is a blank placeholder (one or more underscores)
if (/^_+$/.test(part)) {
const blank = blanksWithIds[blankIndex];
if (!blank) return <span key={index}>{part}</span>;
const blankId = blank.id;
const placedWord = placements[blankId];
const isCorrect = submitted && checkAnswer(blankId, placedWord || "");
const isWrong = submitted && !isCorrect;
blankIndex++;
return (
<span key={index} className="inline-flex items-center gap-1 mx-1 my-1">
{blank?.emoji && <span className="text-lg">{blank.emoji}</span>}
<span
onClick={() => placedWord && handleClickBlank(blankId)}
onDragOver={(e) => e.preventDefault()}
onDrop={() => handleDrop(blankId)}
className={cn(
"px-3 py-2 border-2 border-dashed rounded-xl min-w-[100px] text-center font-medium transition-all cursor-pointer",
!placedWord && "bg-white dark:bg-card border-primary/50",
placedWord && !submitted && "bg-primary/10 border-primary border-solid",
isCorrect && "border-green-500 border-solid bg-green-50 dark:bg-green-950/30",
isWrong && "border-red-400 border-solid bg-red-50 dark:bg-red-950/30",
draggedWord && !placedWord && "border-primary bg-primary/5 scale-105"
"px-3 py-2 border-2 border-dashed rounded-lg min-w-[100px] text-xl text-center font-medium transition-all cursor-pointer text-[#2C1810]",
!placedWord && "bg-white border-purple-400",
placedWord && !submitted && "bg-purple-100 border-purple-500 border-solid",
isCorrect && "border-green-500 border-solid bg-green-50",
isWrong && "border-red-400 border-solid bg-red-50",
draggedWord && !placedWord && "border-purple-500 bg-purple-50 scale-105"
)}
title={blank.hint}
>
{placedWord || "___"}
{placedWord || blank.hint}
</span>
{submitted && isCorrect && <Check className="h-5 w-5 text-green-500" />}
{submitted && isCorrect && <Check className="h-6 w-6 text-green-500" />}
</span>
);
}
@@ -151,25 +213,25 @@ export function MagicWords({
};
return (
<div className="my-6 rounded-2xl border-2 border-purple-200 dark:border-purple-800 overflow-hidden">
<div className="my-4 rounded-xl border-4 border-purple-300 overflow-hidden bg-white">
{/* Header */}
<div className="px-4 py-3 bg-gradient-to-r from-purple-100 to-pink-100 dark:from-purple-950/50 dark:to-pink-950/50 border-b border-purple-200 dark:border-purple-800">
<div className="px-4 py-3 bg-gradient-to-r from-purple-100 to-pink-100 border-b-2 border-purple-200">
<div className="flex items-center gap-2">
<Sparkles className="h-5 w-5 text-purple-500" />
<span className="font-semibold">{title}</span>
<Sparkles className="h-6 w-6 text-purple-500" />
<span className="font-bold text-2xl text-[#2C1810]">{displayTitle}</span>
</div>
</div>
<div className="p-4 space-y-4">
{/* Sentence with blanks */}
<div className="text-lg leading-loose p-4 bg-muted/30 rounded-xl flex flex-wrap items-center">
<div className="text-xl leading-relaxed p-4 bg-purple-50 rounded-lg flex flex-wrap items-center text-[#2C1810]">
{renderSentence()}
</div>
{/* Word bank */}
<div className="p-4 bg-purple-50 dark:bg-purple-950/30 rounded-xl">
<p className="text-sm font-medium text-purple-700 dark:text-purple-300 mb-3">
🎯 Drag or tap words to fill the blanks:
<div className="p-4 bg-purple-100 rounded-lg">
<p className="text-lg font-medium text-purple-700 mb-3 m-0">
{t("dragOrTap")}
</p>
<div className="flex flex-wrap gap-2">
{availableWords.map(({ word }, index) => {
@@ -180,16 +242,18 @@ export function MagicWords({
draggable={!submitted && !isUsed}
onDragStart={() => handleDragStart(word)}
onDragEnd={handleDragEnd}
onTouchStart={() => !isUsed && !submitted && handleDragStart(word)}
onTouchEnd={() => handleDragEnd()}
onClick={() => !isUsed && handleClickWord(word)}
disabled={submitted || isUsed}
className={cn(
"flex items-center gap-1 px-3 py-2 rounded-lg border-2 font-medium transition-all",
!isUsed && !submitted && "bg-white dark:bg-card border-purple-300 hover:border-purple-500 hover:shadow-md cursor-grab active:cursor-grabbing",
isUsed && "bg-muted/50 border-muted text-muted-foreground opacity-50 cursor-not-allowed",
"flex items-center gap-2 px-4 py-3 rounded-lg border-2 text-xl font-medium transition-all text-[#2C1810]",
!isUsed && !submitted && "bg-white border-purple-300 hover:border-purple-500 hover:shadow-md cursor-grab active:cursor-grabbing",
isUsed && "bg-gray-100 border-gray-300 text-gray-400 opacity-50 cursor-not-allowed",
submitted && "cursor-default"
)}
>
{!submitted && !isUsed && <GripVertical className="h-4 w-4 text-muted-foreground" />}
{!submitted && !isUsed && <GripVertical className="h-5 w-5 text-purple-400" />}
{word}
</button>
);
@@ -201,45 +265,37 @@ export function MagicWords({
{submitted && (
<div
className={cn(
"p-4 rounded-xl text-center",
"p-4 rounded-lg text-center",
allCorrect
? "bg-green-100 dark:bg-green-950/50 border border-green-300 dark:border-green-800"
: "bg-amber-100 dark:bg-amber-950/50 border border-amber-300 dark:border-amber-800"
? "bg-green-100 border-2 border-green-300"
: "bg-amber-100 border-2 border-amber-300"
)}
>
{allCorrect ? (
<>
<p className="text-2xl mb-2">🎉</p>
<p className="font-semibold text-lg m-0">{successMessage}</p>
</>
<p className="font-bold text-xl m-0 text-green-800">🎉 {successMessage || "Amazing!"}</p>
) : (
<>
<p className="font-semibold m-0">
{score} of {blanks.length} correct!
</p>
<p className="text-sm text-muted-foreground m-0 mt-1">
Try different words!
</p>
</>
<p className="font-bold text-lg m-0 text-amber-800">
{score} / {blanksWithIds.length} {t("correct")}! {t("tryAgain")}
</p>
)}
</div>
)}
{/* Actions */}
<div className="flex gap-2">
<div className="flex gap-3">
{!submitted ? (
<Button
onClick={handleSubmit}
className="rounded-full"
disabled={Object.keys(placements).length < blanks.length}
className="rounded-full h-12 text-xl px-6"
disabled={Object.keys(placements).length < blanksWithIds.length}
>
<Check className="h-4 w-4 mr-1" />
Check my words!
<Check className="h-5 w-5 mr-2" />
{t("check")}
</Button>
) : (
<Button onClick={handleReset} variant="outline" className="rounded-full">
<RefreshCw className="h-4 w-4 mr-1" />
Try again
<Button onClick={handleReset} variant="outline" className="rounded-full h-12 text-xl px-6">
<RefreshCw className="h-5 w-5 mr-2" />
{t("retry")}
</Button>
)}
</div>

View File

@@ -0,0 +1,287 @@
"use client";
import { cn } from "@/lib/utils";
// Pixel Art Tree (Pine)
export function PixelTree({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 16 24"
className={cn("w-8 h-12", className)}
style={{ imageRendering: "pixelated" }}
>
{/* Tree top */}
<rect x="6" y="0" width="4" height="2" fill="#228B22" />
<rect x="4" y="2" width="8" height="2" fill="#228B22" />
<rect x="2" y="4" width="12" height="2" fill="#2E8B2E" />
<rect x="4" y="6" width="8" height="2" fill="#228B22" />
<rect x="2" y="8" width="12" height="2" fill="#2E8B2E" />
<rect x="0" y="10" width="16" height="2" fill="#228B22" />
<rect x="2" y="12" width="12" height="2" fill="#2E8B2E" />
<rect x="0" y="14" width="16" height="2" fill="#1E6B1E" />
{/* Trunk */}
<rect x="6" y="16" width="4" height="8" fill="#8B4513" />
</svg>
);
}
// Pixel Art Bush
export function PixelBush({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 16 10"
className={cn("w-8 h-5", className)}
style={{ imageRendering: "pixelated" }}
>
<rect x="4" y="0" width="8" height="2" fill="#32CD32" />
<rect x="2" y="2" width="12" height="2" fill="#228B22" />
<rect x="0" y="4" width="16" height="4" fill="#2E8B2E" />
<rect x="2" y="8" width="12" height="2" fill="#1E6B1E" />
</svg>
);
}
// Pixel Art Cloud
export function PixelCloud({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 24 12"
className={cn("w-12 h-6", className)}
style={{ imageRendering: "pixelated" }}
>
<rect x="4" y="0" width="8" height="2" fill="white" />
<rect x="2" y="2" width="14" height="2" fill="white" />
<rect x="0" y="4" width="20" height="4" fill="white" />
<rect x="14" y="2" width="6" height="2" fill="white" />
<rect x="16" y="4" width="8" height="4" fill="white" />
<rect x="2" y="8" width="20" height="2" fill="#f0f0f0" />
<rect x="4" y="10" width="16" height="2" fill="#e8e8e8" />
</svg>
);
}
// Pixel Art Castle
export function PixelCastle({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 32 32"
className={cn("w-16 h-16", className)}
style={{ imageRendering: "pixelated" }}
>
{/* Towers */}
<rect x="0" y="4" width="6" height="4" fill="#808080" />
<rect x="0" y="0" width="2" height="4" fill="#696969" />
<rect x="4" y="0" width="2" height="4" fill="#696969" />
<rect x="26" y="4" width="6" height="4" fill="#808080" />
<rect x="26" y="0" width="2" height="4" fill="#696969" />
<rect x="30" y="0" width="2" height="4" fill="#696969" />
{/* Main body */}
<rect x="0" y="8" width="32" height="16" fill="#A0A0A0" />
<rect x="4" y="8" width="24" height="4" fill="#888888" />
{/* Windows */}
<rect x="6" y="14" width="4" height="4" fill="#4169E1" />
<rect x="22" y="14" width="4" height="4" fill="#4169E1" />
{/* Door */}
<rect x="12" y="16" width="8" height="8" fill="#8B4513" />
<rect x="14" y="18" width="4" height="6" fill="#654321" />
{/* Flag */}
<rect x="15" y="4" width="2" height="8" fill="#654321" />
<rect x="17" y="4" width="6" height="4" fill="#FF4444" />
{/* Base */}
<rect x="0" y="24" width="32" height="8" fill="#696969" />
</svg>
);
}
// Pixel Art Mountain
export function PixelMountain({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 24 16"
className={cn("w-12 h-8", className)}
style={{ imageRendering: "pixelated" }}
>
{/* Snow cap */}
<rect x="10" y="0" width="4" height="2" fill="white" />
<rect x="8" y="2" width="8" height="2" fill="white" />
{/* Mountain body */}
<rect x="6" y="4" width="12" height="2" fill="#808080" />
<rect x="4" y="6" width="16" height="2" fill="#696969" />
<rect x="2" y="8" width="20" height="2" fill="#606060" />
<rect x="0" y="10" width="24" height="6" fill="#505050" />
</svg>
);
}
// Pixel Art Flower
export function PixelFlower({ className, color = "#FF69B4" }: { className?: string; color?: string }) {
return (
<svg
viewBox="0 0 8 12"
className={cn("w-4 h-6", className)}
style={{ imageRendering: "pixelated" }}
>
{/* Petals */}
<rect x="2" y="0" width="4" height="2" fill={color} />
<rect x="0" y="2" width="2" height="4" fill={color} />
<rect x="6" y="2" width="2" height="4" fill={color} />
<rect x="2" y="6" width="4" height="2" fill={color} />
{/* Center */}
<rect x="2" y="2" width="4" height="4" fill="#FFD700" />
{/* Stem */}
<rect x="3" y="8" width="2" height="4" fill="#228B22" />
</svg>
);
}
// Pixel Art Star
export function PixelStar({ className, filled = false }: { className?: string; filled?: boolean }) {
const color = filled ? "#FFD700" : "#D3D3D3";
return (
<svg
viewBox="0 0 12 12"
className={cn("w-4 h-4", className)}
style={{ imageRendering: "pixelated" }}
>
<rect x="5" y="0" width="2" height="2" fill={color} />
<rect x="4" y="2" width="4" height="2" fill={color} />
<rect x="0" y="4" width="12" height="2" fill={color} />
<rect x="2" y="6" width="8" height="2" fill={color} />
<rect x="2" y="8" width="2" height="2" fill={color} />
<rect x="8" y="8" width="2" height="2" fill={color} />
<rect x="0" y="10" width="2" height="2" fill={color} />
<rect x="10" y="10" width="2" height="2" fill={color} />
</svg>
);
}
// Pixel Art Robot (Promi)
export function PixelRobot({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 16 20"
className={cn("w-8 h-10", className)}
style={{ imageRendering: "pixelated" }}
>
{/* Antenna */}
<rect x="7" y="0" width="2" height="2" fill="#FFD700" />
<rect x="6" y="2" width="4" height="2" fill="#C0C0C0" />
{/* Head */}
<rect x="2" y="4" width="12" height="8" fill="#4A90D9" />
<rect x="4" y="6" width="3" height="3" fill="white" />
<rect x="9" y="6" width="3" height="3" fill="white" />
<rect x="5" y="7" width="2" height="2" fill="#333" />
<rect x="10" y="7" width="2" height="2" fill="#333" />
<rect x="6" y="10" width="4" height="2" fill="#333" />
{/* Body */}
<rect x="4" y="12" width="8" height="6" fill="#4A90D9" />
<rect x="6" y="14" width="4" height="2" fill="#FFD700" />
{/* Arms */}
<rect x="0" y="12" width="4" height="2" fill="#4A90D9" />
<rect x="12" y="12" width="4" height="2" fill="#4A90D9" />
{/* Feet */}
<rect x="4" y="18" width="3" height="2" fill="#333" />
<rect x="9" y="18" width="3" height="2" fill="#333" />
</svg>
);
}
// Pixel Art Level Node
export function PixelLevelNode({
state,
levelNumber,
className
}: {
state: "locked" | "available" | "completed";
levelNumber: string;
className?: string;
}) {
const bgColor = state === "completed" ? "#22C55E" : state === "available" ? "#3B82F6" : "#6B7280";
const borderColor = state === "completed" ? "#16A34A" : state === "available" ? "#2563EB" : "#4B5563";
const glowColor = state === "available" ? "rgba(59, 130, 246, 0.4)" : state === "completed" ? "rgba(34, 197, 94, 0.3)" : "transparent";
return (
<div className={cn("relative", className)}>
{/* Glow effect */}
{state !== "locked" && (
<div
className="absolute inset-0 -m-2 rounded-lg animate-pulse"
style={{ backgroundColor: glowColor }}
/>
)}
<svg
viewBox="0 0 24 24"
className="w-16 h-16 md:w-20 md:h-20"
style={{ imageRendering: "pixelated" }}
>
{/* Outer border */}
<rect x="2" y="0" width="20" height="2" fill={borderColor} />
<rect x="0" y="2" width="2" height="20" fill={borderColor} />
<rect x="22" y="2" width="2" height="20" fill={borderColor} />
<rect x="2" y="22" width="20" height="2" fill={borderColor} />
{/* Inner fill */}
<rect x="2" y="2" width="20" height="20" fill={bgColor} />
{/* Icon based on state */}
{state === "locked" && (
<>
{/* Lock icon */}
<rect x="9" y="8" width="6" height="2" fill="#333" />
<rect x="8" y="10" width="8" height="8" fill="#333" />
<rect x="10" y="12" width="4" height="4" fill="#666" />
</>
)}
{state === "available" && (
<>
{/* Play icon */}
<rect x="9" y="7" width="2" height="10" fill="white" />
<rect x="11" y="9" width="2" height="6" fill="white" />
<rect x="13" y="11" width="2" height="2" fill="white" />
</>
)}
{state === "completed" && (
<>
{/* Checkmark */}
<rect x="6" y="12" width="2" height="4" fill="white" />
<rect x="8" y="14" width="2" height="2" fill="white" />
<rect x="10" y="12" width="2" height="4" fill="white" />
<rect x="12" y="10" width="2" height="4" fill="white" />
<rect x="14" y="8" width="2" height="4" fill="white" />
<rect x="16" y="6" width="2" height="4" fill="white" />
</>
)}
</svg>
{/* Level number badge */}
<div
className="absolute -top-2 -right-2 w-8 h-8 flex items-center justify-center text-sm font-bold bg-amber-400 text-amber-900 shadow-md"
style={{
clipPath: "polygon(10% 0%, 90% 0%, 100% 10%, 100% 90%, 90% 100%, 10% 100%, 0% 90%, 0% 10%)",
}}
>
{levelNumber}
</div>
</div>
);
}
// Pixel Art Path Segment
export function PixelPath({ width = 100, className }: { width?: number; className?: string }) {
const segments = Math.ceil(width / 8);
return (
<svg
viewBox={`0 0 ${width} 8`}
className={cn("h-2", className)}
style={{ width: `${width}px`, imageRendering: "pixelated" }}
>
{Array.from({ length: segments }).map((_, i) => (
<rect
key={i}
x={i * 8}
y="2"
width="6"
height="4"
fill={i % 2 === 0 ? "#D4A574" : "#C4956A"}
/>
))}
</svg>
);
}

View File

@@ -1,99 +1,266 @@
"use client";
import { useEffect, useState } from "react";
import { useEffect, useState, useRef } from "react";
import Link from "next/link";
import { useTranslations } from "next-intl";
import { Star, Lock, Check, Play } from "lucide-react";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { worlds, type Level } from "@/lib/kids/levels";
import { worlds, type Level, getAllLevels } from "@/lib/kids/levels";
import { getProgress, isLevelUnlocked, type KidsProgress } from "@/lib/kids/progress";
import { analyticsKids } from "@/lib/analytics";
import { Button } from "@/components/ui/button";
import {
PixelTree,
PixelBush,
PixelCastle,
PixelMountain,
PixelFlower,
PixelStar,
PixelRobot,
PixelLevelNode
} from "./pixel-art";
export function ProgressMap() {
const t = useTranslations("kids");
const [progress, setProgress] = useState<KidsProgress | null>(null);
const scrollRef = useRef<HTMLDivElement>(null);
const allLevels = getAllLevels();
useEffect(() => {
setProgress(getProgress());
analyticsKids.viewMap();
}, []);
return (
<div className="space-y-8">
{worlds.map((world) => (
<WorldSection key={world.number} world={world} progress={progress} t={t} />
))}
</div>
);
}
// Calculate positions for all levels in a continuous horizontal path
const levelPositions = allLevels.map((_, index) => {
const spacing = 200; // pixels between levels
const x = 120 + index * spacing;
// Create a gentle wave pattern - centered around 55%
const y = 55 + Math.sin(index * 0.8) * 15;
return { x, y };
});
interface WorldSectionProps {
world: typeof worlds[0];
progress: KidsProgress | null;
t: ReturnType<typeof useTranslations>;
}
// Calculate map width - ensure it fits content but also works on large screens
const calculatedWidth = (allLevels.length * 200) + 240;
const mapWidth = Math.max(calculatedWidth, 800);
function WorldSection({ world, progress, t }: WorldSectionProps) {
const colorClasses = {
emerald: {
bg: "bg-emerald-50 dark:bg-emerald-950/30",
border: "border-emerald-200 dark:border-emerald-800",
text: "text-emerald-700 dark:text-emerald-300",
badge: "bg-emerald-100 dark:bg-emerald-900/50",
},
blue: {
bg: "bg-blue-50 dark:bg-blue-950/30",
border: "border-blue-200 dark:border-blue-800",
text: "text-blue-700 dark:text-blue-300",
badge: "bg-blue-100 dark:bg-blue-900/50",
},
}[world.color] || {
bg: "bg-gray-50 dark:bg-gray-950/30",
border: "border-gray-200 dark:border-gray-800",
text: "text-gray-700 dark:text-gray-300",
badge: "bg-gray-100 dark:bg-gray-900/50",
const scrollLeft = () => {
scrollRef.current?.scrollBy({ left: -300, behavior: "smooth" });
};
const scrollRight = () => {
scrollRef.current?.scrollBy({ left: 300, behavior: "smooth" });
};
return (
<div className={cn("rounded-2xl border-2 p-6", colorClasses.bg, colorClasses.border)}>
{/* World Header */}
<div className="flex items-center gap-3 mb-6">
<span className="text-4xl">{world.emoji}</span>
<div>
<h2 className="text-xl font-bold">{t(`worlds.${world.number}.title`)}</h2>
<p className="text-sm text-muted-foreground">
{t("map.worldLevels", { count: world.levels.length })}
</p>
<div className="relative h-full flex flex-col">
{/* Scroll controls for desktop */}
<div className="hidden md:flex absolute left-2 top-1/2 -translate-y-1/2 z-20">
<Button variant="secondary" size="icon" onClick={scrollLeft} className="rounded-full shadow-lg">
<ChevronLeft className="h-5 w-5" />
</Button>
</div>
<div className="hidden md:flex absolute right-2 top-1/2 -translate-y-1/2 z-20">
<Button variant="secondary" size="icon" onClick={scrollRight} className="rounded-full shadow-lg">
<ChevronRight className="h-5 w-5" />
</Button>
</div>
{/* Horizontal scrolling map container */}
<div
ref={scrollRef}
className="flex-1 overflow-x-auto overflow-y-hidden scrollbar-hide flex justify-center"
style={{ scrollbarWidth: "none", msOverflowStyle: "none" }}
>
<div
className="relative h-full min-h-[400px] min-w-full"
style={{ width: `${mapWidth}px`, imageRendering: "pixelated" }}
>
{/* Sky is handled by kids layout - no additional background needed */}
{/* Pixel art ground layers - use full width (max of mapWidth or 100%) */}
<div className="absolute bottom-0 left-0 right-0 h-12 bg-[#8B5A2B]" style={{ minWidth: `${mapWidth}px` }} />
<div className="absolute bottom-12 left-0 right-0 h-4 bg-[#228B22]" style={{ minWidth: `${mapWidth}px` }} />
<div className="absolute bottom-16 left-0 right-0 h-2 bg-[#32CD32]" style={{ minWidth: `${mapWidth}px` }} />
{/* Pixel art path connecting all levels */}
<svg
className="absolute inset-0 w-full h-full pointer-events-none z-0"
style={{ width: `${mapWidth}px` }}
preserveAspectRatio="none"
>
{/* Draw dotted path between levels - pixel style */}
<path
d={generatePathD(levelPositions)}
fill="none"
stroke="#D4A574"
strokeWidth="12"
strokeLinecap="square"
strokeLinejoin="miter"
strokeDasharray="16 8"
/>
{/* Path border for depth */}
<path
d={generatePathD(levelPositions)}
fill="none"
stroke="#8B5A2B"
strokeWidth="16"
strokeLinecap="square"
strokeLinejoin="miter"
strokeDasharray="16 8"
className="opacity-30"
/>
</svg>
{/* Decorative elements - rendered after path so clouds appear on top */}
<MapDecorations mapWidth={mapWidth} />
{/* World labels - pixel art style */}
{worlds.map((world) => {
const firstLevelIndex = allLevels.findIndex(l => l.world === world.number);
if (firstLevelIndex === -1) return null;
const pos = levelPositions[firstLevelIndex];
return (
<div
key={world.number}
className="absolute z-10 pointer-events-none"
style={{ left: `${pos.x - 50}px`, top: "12px" }}
>
<div
className="px-4 py-2 bg-[#FFD700] border-3 border-[#DAA520] text-base font-bold text-[#8B4513] whitespace-nowrap"
style={{
clipPath: "polygon(4px 0%, calc(100% - 4px) 0%, 100% 4px, 100% calc(100% - 4px), calc(100% - 4px) 100%, 4px 100%, 0% calc(100% - 4px), 0% 4px)",
}}
>
{t(`worlds.${world.number}.title`)}
</div>
</div>
);
})}
{/* Level nodes */}
{allLevels.map((level, index) => (
<LevelNode
key={level.slug}
level={level}
position={levelPositions[index]}
progress={progress}
index={index}
t={t}
/>
))}
</div>
</div>
{/* Levels */}
<div className="grid sm:grid-cols-3 gap-4">
{world.levels.map((level) => (
<LevelCard
key={level.slug}
level={level}
progress={progress}
colorClasses={colorClasses}
t={t}
/>
))}
{/* Progress bar at bottom - pixel art style */}
<div className="shrink-0 px-4 py-3 bg-[#2C1810] border-t-4 border-[#8B4513]">
<div className="max-w-md mx-auto">
<div className="flex items-center justify-between text-xl mb-2">
<span className="font-bold text-2xl text-[#FFD700]">{t("map.title")}</span>
<div className="flex items-center gap-2">
<PixelStar filled className="w-6 h-6" />
<span className="text-[#FFD700] font-bold text-xl">
{getCompletedCount(progress, allLevels)} / {allLevels.length}
</span>
</div>
</div>
{/* Pixel art progress bar */}
<div className="h-6 bg-[#4A3728] border-2 border-[#8B4513] relative overflow-hidden">
<div
className="h-full bg-[#22C55E] transition-all duration-500"
style={{
width: `${(getCompletedCount(progress, allLevels) / allLevels.length) * 100}%`,
}}
/>
{/* Pixel segments overlay */}
<div className="absolute inset-0 flex">
{Array.from({ length: allLevels.length }).map((_, i) => (
<div
key={i}
className="flex-1 border-r border-[#2C1810] last:border-r-0"
/>
))}
</div>
</div>
</div>
</div>
</div>
);
}
interface LevelCardProps {
function generatePathD(positions: { x: number; y: number }[]): string {
if (positions.length === 0) return "";
let d = `M ${positions[0].x} ${positions[0].y}`;
for (let i = 1; i < positions.length; i++) {
const prev = positions[i - 1];
const curr = positions[i];
const midX = (prev.x + curr.x) / 2;
// Create smooth curves
d += ` Q ${midX} ${prev.y}, ${midX} ${(prev.y + curr.y) / 2}`;
d += ` Q ${midX} ${curr.y}, ${curr.x} ${curr.y}`;
}
return d;
}
function MapDecorations({ mapWidth }: { mapWidth: number }) {
// Generate decorations along the map - using seeded positions for consistency
const decorations = [];
const spacing = 100;
for (let i = 0; i < Math.floor(mapWidth / spacing); i++) {
const x = 50 + i * spacing;
const type = i % 5;
const yOffset = Math.sin(i * 0.8) * 10;
decorations.push(
<div
key={`deco-${i}`}
className="absolute pointer-events-none"
style={{
left: `${x + (i % 3) * 15}px`,
bottom: `${55 + yOffset + (i % 4) * 5}px`
}}
>
{type === 0 && <PixelTree />}
{type === 1 && <PixelBush />}
{type === 2 && <PixelFlower color={i % 2 === 0 ? "#FF69B4" : "#FF6347"} />}
{type === 3 && <PixelTree className="w-10 h-14" />}
{type === 4 && <PixelMountain />}
</div>
);
}
// Add castle at the end
decorations.push(
<div
key="castle"
className="absolute pointer-events-none"
style={{ left: `${mapWidth - 80}px`, bottom: "50px" }}
>
<PixelCastle />
</div>
);
return <>{decorations}</>;
}
function getCompletedCount(progress: KidsProgress | null, levels: Level[]): number {
if (!progress) return 0;
return levels.filter(l => progress.levels[l.slug]?.completed).length;
}
interface LevelNodeProps {
level: Level;
position: { x: number; y: number };
progress: KidsProgress | null;
colorClasses: {
bg: string;
border: string;
text: string;
badge: string;
};
index: number;
t: ReturnType<typeof useTranslations>;
}
function LevelCard({ level, progress, colorClasses, t }: LevelCardProps) {
function LevelNode({ level, position, progress, index, t }: LevelNodeProps) {
const [unlocked, setUnlocked] = useState(false);
const levelProgress = progress?.levels[level.slug];
const isCompleted = levelProgress?.completed;
@@ -103,65 +270,56 @@ function LevelCard({ level, progress, colorClasses, t }: LevelCardProps) {
setUnlocked(isLevelUnlocked(level.slug));
}, [level.slug, progress]);
if (!unlocked) {
return (
<div className="relative p-4 rounded-xl bg-muted/50 border-2 border-dashed border-muted-foreground/20 opacity-60">
<div className="absolute top-2 right-2">
<Lock className="h-5 w-5 text-muted-foreground" />
</div>
<div className="text-center py-4">
<p className="font-medium text-muted-foreground">{t(`levels.${level.slug.replace(/-/g, "_")}.title`)}</p>
<p className="text-xs text-muted-foreground mt-1">{t("map.locked")}</p>
const state = !unlocked ? "locked" : isCompleted ? "completed" : "available";
const nodeContent = (
<div
className={cn(
"absolute transform -translate-x-1/2 -translate-y-1/2 transition-all duration-300",
"animate-in fade-in zoom-in",
unlocked && !isCompleted && "hover:scale-110",
isCompleted && "hover:scale-110",
)}
style={{
left: `${position.x}px`,
top: `${position.y}%`,
animationDelay: `${index * 100}ms`,
}}
>
{/* Pixel art level node */}
<PixelLevelNode
state={state}
levelNumber={`${level.world}.${level.levelNumber}`}
/>
{/* Stars for completed levels */}
{isCompleted && (
<div className="absolute -bottom-1 left-1/2 -translate-x-1/2 flex gap-0">
{[1, 2, 3].map((star) => (
<PixelStar key={star} filled={star <= stars} />
))}
</div>
)}
{/* Level title label - pixel style */}
<div
className="absolute top-full mt-6 left-1/2 -translate-x-1/2 whitespace-nowrap px-3 py-2 text-base font-bold bg-amber-100 dark:bg-amber-900 border-2 border-amber-400"
style={{
clipPath: "polygon(4px 0%, calc(100% - 4px) 0%, 100% 4px, 100% calc(100% - 4px), calc(100% - 4px) 100%, 4px 100%, 0% calc(100% - 4px), 0% 4px)",
}}
>
{t(`levels.${level.slug.replace(/-/g, "_")}.title`)}
</div>
);
</div>
);
if (!unlocked) {
return <div className="group cursor-not-allowed">{nodeContent}</div>;
}
return (
<Link
href={`/kids/level/${level.slug}`}
className={cn(
"relative block p-4 rounded-xl bg-white dark:bg-card border-2 transition-all hover:scale-105 hover:shadow-lg",
isCompleted ? "border-green-400 dark:border-green-600" : "border-primary/30 hover:border-primary"
)}
>
{/* Status badge */}
<div className="absolute top-2 right-2">
{isCompleted ? (
<div className="flex items-center justify-center w-6 h-6 rounded-full bg-green-500 text-white">
<Check className="h-4 w-4" />
</div>
) : (
<div className="flex items-center justify-center w-6 h-6 rounded-full bg-primary text-primary-foreground">
<Play className="h-3 w-3" />
</div>
)}
</div>
{/* Level info */}
<div className="mb-3">
<span className={cn("text-xs font-medium px-2 py-0.5 rounded-full", colorClasses.badge, colorClasses.text)}>
{t("map.levelNumber", { number: `${level.world}-${level.levelNumber}` })}
</span>
</div>
<h3 className="font-semibold mb-1">{level.title}</h3>
<p className="text-xs text-muted-foreground line-clamp-2">{level.description}</p>
{/* Stars */}
<div className="flex items-center gap-1 mt-3">
{[1, 2, 3].map((star) => (
<Star
key={star}
className={cn(
"h-4 w-4",
star <= stars
? "fill-amber-400 text-amber-400"
: "text-muted-foreground/30"
)}
/>
))}
</div>
<Link href={`/kids/level/${level.slug}`} className="group cursor-pointer">
{nodeContent}
</Link>
);
}

View File

@@ -0,0 +1,208 @@
"use client";
import { useState, useEffect, useId } from "react";
import { useTranslations } from "next-intl";
import { cn } from "@/lib/utils";
import { useLevelSlug } from "@/components/kids/providers/level-context";
import { getComponentState, saveComponentState } from "@/lib/kids/progress";
interface Problem {
issue: string;
symptom: string;
fix: string;
}
interface PromptDoctorProps {
title?: string;
brokenPrompt: string;
problems: Problem[];
healedPrompt: string;
successMessage?: string;
}
interface SavedState {
fixedProblems: number[];
isHealed: boolean;
}
export function PromptDoctor({
title,
brokenPrompt,
problems,
healedPrompt,
successMessage,
}: PromptDoctorProps) {
const t = useTranslations("kids.promptDoctor");
const levelSlug = useLevelSlug();
const componentId = useId();
const displayTitle = title || t("title");
const [fixedProblems, setFixedProblems] = useState<number[]>([]);
const [isHealed, setIsHealed] = useState(false);
const [isLoaded, setIsLoaded] = useState(false);
// Load saved state
useEffect(() => {
if (!levelSlug) {
setIsLoaded(true);
return;
}
const saved = getComponentState<SavedState>(levelSlug, componentId);
if (saved && saved.fixedProblems) {
setFixedProblems(saved.fixedProblems);
setIsHealed(saved.isHealed || false);
}
setIsLoaded(true);
}, [levelSlug, componentId]);
// Save state
useEffect(() => {
if (!levelSlug || !isLoaded) return;
saveComponentState<SavedState>(levelSlug, componentId, {
fixedProblems,
isHealed,
});
}, [levelSlug, componentId, fixedProblems, isHealed, isLoaded]);
if (!isLoaded) return null;
const handleFixProblem = (index: number) => {
if (fixedProblems.includes(index) || isHealed) return;
const newFixed = [...fixedProblems, index];
setFixedProblems(newFixed);
if (newFixed.length === problems.length) {
setIsHealed(true);
}
};
const handleReset = () => {
setFixedProblems([]);
setIsHealed(false);
};
// Calculate current prompt based on fixes applied
const getCurrentPrompt = () => {
if (isHealed) return healedPrompt;
let currentPrompt = brokenPrompt;
// Apply fixes in order
problems.forEach((problem, index) => {
if (fixedProblems.includes(index)) {
// This is simplified - in reality you'd need smarter text manipulation
currentPrompt = problem.fix;
}
});
// Return the last applied fix or broken prompt
if (fixedProblems.length > 0) {
return problems[fixedProblems[fixedProblems.length - 1]].fix;
}
return brokenPrompt;
};
const healthPercentage = (fixedProblems.length / problems.length) * 100;
return (
<div className="my-4 p-4 bg-gradient-to-br from-[#FEE2E2] to-[#FECACA] border-4 border-[#EF4444] rounded-xl">
{/* Title */}
<h3 className="text-xl font-bold text-[#DC2626] mb-4 flex items-center gap-2">
🏥 {displayTitle}
</h3>
{/* Health bar */}
<div className="mb-4">
<div className="flex items-center justify-between mb-1">
<span className="text-sm font-medium text-[#DC2626]">{t("health")}</span>
<span className="text-sm font-medium text-[#DC2626]">{Math.round(healthPercentage)}%</span>
</div>
<div className="h-4 bg-red-200 rounded-full overflow-hidden border-2 border-[#EF4444]">
<div
className={cn(
"h-full transition-all duration-500",
healthPercentage < 50 ? "bg-red-500" : healthPercentage < 100 ? "bg-yellow-500" : "bg-green-500"
)}
style={{ width: `${healthPercentage}%` }}
/>
</div>
</div>
{/* Current prompt display */}
<div className={cn(
"p-4 rounded-lg border-4 mb-4 transition-all duration-300",
isHealed
? "bg-green-100 border-green-500"
: "bg-white border-red-300"
)}>
<div className="flex items-center gap-2 mb-2">
{isHealed ? (
<span className="text-2xl">💚</span>
) : (
<span className="text-2xl animate-pulse">🤒</span>
)}
<span className={cn(
"font-bold",
isHealed ? "text-green-700" : "text-red-600"
)}>
{isHealed ? t("healthy") : t("sick")}
</span>
</div>
<p className="text-lg font-medium text-[#2C1810] m-0">
"{getCurrentPrompt()}"
</p>
</div>
{/* Problems to fix */}
{!isHealed && (
<div className="space-y-2 mb-4">
<div className="text-sm font-medium text-[#DC2626] mb-2">{t("diagnose")}</div>
{problems.map((problem, index) => {
const isFixed = fixedProblems.includes(index);
return (
<button
key={index}
onClick={() => handleFixProblem(index)}
disabled={isFixed}
className={cn(
"w-full p-3 rounded-lg border-2 text-left transition-all",
isFixed
? "bg-green-100 border-green-400 opacity-60"
: "bg-white border-red-300 hover:border-red-500 hover:bg-red-50 cursor-pointer"
)}
>
<div className="flex items-center gap-2">
<span className="text-xl">{isFixed ? "✅" : "💊"}</span>
<div>
<div className="font-bold text-[#DC2626]">{problem.issue}</div>
<div className="text-sm text-[#5D4037]">{problem.symptom}</div>
</div>
</div>
</button>
);
})}
</div>
)}
{/* Success message */}
{isHealed && (
<div className="p-4 bg-green-100 border-2 border-green-500 rounded-lg mb-4 animate-in fade-in zoom-in-95 duration-300">
<p className="font-bold text-green-700 text-lg m-0">
{successMessage || t("success")}
</p>
</div>
)}
{/* Reset button */}
{(isHealed || fixedProblems.length > 0) && (
<button
onClick={handleReset}
className="px-6 py-2 rounded-lg font-bold bg-[#DC2626] hover:bg-[#B91C1C] text-white"
>
{t("retry")}
</button>
)}
</div>
);
}

View File

@@ -0,0 +1,203 @@
"use client";
import { useState, useEffect, useId } from "react";
import { useTranslations } from "next-intl";
import { cn } from "@/lib/utils";
import { useLevelSlug } from "@/components/kids/providers/level-context";
import { getComponentState, saveComponentState } from "@/lib/kids/progress";
interface Improvement {
label: string;
prompt: string;
response: string;
}
interface PromptLabProps {
title?: string;
scenario: string;
basePrompt: string;
baseResponse: string;
improvements: Improvement[];
successMessage?: string;
}
interface SavedState {
appliedImprovements: number[];
completed: boolean;
}
export function PromptLab({
title,
scenario,
basePrompt,
baseResponse,
improvements,
successMessage,
}: PromptLabProps) {
const t = useTranslations("kids.promptLab");
const levelSlug = useLevelSlug();
const componentId = useId();
const displayTitle = title || t("title");
const [appliedImprovements, setAppliedImprovements] = useState<number[]>([]);
const [completed, setCompleted] = useState(false);
const [isLoaded, setIsLoaded] = useState(false);
// Load saved state
useEffect(() => {
if (!levelSlug) {
setIsLoaded(true);
return;
}
const saved = getComponentState<SavedState>(levelSlug, componentId);
if (saved && saved.appliedImprovements && Array.isArray(saved.appliedImprovements)) {
setAppliedImprovements(saved.appliedImprovements);
setCompleted(saved.completed || false);
}
setIsLoaded(true);
}, [levelSlug, componentId]);
// Save state
useEffect(() => {
if (!levelSlug || !isLoaded) return;
saveComponentState<SavedState>(levelSlug, componentId, {
appliedImprovements,
completed,
});
}, [levelSlug, componentId, appliedImprovements, completed, isLoaded]);
if (!isLoaded) return null;
const handleApplyImprovement = (index: number) => {
if (appliedImprovements.includes(index) || completed) return;
const newApplied = [...appliedImprovements, index];
setAppliedImprovements(newApplied);
if (newApplied.length === improvements.length) {
setCompleted(true);
}
};
const handleReset = () => {
setAppliedImprovements([]);
setCompleted(false);
};
// Get current prompt based on applied improvements
const getCurrentPrompt = () => {
if (appliedImprovements.length === 0) return basePrompt;
// Return the prompt from the highest applied improvement
const maxApplied = Math.max(...appliedImprovements);
return improvements[maxApplied].prompt;
};
// Get current response based on improvements
const getCurrentResponse = () => {
if (appliedImprovements.length === 0) return baseResponse;
// Return the response from the highest applied improvement
const maxApplied = Math.max(...appliedImprovements);
return improvements[maxApplied].response;
};
const progressPercentage = (appliedImprovements.length / improvements.length) * 100;
return (
<div className="my-4 p-4 bg-gradient-to-br from-[#D1FAE5] to-[#A7F3D0] border-4 border-[#10B981] rounded-xl">
{/* Title */}
<h3 className="text-xl font-bold text-[#047857] mb-2 flex items-center gap-2">
🔬 {displayTitle}
</h3>
<p className="text-[#5D4037] mb-4 m-0">{scenario}</p>
{/* Progress bar */}
<div className="mb-4">
<div className="flex items-center justify-between mb-1">
<span className="text-sm font-medium text-[#047857]">{t("progress")}</span>
<span className="text-sm font-medium text-[#047857]">{appliedImprovements.length}/{improvements.length}</span>
</div>
<div className="h-3 bg-green-200 rounded-full overflow-hidden border-2 border-[#10B981]">
<div
className="h-full bg-[#10B981] transition-all duration-500"
style={{ width: `${progressPercentage}%` }}
/>
</div>
</div>
{/* Current prompt */}
<div className="bg-white/80 rounded-lg p-4 mb-4 border-2 border-[#10B981]">
<div className="text-sm font-medium text-[#047857] mb-2">{t("yourPrompt")}</div>
<p className="text-lg font-medium text-[#2C1810] m-0">
"{getCurrentPrompt()}"
</p>
</div>
{/* AI Response */}
<div className={cn(
"rounded-lg p-4 mb-4 border-2 transition-all duration-300",
completed
? "bg-green-100 border-green-500"
: "bg-gray-50 border-gray-300"
)}>
<div className="flex items-center gap-2 mb-2">
<span className="text-xl">🤖</span>
<span className={cn(
"font-medium text-sm",
completed ? "text-green-700" : "text-gray-600"
)}>
{t("aiSays")}
</span>
</div>
<p className="text-[#5D4037] m-0 italic">"{getCurrentResponse()}"</p>
</div>
{/* Improvement buttons */}
{!completed && (
<div className="space-y-2 mb-4">
<div className="text-sm font-medium text-[#047857]">{t("addDetails")}</div>
{improvements.map((improvement, index) => {
const isApplied = appliedImprovements.includes(index);
return (
<button
key={index}
onClick={() => handleApplyImprovement(index)}
disabled={isApplied}
className={cn(
"w-full p-3 rounded-lg border-2 text-left transition-all",
isApplied
? "bg-green-100 border-green-400 opacity-60"
: "bg-white border-[#10B981] hover:bg-green-50 cursor-pointer"
)}
>
<div className="flex items-center gap-2">
<span className="text-xl">{isApplied ? "✅" : ""}</span>
<div className="font-bold text-[#047857]">{improvement.label}</div>
</div>
</button>
);
})}
</div>
)}
{/* Success message */}
{completed && (
<div className="p-4 bg-green-100 border-2 border-green-500 rounded-lg mb-4 animate-in fade-in zoom-in-95 duration-300">
<p className="font-bold text-green-700 text-lg m-0">
🎉 {successMessage || t("success")}
</p>
</div>
)}
{/* Reset button */}
{(completed || appliedImprovements.length > 0) && (
<button
onClick={handleReset}
className="px-6 py-2 rounded-lg font-bold bg-[#047857] hover:bg-[#065F46] text-white"
>
{t("retry")}
</button>
)}
</div>
);
}

View File

@@ -0,0 +1,204 @@
"use client";
import { useState, useEffect, useId } from "react";
import { useTranslations } from "next-intl";
import { cn } from "@/lib/utils";
import { useLevelSlug } from "@/components/kids/providers/level-context";
import { getComponentState, saveComponentState } from "@/lib/kids/progress";
type PartType = "role" | "task" | "context" | "constraint";
interface PromptPart {
text: string;
type: PartType;
}
interface PromptPartsProps {
title?: string;
instruction?: string;
parts: PromptPart[];
successMessage?: string;
}
interface SavedState {
assignments: Record<number, PartType | null>;
completed: boolean;
}
const partColors: Record<PartType, { bg: string; border: string; text: string; emoji: string }> = {
role: { bg: "bg-purple-100", border: "border-purple-400", text: "text-purple-700", emoji: "🎭" },
task: { bg: "bg-blue-100", border: "border-blue-400", text: "text-blue-700", emoji: "✏️" },
context: { bg: "bg-green-100", border: "border-green-400", text: "text-green-700", emoji: "📖" },
constraint: { bg: "bg-orange-100", border: "border-orange-400", text: "text-orange-700", emoji: "📏" },
};
export function PromptParts({ title, instruction, parts, successMessage }: PromptPartsProps) {
const t = useTranslations("kids.promptParts");
const levelSlug = useLevelSlug();
const componentId = useId();
const displayTitle = title || t("title");
const displayInstruction = instruction || t("instruction");
const [assignments, setAssignments] = useState<Record<number, PartType | null>>({});
const [selectedPart, setSelectedPart] = useState<number | null>(null);
const [completed, setCompleted] = useState(false);
const [isLoaded, setIsLoaded] = useState(false);
// Load saved state
useEffect(() => {
if (!levelSlug) {
setIsLoaded(true);
return;
}
const saved = getComponentState<SavedState>(levelSlug, componentId);
if (saved) {
setAssignments(saved.assignments || {});
setCompleted(saved.completed || false);
}
setIsLoaded(true);
}, [levelSlug, componentId]);
// Save state
useEffect(() => {
if (!levelSlug || !isLoaded) return;
saveComponentState<SavedState>(levelSlug, componentId, {
assignments,
completed,
});
}, [levelSlug, componentId, assignments, completed, isLoaded]);
if (!isLoaded) return null;
// Check if all parts are correctly assigned
const checkCompletion = (newAssignments: Record<number, PartType | null>) => {
const allAssigned = parts.every((_, index) => newAssignments[index] !== undefined && newAssignments[index] !== null);
const allCorrect = parts.every((part, index) => newAssignments[index] === part.type);
if (allAssigned && allCorrect) {
setCompleted(true);
}
};
const handlePartClick = (index: number) => {
if (completed) return;
setSelectedPart(selectedPart === index ? null : index);
};
const handleCategoryClick = (category: PartType) => {
if (completed || selectedPart === null) return;
const newAssignments = { ...assignments, [selectedPart]: category };
setAssignments(newAssignments);
setSelectedPart(null);
checkCompletion(newAssignments);
};
const handleReset = () => {
setAssignments({});
setCompleted(false);
setSelectedPart(null);
};
const getAssignmentStatus = (index: number): "correct" | "wrong" | "pending" | null => {
const assigned = assignments[index];
if (assigned === undefined || assigned === null) return null;
return assigned === parts[index].type ? "correct" : "wrong";
};
const score = parts.filter((part, index) => assignments[index] === part.type).length;
return (
<div className="my-4 p-4 bg-gradient-to-br from-[#FEF3C7] to-[#FDE68A] border-4 border-[#D97706] rounded-xl">
{/* Title */}
<h3 className="text-xl font-bold text-[#92400E] mb-2 flex items-center gap-2">
🧩 {displayTitle}
</h3>
<p className="text-[#92400E] mb-4 m-0">{displayInstruction}</p>
{/* Score */}
<div className="mb-4 text-sm font-medium text-[#92400E]">
{t("score")}: {score}/{parts.length}
</div>
{/* Prompt pieces to categorize */}
<div className="bg-white/80 rounded-lg p-4 mb-4 border-2 border-[#D97706]">
<div className="flex flex-wrap gap-2">
{parts.map((part, index) => {
const status = getAssignmentStatus(index);
const isSelected = selectedPart === index;
const assigned = assignments[index];
const colors = assigned ? partColors[assigned] : null;
return (
<button
key={index}
onClick={() => handlePartClick(index)}
disabled={completed}
className={cn(
"px-3 py-2 rounded-lg border-2 transition-all text-base font-medium",
!assigned && !isSelected && "bg-gray-100 border-gray-300 text-gray-700 hover:border-[#D97706]",
!assigned && isSelected && "bg-yellow-100 border-[#D97706] text-[#92400E] ring-2 ring-[#D97706] scale-105",
assigned && colors && `${colors.bg} ${colors.border} ${colors.text}`,
status === "correct" && "ring-2 ring-green-500",
status === "wrong" && "ring-2 ring-red-400",
!completed && "cursor-pointer"
)}
>
{status === "correct" && <span className="mr-1"></span>}
{status === "wrong" && <span className="mr-1"></span>}
{part.text}
</button>
);
})}
</div>
</div>
{/* Category buttons */}
{selectedPart !== null && !completed && (
<div className="mb-4 animate-in fade-in slide-in-from-top-2 duration-200">
<div className="text-sm font-medium text-[#92400E] mb-2">{t("pickCategory")}</div>
<div className="grid grid-cols-2 gap-2">
{(Object.keys(partColors) as PartType[]).map((type) => {
const colors = partColors[type];
return (
<button
key={type}
onClick={() => handleCategoryClick(type)}
className={cn(
"flex items-center justify-center gap-2 px-4 py-3 rounded-lg border-2 font-bold transition-all",
colors.bg,
colors.border,
colors.text,
"hover:scale-105 cursor-pointer"
)}
>
<span className="text-xl">{colors.emoji}</span>
<span>{t(`types.${type}`)}</span>
</button>
);
})}
</div>
</div>
)}
{/* Success message */}
{completed && (
<div className="p-4 bg-green-100 border-2 border-green-500 rounded-lg mb-4 animate-in fade-in zoom-in-95 duration-300">
<p className="font-bold text-green-700 text-lg m-0">
🎉 {successMessage || t("success")}
</p>
</div>
)}
{/* Reset button */}
{(Object.keys(assignments).length > 0 || completed) && (
<button
onClick={handleReset}
className="px-6 py-2 rounded-lg font-bold bg-[#D97706] hover:bg-[#B45309] text-white"
>
{t("retry")}
</button>
)}
</div>
);
}

View File

@@ -1,35 +1,73 @@
"use client";
import { useState } from "react";
import { Check, X, RefreshCw } from "lucide-react";
import { Button } from "@/components/ui/button";
import { useState, useEffect, useId } from "react";
import { useTranslations } from "next-intl";
import { cn } from "@/lib/utils";
import { PromiWithMessage } from "./character-guide";
import { PixelRobot, PixelStar } from "./pixel-art";
import { useLevelSlug } from "@/components/kids/providers/level-context";
import { getComponentState, saveComponentState } from "@/lib/kids/progress";
interface PromptVsMistakeProps {
question: string;
good: string;
bad: string;
goodLabel?: string;
badLabel?: string;
explanation?: string;
promiMessage?: string;
}
interface SavedState {
selected: "good" | "bad" | null;
showResult: boolean;
order: string[];
}
export function PromptVsMistake({
question,
good,
bad,
goodLabel = "Good prompt ✓",
badLabel = "Not so good ✗",
explanation,
promiMessage,
}: PromptVsMistakeProps) {
const t = useTranslations("kids.quiz");
const levelSlug = useLevelSlug();
const componentId = useId();
const [selected, setSelected] = useState<"good" | "bad" | null>(null);
const [showResult, setShowResult] = useState(false);
const [order, setOrder] = useState<string[]>([]);
const [isLoaded, setIsLoaded] = useState(false);
// Randomize order
const [order] = useState(() => Math.random() > 0.5 ? ["good", "bad"] : ["bad", "good"]);
// Load saved state on mount
useEffect(() => {
const randomOrder = Math.random() > 0.5 ? ["good", "bad"] : ["bad", "good"];
if (!levelSlug) {
setOrder(randomOrder);
setIsLoaded(true);
return;
}
const saved = getComponentState<SavedState>(levelSlug, componentId);
if (saved && saved.order && saved.order.length > 0) {
setSelected(saved.selected);
setShowResult(saved.showResult);
setOrder(saved.order);
} else {
setOrder(randomOrder);
}
setIsLoaded(true);
}, [levelSlug, componentId]);
// Save state when it changes
useEffect(() => {
if (!levelSlug || !isLoaded || order.length === 0) return;
saveComponentState<SavedState>(levelSlug, componentId, {
selected,
showResult,
order,
});
}, [levelSlug, componentId, selected, showResult, order, isLoaded]);
const handleSelect = (choice: "good" | "bad") => {
setSelected(choice);
@@ -40,6 +78,9 @@ export function PromptVsMistake({
setSelected(null);
setShowResult(false);
};
// Don't render until loaded to prevent hydration mismatch
if (!isLoaded) return null;
const isCorrect = selected === "good";
@@ -49,83 +90,193 @@ export function PromptVsMistake({
}));
return (
<div className="my-6 rounded-2xl border-2 border-primary/20 overflow-hidden">
{/* Question */}
<div className="px-4 py-3 bg-primary/10 border-b border-primary/20">
<p className="font-semibold text-lg m-0">🤔 {question}</p>
<div className="my-2">
{/* Question card with Promi */}
<div className="flex items-center gap-2 mb-2">
<div className="shrink-0">
<PixelRobot className="w-8 h-10" />
</div>
<div className="flex-1 relative">
{/* Speech bubble */}
<div className="bg-white rounded-lg p-2 shadow-md border-2 border-[#8B4513] relative ml-2">
{/* Arrow pointing left */}
<div className="absolute -left-2 top-1/2 -translate-y-1/2 w-0 h-0 border-t-[6px] border-t-transparent border-r-[8px] border-r-[#8B4513] border-b-[6px] border-b-transparent" />
<div className="absolute -left-1 top-1/2 -translate-y-1/2 w-0 h-0 border-t-[5px] border-t-transparent border-r-[6px] border-r-white border-b-[5px] border-b-transparent" />
<p className="text-base font-bold text-[#2C1810] m-0">
{question}
</p>
</div>
</div>
</div>
<div className="p-4 space-y-4">
{/* Options */}
<div className="grid sm:grid-cols-2 gap-4">
{options.map(({ type, text }) => (
{/* Choice cards - side by side for compact layout */}
<div className="grid grid-cols-2 gap-2">
{options.map(({ type, text }, index) => {
const isGood = type === "good";
return (
<button
key={type}
onClick={() => handleSelect(type)}
disabled={showResult}
className={cn(
"p-4 rounded-xl border-2 text-left transition-all",
!showResult && "hover:border-primary hover:shadow-md cursor-pointer",
showResult && type === "good" && "border-green-500 bg-green-50 dark:bg-green-950/30",
showResult && type === "bad" && "border-red-400 bg-red-50 dark:bg-red-950/30",
!showResult && "border-muted-foreground/20 bg-muted/30"
"w-full h-full text-left transition-all duration-300 rounded-lg overflow-hidden",
!showResult && "hover:scale-[1.01] hover:shadow-lg cursor-pointer",
showResult && "scale-100"
)}
>
<pre className="whitespace-pre-wrap text-sm font-mono m-0">{text}</pre>
{showResult && (
<div className={cn(
"p-0.5 rounded-md transition-colors h-full",
!showResult && "bg-gradient-to-br from-[#FFB347] to-[#FF8C00]",
showResult && isGood && "bg-gradient-to-br from-[#4ADE80] to-[#16A34A]",
showResult && !isGood && "bg-gradient-to-br from-[#FB7185] to-[#E11D48]"
)}>
<div className={cn(
"flex items-center gap-2 mt-3 pt-3 border-t text-sm font-medium",
type === "good" ? "text-green-600 border-green-200" : "text-red-500 border-red-200"
"bg-white rounded-md p-2 transition-colors h-full flex flex-col",
showResult && isGood && "bg-[#F0FDF4]",
showResult && !isGood && "bg-[#FFF1F2]"
)}>
{type === "good" ? (
<>
<Check className="h-4 w-4" />
{goodLabel}
</>
) : (
<>
<X className="h-4 w-4" />
{badLabel}
</>
)}
{/* Option label */}
<div className="flex items-center gap-1 mb-1">
<span className={cn(
"w-6 h-6 rounded-full flex items-center justify-center text-sm font-bold text-white",
!showResult && "bg-[#F59E0B]",
showResult && isGood && "bg-[#22C55E]",
showResult && !isGood && "bg-[#EF4444]"
)}>
{showResult ? (isGood ? <PixelCheckIcon /> : <PixelXIcon />) : (index === 0 ? "A" : "B")}
</span>
{showResult && (
<span className={cn(
"text-sm font-bold",
isGood ? "text-[#16A34A]" : "text-[#DC2626]"
)}>
{isGood ? t("goodLabel") : t("badLabel")}
</span>
)}
</div>
{/* Prompt text */}
<div className="bg-[#FEF3C7] rounded p-1.5 border border-[#F59E0B]/30 flex-1 flex items-center">
<p className="text-sm text-[#2C1810] m-0 whitespace-pre-wrap leading-tight">
"{text}"
</p>
</div>
</div>
)}
</div>
</button>
))}
</div>
{/* Result */}
{showResult && (
<div className={cn(
"p-4 rounded-xl",
isCorrect
? "bg-green-100 dark:bg-green-950/50 border border-green-300 dark:border-green-800"
: "bg-amber-100 dark:bg-amber-950/50 border border-amber-300 dark:border-amber-800"
)}>
<p className="font-semibold text-lg m-0 mb-2">
{isCorrect ? "🎉 Great job!" : "🤔 Not quite!"}
</p>
{explanation && <p className="text-sm m-0">{explanation}</p>}
</div>
)}
{/* Promi message */}
{showResult && promiMessage && (
<PromiWithMessage
message={promiMessage}
mood={isCorrect ? "celebrating" : "thinking"}
/>
)}
{/* Reset button */}
{showResult && (
<Button onClick={handleReset} variant="outline" size="sm" className="rounded-full">
<RefreshCw className="h-4 w-4 mr-1" />
Try again
</Button>
)}
);
})}
</div>
{/* Result feedback */}
{showResult && (
<div className={cn(
"mt-3 rounded-lg p-3 text-center animate-in fade-in zoom-in-95 duration-300",
isCorrect
? "bg-gradient-to-br from-[#BBF7D0] to-[#86EFAC] border-2 border-[#22C55E]"
: "bg-gradient-to-br from-[#FEF08A] to-[#FDE047] border-2 border-[#F59E0B]"
)}>
<p className="text-base font-bold text-[#2C1810] m-0">
{isCorrect ? (
<><PixelStar filled className="w-4 h-4 inline" /> {t("correct")} <PixelStar filled className="w-4 h-4 inline" /></>
) : t("incorrect")}
</p>
{explanation && (
<p className="text-sm text-[#5D4037] m-0 mt-2">{explanation}</p>
)}
{promiMessage && (
<div className="flex items-center justify-center gap-2 mt-3 bg-white/60 rounded-md p-2">
<PixelRobot className="w-6 h-7 shrink-0" />
<p className="text-sm text-[#5D4037] m-0">{promiMessage}</p>
</div>
)}
<button
onClick={handleReset}
className="mt-3 inline-flex items-center gap-1 px-4 py-2 bg-[#8B4513] hover:bg-[#A0522D] text-white text-sm font-bold rounded-md transition-colors"
>
<PixelRefreshIcon />
{t("tryAgain")}
</button>
</div>
)}
</div>
);
}
// Pixel art icons
function PixelCheckIcon() {
return (
<svg viewBox="0 0 12 12" className="w-5 h-5" style={{ imageRendering: "pixelated" }}>
<rect x="2" y="6" width="2" height="2" fill="currentColor" />
<rect x="4" y="8" width="2" height="2" fill="currentColor" />
<rect x="6" y="6" width="2" height="2" fill="currentColor" />
<rect x="8" y="4" width="2" height="2" fill="currentColor" />
<rect x="10" y="2" width="2" height="2" fill="currentColor" />
</svg>
);
}
function PixelXIcon() {
return (
<svg viewBox="0 0 12 12" className="w-5 h-5" style={{ imageRendering: "pixelated" }}>
<rect x="2" y="2" width="2" height="2" fill="currentColor" />
<rect x="8" y="2" width="2" height="2" fill="currentColor" />
<rect x="4" y="4" width="2" height="2" fill="currentColor" />
<rect x="6" y="4" width="2" height="2" fill="currentColor" />
<rect x="4" y="6" width="2" height="2" fill="currentColor" />
<rect x="6" y="6" width="2" height="2" fill="currentColor" />
<rect x="2" y="8" width="2" height="2" fill="currentColor" />
<rect x="8" y="8" width="2" height="2" fill="currentColor" />
</svg>
);
}
function PixelRefreshIcon() {
return (
<svg viewBox="0 0 16 16" className="w-4 h-4" style={{ imageRendering: "pixelated" }}>
<rect x="6" y="1" width="4" height="2" fill="currentColor" />
<rect x="4" y="3" width="2" height="2" fill="currentColor" />
<rect x="10" y="3" width="2" height="2" fill="currentColor" />
<rect x="2" y="5" width="2" height="2" fill="currentColor" />
<rect x="12" y="5" width="2" height="4" fill="currentColor" />
<rect x="2" y="7" width="2" height="4" fill="currentColor" />
<rect x="12" y="9" width="2" height="2" fill="currentColor" />
<rect x="4" y="11" width="2" height="2" fill="currentColor" />
<rect x="10" y="11" width="2" height="2" fill="currentColor" />
<rect x="6" y="13" width="4" height="2" fill="currentColor" />
<rect x="12" y="3" width="3" height="2" fill="currentColor" />
<rect x="1" y="11" width="3" height="2" fill="currentColor" />
</svg>
);
}
function PixelThinkingIcon() {
return (
<svg viewBox="0 0 32 32" className="w-12 h-12" style={{ imageRendering: "pixelated" }}>
{/* Face circle */}
<rect x="10" y="4" width="12" height="2" fill="#F59E0B" />
<rect x="8" y="6" width="2" height="2" fill="#F59E0B" />
<rect x="22" y="6" width="2" height="2" fill="#F59E0B" />
<rect x="6" y="8" width="2" height="4" fill="#F59E0B" />
<rect x="24" y="8" width="2" height="4" fill="#F59E0B" />
<rect x="6" y="12" width="2" height="4" fill="#F59E0B" />
<rect x="24" y="12" width="2" height="4" fill="#F59E0B" />
<rect x="8" y="16" width="2" height="2" fill="#F59E0B" />
<rect x="22" y="16" width="2" height="2" fill="#F59E0B" />
<rect x="10" y="18" width="12" height="2" fill="#F59E0B" />
{/* Fill */}
<rect x="8" y="8" width="16" height="8" fill="#FEF3C7" />
<rect x="10" y="6" width="12" height="2" fill="#FEF3C7" />
<rect x="10" y="16" width="12" height="2" fill="#FEF3C7" />
{/* Eyes */}
<rect x="10" y="10" width="2" height="2" fill="#2C1810" />
<rect x="20" y="10" width="2" height="2" fill="#2C1810" />
{/* Thinking mouth */}
<rect x="14" y="14" width="4" height="2" fill="#2C1810" />
{/* Thought bubbles */}
<rect x="26" y="4" width="2" height="2" fill="#8B7355" />
<rect x="28" y="2" width="3" height="3" fill="#8B7355" />
</svg>
);
}

View File

@@ -0,0 +1,212 @@
"use client";
import { useState, useEffect, useId } from "react";
import { useTranslations } from "next-intl";
import { cn } from "@/lib/utils";
import { useLevelSlug } from "@/components/kids/providers/level-context";
import { getComponentState, saveComponentState } from "@/lib/kids/progress";
interface StepByStepProps {
title?: string;
problem: string;
wrongAnswer: string;
steps: string[];
rightAnswer: string;
magicWords?: string;
successMessage?: string;
}
interface SavedState {
magicWordsAdded: boolean;
revealedSteps: number;
completed: boolean;
}
export function StepByStep({
title,
problem,
wrongAnswer,
steps,
rightAnswer,
magicWords = "Let's think step by step",
successMessage,
}: StepByStepProps) {
const t = useTranslations("kids.stepByStep");
const levelSlug = useLevelSlug();
const componentId = useId();
const displayTitle = title || t("title");
const [magicWordsAdded, setMagicWordsAdded] = useState(false);
const [revealedSteps, setRevealedSteps] = useState(0);
const [completed, setCompleted] = useState(false);
const [isLoaded, setIsLoaded] = useState(false);
// Load saved state
useEffect(() => {
if (!levelSlug) {
setIsLoaded(true);
return;
}
const saved = getComponentState<SavedState>(levelSlug, componentId);
if (saved && typeof saved.magicWordsAdded === 'boolean') {
setMagicWordsAdded(saved.magicWordsAdded);
setRevealedSteps(saved.revealedSteps || 0);
setCompleted(saved.completed || false);
}
setIsLoaded(true);
}, [levelSlug, componentId]);
// Save state
useEffect(() => {
if (!levelSlug || !isLoaded) return;
saveComponentState<SavedState>(levelSlug, componentId, {
magicWordsAdded,
revealedSteps,
completed,
});
}, [levelSlug, componentId, magicWordsAdded, revealedSteps, completed, isLoaded]);
if (!isLoaded) return null;
const handleAddMagicWords = () => {
setMagicWordsAdded(true);
};
const handleRevealNextStep = () => {
if (revealedSteps < steps.length) {
const newRevealed = revealedSteps + 1;
setRevealedSteps(newRevealed);
if (newRevealed === steps.length) {
setCompleted(true);
}
}
};
const handleReset = () => {
setMagicWordsAdded(false);
setRevealedSteps(0);
setCompleted(false);
};
return (
<div className="my-4 p-4 bg-gradient-to-br from-[#DBEAFE] to-[#BFDBFE] border-4 border-[#3B82F6] rounded-xl">
{/* Title */}
<h3 className="text-xl font-bold text-[#1D4ED8] mb-4 flex items-center gap-2">
🧠 {displayTitle}
</h3>
{/* Problem */}
<div className="bg-white/80 rounded-lg p-4 mb-4 border-2 border-[#3B82F6]">
<div className="text-sm font-medium text-[#3B82F6] mb-2">{t("problem")}</div>
<p className="text-lg font-medium text-[#2C1810] m-0">{problem}</p>
</div>
{/* Wrong answer (before magic words) */}
{!magicWordsAdded && (
<div className="bg-red-50 rounded-lg p-4 mb-4 border-2 border-red-300">
<div className="flex items-center gap-2 mb-2">
<span className="text-xl">😕</span>
<span className="font-bold text-red-600">{t("withoutMagic")}</span>
</div>
<p className="text-[#5D4037] m-0">{wrongAnswer}</p>
</div>
)}
{/* Magic words button */}
{!magicWordsAdded && (
<button
onClick={handleAddMagicWords}
className="w-full p-4 mb-4 rounded-lg border-4 border-dashed border-[#8B5CF6] bg-purple-50 hover:bg-purple-100 transition-all cursor-pointer group"
>
<div className="flex items-center justify-center gap-2">
<span className="text-2xl group-hover:animate-bounce"></span>
<span className="text-lg font-bold text-[#7C3AED]">{t("addMagicWords")}</span>
<span className="text-2xl group-hover:animate-bounce"></span>
</div>
<div className="text-[#8B5CF6] font-medium mt-1">"{magicWords}"</div>
</button>
)}
{/* Steps revealed after magic words */}
{magicWordsAdded && (
<>
<div className="bg-purple-50 rounded-lg p-3 mb-4 border-2 border-[#8B5CF6]">
<div className="flex items-center gap-2">
<span className="text-xl"></span>
<span className="font-bold text-[#7C3AED]">{t("magicWordsActive")}</span>
</div>
<p className="text-[#8B5CF6] m-0">"{magicWords}"</p>
</div>
{/* Steps */}
<div className="space-y-2 mb-4">
{steps.map((step, index) => {
const isRevealed = index < revealedSteps;
return (
<div
key={index}
className={cn(
"p-3 rounded-lg border-2 transition-all duration-300",
isRevealed
? "bg-green-50 border-green-400 animate-in fade-in slide-in-from-left-4"
: "bg-gray-100 border-gray-300"
)}
>
<div className="flex items-center gap-2">
<span className={cn(
"w-6 h-6 rounded-full flex items-center justify-center text-sm font-bold",
isRevealed ? "bg-green-500 text-white" : "bg-gray-300 text-gray-500"
)}>
{isRevealed ? "✓" : index + 1}
</span>
<span className={cn(
"font-medium",
isRevealed ? "text-green-700" : "text-gray-400"
)}>
{isRevealed ? step : "???"}
</span>
</div>
</div>
);
})}
</div>
{/* Reveal next step button */}
{!completed && (
<button
onClick={handleRevealNextStep}
className="w-full p-3 rounded-lg font-bold bg-[#3B82F6] hover:bg-[#2563EB] text-white transition-all"
>
{t("nextStep")} ({revealedSteps + 1}/{steps.length})
</button>
)}
{/* Correct answer revealed */}
{completed && (
<div className="bg-green-100 rounded-lg p-4 border-2 border-green-500 animate-in fade-in zoom-in-95 duration-300">
<div className="flex items-center gap-2 mb-2">
<span className="text-2xl">🎉</span>
<span className="font-bold text-green-700">{t("withMagic")}</span>
</div>
<p className="text-green-700 font-medium m-0">{rightAnswer}</p>
{successMessage && (
<p className="text-[#5D4037] mt-2 m-0">{successMessage}</p>
)}
</div>
)}
</>
)}
{/* Reset button */}
{(magicWordsAdded || completed) && (
<button
onClick={handleReset}
className="mt-4 px-6 py-2 rounded-lg font-bold bg-gray-500 hover:bg-gray-600 text-white"
>
{t("retry")}
</button>
)}
</div>
);
}

View File

@@ -1,10 +1,8 @@
"use client";
import { useState } from "react";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { PromiCharacter } from "./character-guide";
import { PixelRobot } from "./pixel-art";
type PromiMood = "happy" | "thinking" | "excited" | "confused" | "celebrating";
@@ -27,66 +25,62 @@ export function StoryScene({ panels, className }: StorySceneProps) {
const isLast = currentPanel === panels.length - 1;
return (
<div className={cn("my-6 rounded-2xl border-2 border-primary/20 overflow-hidden", className)}>
<div className={cn("my-6 pixel-panel overflow-hidden", className)}>
{/* Story panel */}
<div className="p-6 bg-gradient-to-br from-primary/5 to-purple-500/5 min-h-[200px] flex items-center">
<div className="p-4 min-h-[180px] flex items-center">
<div className="flex items-start gap-4 w-full">
{panel.character === "promi" && (
<div className="shrink-0">
<PromiCharacter mood={panel.mood || "happy"} size="md" />
<PixelRobot className="w-12 h-16" />
</div>
)}
<div
className={cn(
"flex-1 p-4 bg-white dark:bg-card rounded-xl shadow-md",
panel.highlight && "ring-2 ring-primary ring-offset-2"
"flex-1 p-4 bg-white/80 border-2 border-[#D97706]",
panel.highlight && "bg-[#FEF3C7]"
)}
style={{ clipPath: "polygon(4px 0, calc(100% - 4px) 0, 100% 4px, 100% calc(100% - 4px), calc(100% - 4px) 100%, 4px 100%, 0 calc(100% - 4px), 0 4px)" }}
>
<p className="text-lg leading-relaxed m-0">{panel.text}</p>
<p className="text-xl leading-relaxed m-0 text-[#2C1810]">{panel.text}</p>
</div>
</div>
</div>
{/* Navigation */}
{/* Navigation - pixel style */}
{panels.length > 1 && (
<div className="flex items-center justify-between px-4 py-3 bg-muted/30 border-t">
<Button
variant="ghost"
size="sm"
<div className="flex items-center justify-between px-4 py-2 bg-[#4A3728] border-t-2 border-[#8B4513]">
<button
onClick={() => setCurrentPanel((p) => p - 1)}
disabled={isFirst}
className="gap-1"
className={cn("pixel-btn px-3 py-1 text-xs", isFirst && "opacity-40")}
>
<ChevronLeft className="h-4 w-4" />
Back
</Button>
</button>
{/* Progress dots */}
{/* Progress dots - pixel style */}
<div className="flex items-center gap-1.5">
{panels.map((_, i) => (
<button
key={i}
onClick={() => setCurrentPanel(i)}
className={cn(
"w-2 h-2 rounded-full transition-colors",
"w-3 h-3 border-2",
i === currentPanel
? "bg-primary"
: "bg-muted-foreground/30 hover:bg-muted-foreground/50"
? "bg-[#22C55E] border-[#16A34A]"
: "bg-[#4A3728] border-[#8B4513] hover:bg-[#5D4037]"
)}
style={{ clipPath: "polygon(2px 0, calc(100% - 2px) 0, 100% 2px, 100% calc(100% - 2px), calc(100% - 2px) 100%, 2px 100%, 0 calc(100% - 2px), 0 2px)" }}
/>
))}
</div>
<Button
variant="ghost"
size="sm"
<button
onClick={() => setCurrentPanel((p) => p + 1)}
disabled={isLast}
className="gap-1"
className={cn("pixel-btn pixel-btn-green px-3 py-1 text-xs", isLast && "opacity-40")}
>
Next
<ChevronRight className="h-4 w-4" />
</Button>
</button>
</div>
)}
</div>
@@ -102,21 +96,22 @@ interface SinglePanelProps {
export function Panel({ character = "promi", mood = "happy", children, highlight }: SinglePanelProps) {
return (
<div className="my-6 rounded-2xl border-2 border-primary/20 overflow-hidden">
<div className="p-6 bg-gradient-to-br from-primary/5 to-purple-500/5">
<div className="my-6 pixel-panel">
<div className="p-4">
<div className="flex items-start gap-4">
{character === "promi" && (
<div className="shrink-0">
<PromiCharacter mood={mood} size="md" />
<PixelRobot className="w-12 h-16" />
</div>
)}
<div
className={cn(
"flex-1 p-4 bg-white dark:bg-card rounded-xl shadow-md",
highlight && "ring-2 ring-primary ring-offset-2"
"flex-1 p-4 bg-white/80 border-2 border-[#D97706]",
highlight && "bg-[#FEF3C7]"
)}
style={{ clipPath: "polygon(4px 0, calc(100% - 4px) 0, 100% 4px, 100% calc(100% - 4px), calc(100% - 4px) 100%, 4px 100%, 0 calc(100% - 4px), 0 4px)" }}
>
<div className="text-lg leading-relaxed">{children}</div>
<div className="text-xl leading-relaxed text-[#2C1810]">{children}</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,204 @@
"use client";
import { useState, useEffect, useId } from "react";
import { useTranslations } from "next-intl";
import { cn } from "@/lib/utils";
import { useLevelSlug } from "@/components/kids/providers/level-context";
import { getComponentState, saveComponentState } from "@/lib/kids/progress";
interface WordPredictorProps {
title?: string;
instruction?: string;
sentence: string;
options: string[];
correctAnswer: string;
explanation: string;
aiThinking?: string;
successMessage?: string;
}
interface SavedState {
selectedAnswer: string | null;
submitted: boolean;
}
export function WordPredictor({
title,
instruction,
sentence,
options,
correctAnswer,
explanation,
aiThinking,
successMessage,
}: WordPredictorProps) {
const t = useTranslations("kids.wordPredictor");
const levelSlug = useLevelSlug();
const componentId = useId();
const displayTitle = title || t("title");
const displayInstruction = instruction || t("instruction");
const [selectedAnswer, setSelectedAnswer] = useState<string | null>(null);
const [submitted, setSubmitted] = useState(false);
const [isLoaded, setIsLoaded] = useState(false);
// Load saved state
useEffect(() => {
if (!levelSlug) {
setIsLoaded(true);
return;
}
const saved = getComponentState<SavedState>(levelSlug, componentId);
if (saved && typeof saved.submitted === "boolean") {
setSelectedAnswer(saved.selectedAnswer);
setSubmitted(saved.submitted);
}
setIsLoaded(true);
}, [levelSlug, componentId]);
// Save state
useEffect(() => {
if (!levelSlug || !isLoaded) return;
saveComponentState<SavedState>(levelSlug, componentId, {
selectedAnswer,
submitted,
});
}, [levelSlug, componentId, selectedAnswer, submitted, isLoaded]);
if (!isLoaded) return null;
const isCorrect = selectedAnswer === correctAnswer;
const handleSelect = (option: string) => {
if (submitted) return;
setSelectedAnswer(option);
};
const handleSubmit = () => {
if (!selectedAnswer) return;
setSubmitted(true);
};
const handleReset = () => {
setSelectedAnswer(null);
setSubmitted(false);
};
// Render sentence with blank
const renderSentence = () => {
const parts = sentence.split("___");
return (
<span>
{parts[0]}
<span className={cn(
"inline-block min-w-[80px] px-3 py-1 mx-1 rounded-lg border-2 border-dashed text-center font-bold",
!submitted && "bg-yellow-100 border-yellow-500 text-yellow-700",
submitted && isCorrect && "bg-green-100 border-green-500 text-green-700 border-solid",
submitted && !isCorrect && "bg-red-100 border-red-400 text-red-600 border-solid"
)}>
{selectedAnswer || "???"}
</span>
{parts[1]}
</span>
);
};
return (
<div className="my-4 p-4 bg-gradient-to-br from-[#E0E7FF] to-[#C7D2FE] border-4 border-[#6366F1] rounded-xl">
{/* Title */}
<h3 className="text-xl font-bold text-[#4338CA] mb-2 flex items-center gap-2">
🧠 {displayTitle}
</h3>
<p className="text-[#5D4037] mb-4 m-0">{displayInstruction}</p>
{/* AI Brain visualization */}
<div className="bg-white/80 rounded-lg p-4 mb-4 border-2 border-[#6366F1]">
<div className="flex items-center gap-2 mb-3">
<span className="text-2xl">🤖</span>
<span className="font-medium text-[#4338CA]">{t("aiThinks")}</span>
</div>
<p className="text-lg text-[#2C1810] m-0">
{renderSentence()}
</p>
</div>
{/* Thinking bubble */}
{!submitted && (
<div className="bg-[#F0F9FF] rounded-lg p-3 mb-4 border-2 border-[#0EA5E9] italic text-[#0369A1]">
💭 {aiThinking || t("thinkingDefault")}
</div>
)}
{/* Options */}
<div className="grid grid-cols-2 gap-2 mb-4">
{options.map((option, index) => {
const isSelected = selectedAnswer === option;
const showCorrect = submitted && option === correctAnswer;
const showWrong = submitted && isSelected && !isCorrect;
return (
<button
key={index}
onClick={() => handleSelect(option)}
disabled={submitted}
className={cn(
"p-3 rounded-lg border-2 font-bold text-lg transition-all",
!submitted && !isSelected && "bg-white border-[#6366F1] text-[#4338CA] hover:bg-indigo-50 cursor-pointer",
!submitted && isSelected && "bg-indigo-100 border-[#4338CA] text-[#4338CA] ring-2 ring-[#4338CA] scale-105",
showCorrect && "bg-green-100 border-green-500 text-green-700",
showWrong && "bg-red-100 border-red-400 text-red-600"
)}
>
{showCorrect && "✓ "}
{showWrong && "✗ "}
{option}
</button>
);
})}
</div>
{/* Submit button */}
{!submitted && (
<button
onClick={handleSubmit}
disabled={!selectedAnswer}
className={cn(
"w-full py-3 rounded-lg font-bold text-lg transition-all",
selectedAnswer
? "bg-[#6366F1] hover:bg-[#4F46E5] text-white cursor-pointer"
: "bg-gray-200 text-gray-400 cursor-not-allowed"
)}
>
{t("check")}
</button>
)}
{/* Result */}
{submitted && (
<div className={cn(
"p-4 rounded-lg border-2 mb-4 animate-in fade-in zoom-in-95 duration-300",
isCorrect ? "bg-green-100 border-green-500" : "bg-orange-100 border-orange-400"
)}>
<p className={cn(
"font-bold text-lg mb-2 m-0",
isCorrect ? "text-green-700" : "text-orange-700"
)}>
{isCorrect ? `🎉 ${successMessage || t("correct")}` : `🤔 ${t("tryAgain")}`}
</p>
<p className="text-[#5D4037] m-0">{explanation}</p>
</div>
)}
{/* Reset button */}
{submitted && (
<button
onClick={handleReset}
className="px-6 py-2 rounded-lg font-bold bg-[#6366F1] hover:bg-[#4F46E5] text-white"
>
{t("retry")}
</button>
)}
</div>
);
}

View File

@@ -0,0 +1,347 @@
"use client";
import { useState, useRef, useCallback } from "react";
// Pixel art speaker icons
function PixelSpeakerOn() {
return (
<svg viewBox="0 0 16 16" className="w-5 h-5" style={{ imageRendering: "pixelated" }}>
<rect x="2" y="5" width="3" height="6" fill="currentColor" />
<rect x="5" y="4" width="2" height="8" fill="currentColor" />
<rect x="7" y="3" width="2" height="10" fill="currentColor" />
<rect x="11" y="4" width="2" height="2" fill="currentColor" />
<rect x="11" y="10" width="2" height="2" fill="currentColor" />
<rect x="13" y="6" width="2" height="4" fill="currentColor" />
</svg>
);
}
function PixelSpeakerOff() {
return (
<svg viewBox="0 0 16 16" className="w-5 h-5" style={{ imageRendering: "pixelated" }}>
<rect x="2" y="5" width="3" height="6" fill="currentColor" />
<rect x="5" y="4" width="2" height="8" fill="currentColor" />
<rect x="7" y="3" width="2" height="10" fill="currentColor" />
<rect x="11" y="4" width="2" height="2" fill="currentColor" />
<rect x="13" y="6" width="2" height="2" fill="currentColor" />
<rect x="11" y="10" width="2" height="2" fill="currentColor" />
<rect x="13" y="10" width="2" height="2" fill="currentColor" />
</svg>
);
}
// 8-bit chiptune music generator using Web Audio API
class ChiptunePlayer {
private audioContext: AudioContext | null = null;
private masterGain: GainNode | null = null;
private isPlaying = false;
private intervalId: number | null = null;
private step = 0;
// Fun bouncy 8-bit melody - playful and catchy!
private melody = [
// Part 1: Bouncy intro
523.25, 0, 659.25, 0, 783.99, 0, 659.25, 0, // C5 - E5 - G5 - E5 (bouncy)
698.46, 0, 783.99, 0, 880.00, 783.99, 659.25, 0, // F5 - G5 - A5 G5 E5 (climb up!)
// Part 2: Silly descending
783.99, 698.46, 659.25, 587.33, 523.25, 0, 392.00, 0, // G5 F5 E5 D5 C5 - G4 (slide down)
440.00, 493.88, 523.25, 0, 659.25, 0, 523.25, 0, // A4 B4 C5 - E5 - C5 (pop back up)
// Part 3: Playful jumps
392.00, 523.25, 392.00, 523.25, 659.25, 783.99, 659.25, 0, // G4 C5 G4 C5 E5 G5 E5 (jumping!)
880.00, 0, 783.99, 0, 659.25, 523.25, 587.33, 659.25, // A5 - G5 - E5 C5 D5 E5
// Part 4: Fun ending
783.99, 0, 659.25, 0, 523.25, 587.33, 659.25, 783.99, // G5 - E5 - C5 D5 E5 G5
880.00, 783.99, 659.25, 523.25, 392.00, 0, 523.25, 0, // A5 G5 E5 C5 G4 - C5 (finish!)
];
// Funky bass line - groovy!
private bass = [
// Part 1
130.81, 0, 130.81, 164.81, 196.00, 0, 164.81, 0, // C3 - C3 E3 G3 - E3
174.61, 0, 196.00, 0, 220.00, 196.00, 164.81, 0, // F3 - G3 - A3 G3 E3
// Part 2
196.00, 174.61, 164.81, 146.83, 130.81, 0, 98.00, 0, // G3 F3 E3 D3 C3 - G2
110.00, 123.47, 130.81, 0, 164.81, 0, 130.81, 0, // A2 B2 C3 - E3 - C3
// Part 3
98.00, 130.81, 98.00, 130.81, 164.81, 196.00, 164.81, 0, // G2 C3 G2 C3 E3 G3 E3
220.00, 0, 196.00, 0, 164.81, 130.81, 146.83, 164.81, // A3 - G3 - E3 C3 D3 E3
// Part 4
196.00, 0, 164.81, 0, 130.81, 146.83, 164.81, 196.00, // G3 - E3 - C3 D3 E3 G3
220.00, 196.00, 164.81, 130.81, 98.00, 0, 130.81, 0, // A3 G3 E3 C3 G2 - C3
];
async start() {
if (this.isPlaying) return;
try {
this.audioContext = new (window.AudioContext || (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext)();
// Resume audio context if suspended (required by browsers)
if (this.audioContext.state === "suspended") {
await this.audioContext.resume();
}
this.masterGain = this.audioContext.createGain();
this.masterGain.gain.value = 0.2;
this.masterGain.connect(this.audioContext.destination);
this.isPlaying = true;
this.step = 0;
// Play notes at upbeat tempo
this.intervalId = window.setInterval(() => this.playStep(), 150);
} catch (error) {
console.error("Failed to start audio:", error);
}
}
private playStep() {
if (!this.audioContext || !this.masterGain) return;
const now = this.audioContext.currentTime;
const noteIndex = this.step % this.melody.length;
// Play melody (square wave for 8-bit sound) - skip if 0 (rest)
if (this.melody[noteIndex] > 0) {
this.playNote(this.melody[noteIndex], now, 0.12, "square", 0.12);
}
// Play bass (triangle wave) - louder and deeper, skip if 0
if (this.bass[noteIndex] > 0) {
this.playNote(this.bass[noteIndex], now, 0.14, "triangle", 0.15);
// Add sub-bass for extra punch
this.playNote(this.bass[noteIndex] / 2, now, 0.14, "sine", 0.08);
}
// Aggressive dubstep-style percussion
if (this.step % 8 === 0) {
this.playDrum(now, "kick");
this.playDrum(now + 0.15, "kick"); // Double kick
} else if (this.step % 8 === 4) {
this.playDrum(now, "snare");
} else if (this.step % 2 === 1) {
this.playDrum(now, "hihat");
}
// Wobble bass on every other beat for dubstep feel
if (this.step % 4 === 2) {
this.playWobble(now);
}
this.step++;
}
private playNote(freq: number, time: number, duration: number, type: OscillatorType, volume: number) {
if (!this.audioContext || !this.masterGain) return;
const osc = this.audioContext.createOscillator();
const gain = this.audioContext.createGain();
osc.type = type;
osc.frequency.value = freq;
gain.gain.setValueAtTime(volume, time);
gain.gain.exponentialRampToValueAtTime(0.001, time + duration);
osc.connect(gain);
gain.connect(this.masterGain);
osc.start(time);
osc.stop(time + duration);
}
private playDrum(time: number, type: "kick" | "hihat" | "snare" = "hihat") {
if (!this.audioContext || !this.masterGain) return;
if (type === "kick") {
// Heavy dubstep kick - deep and punchy
const osc = this.audioContext.createOscillator();
const gain = this.audioContext.createGain();
osc.type = "sine";
osc.frequency.setValueAtTime(180, time);
osc.frequency.exponentialRampToValueAtTime(30, time + 0.12);
gain.gain.setValueAtTime(0.5, time);
gain.gain.exponentialRampToValueAtTime(0.001, time + 0.2);
osc.connect(gain);
gain.connect(this.masterGain);
osc.start(time);
osc.stop(time + 0.2);
// Add click for attack
const click = this.audioContext.createOscillator();
const clickGain = this.audioContext.createGain();
click.type = "square";
click.frequency.value = 800;
clickGain.gain.setValueAtTime(0.15, time);
clickGain.gain.exponentialRampToValueAtTime(0.001, time + 0.02);
click.connect(clickGain);
clickGain.connect(this.masterGain);
click.start(time);
click.stop(time + 0.02);
} else if (type === "snare") {
// Aggressive dubstep snare - noise + tone
const bufferSize = this.audioContext.sampleRate * 0.15;
const buffer = this.audioContext.createBuffer(1, bufferSize, this.audioContext.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
data[i] = Math.random() * 2 - 1;
}
const noise = this.audioContext.createBufferSource();
noise.buffer = buffer;
const noiseGain = this.audioContext.createGain();
noiseGain.gain.setValueAtTime(0.3, time);
noiseGain.gain.exponentialRampToValueAtTime(0.001, time + 0.15);
const filter = this.audioContext.createBiquadFilter();
filter.type = "bandpass";
filter.frequency.value = 3000;
filter.Q.value = 1;
noise.connect(filter);
filter.connect(noiseGain);
noiseGain.connect(this.masterGain);
noise.start(time);
noise.stop(time + 0.15);
// Add tone body
const tone = this.audioContext.createOscillator();
const toneGain = this.audioContext.createGain();
tone.type = "triangle";
tone.frequency.setValueAtTime(200, time);
tone.frequency.exponentialRampToValueAtTime(100, time + 0.05);
toneGain.gain.setValueAtTime(0.2, time);
toneGain.gain.exponentialRampToValueAtTime(0.001, time + 0.08);
tone.connect(toneGain);
toneGain.connect(this.masterGain);
tone.start(time);
tone.stop(time + 0.08);
} else {
// Hi-hat - crispy noise
const bufferSize = this.audioContext.sampleRate * 0.04;
const buffer = this.audioContext.createBuffer(1, bufferSize, this.audioContext.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
data[i] = Math.random() * 2 - 1;
}
const noise = this.audioContext.createBufferSource();
noise.buffer = buffer;
const gain = this.audioContext.createGain();
gain.gain.setValueAtTime(0.08, time);
gain.gain.exponentialRampToValueAtTime(0.001, time + 0.04);
const filter = this.audioContext.createBiquadFilter();
filter.type = "highpass";
filter.frequency.value = 9000;
noise.connect(filter);
filter.connect(gain);
gain.connect(this.masterGain);
noise.start(time);
noise.stop(time + 0.04);
}
}
private playWobble(time: number) {
if (!this.audioContext || !this.masterGain) return;
// Classic dubstep wobble bass
const osc = this.audioContext.createOscillator();
const gain = this.audioContext.createGain();
const lfo = this.audioContext.createOscillator();
const lfoGain = this.audioContext.createGain();
osc.type = "sawtooth";
osc.frequency.value = 55; // Low A
// LFO for wobble effect
lfo.type = "sine";
lfo.frequency.value = 8; // Wobble speed
lfoGain.gain.value = 400;
lfo.connect(lfoGain);
// Create filter for wobble
const filter = this.audioContext.createBiquadFilter();
filter.type = "lowpass";
filter.frequency.value = 400;
filter.Q.value = 8;
lfoGain.connect(filter.frequency);
gain.gain.setValueAtTime(0.2, time);
gain.gain.exponentialRampToValueAtTime(0.001, time + 0.3);
osc.connect(filter);
filter.connect(gain);
gain.connect(this.masterGain);
lfo.start(time);
osc.start(time);
lfo.stop(time + 0.3);
osc.stop(time + 0.3);
}
stop() {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
if (this.audioContext) {
this.audioContext.close();
this.audioContext = null;
}
this.isPlaying = false;
}
getIsPlaying() {
return this.isPlaying;
}
}
export function MusicButton() {
const [isPlaying, setIsPlaying] = useState(false);
const playerRef = useRef<ChiptunePlayer | null>(null);
const toggleMusic = useCallback(async () => {
if (!playerRef.current) {
playerRef.current = new ChiptunePlayer();
}
if (isPlaying) {
playerRef.current.stop();
setIsPlaying(false);
} else {
await playerRef.current.start();
setIsPlaying(true);
}
}, [isPlaying]);
return (
<button
onClick={toggleMusic}
className="pixel-btn pixel-btn-amber px-2 py-1.5 h-8 flex items-center"
aria-label={isPlaying ? "Mute music" : "Play music"}
title={isPlaying ? "Mute music" : "Play music"}
>
{isPlaying ? <PixelSpeakerOn /> : <PixelSpeakerOff />}
</button>
);
}
// Legacy export for backwards compatibility
export function BackgroundMusic() {
return null;
}

View File

@@ -2,17 +2,24 @@
import Link from "next/link";
import { useTranslations } from "next-intl";
import { Home, Map, Star } from "lucide-react";
import { Button } from "@/components/ui/button";
import { useEffect, useState } from "react";
import { getTotalStars, getCompletedLevelsCount } from "@/lib/kids/progress";
import { getTotalLevels } from "@/lib/kids/levels";
import { getTotalLevels, getLevelBySlug } from "@/lib/kids/levels";
import { PixelStar, PixelRobot } from "@/components/kids/elements/pixel-art";
import { MusicButton } from "./background-music";
import { SettingsButton } from "./settings-modal";
import { useLevelSlug } from "@/components/kids/providers/level-context";
export function KidsHeader() {
const t = useTranslations("kids");
const [stars, setStars] = useState(0);
const [completed, setCompleted] = useState(0);
const total = getTotalLevels();
// Get current level from context (will be empty if not in a level)
const levelSlug = useLevelSlug();
const currentLevel = levelSlug ? getLevelBySlug(levelSlug) : null;
const levelNumber = currentLevel ? `${currentLevel.world}.${currentLevel.levelNumber}` : null;
useEffect(() => {
setStars(getTotalStars());
@@ -20,54 +27,97 @@ export function KidsHeader() {
}, []);
return (
<header className="sticky top-0 z-50 w-full border-b bg-white/80 dark:bg-background/80 backdrop-blur supports-[backdrop-filter]:bg-white/60">
<div className="container flex h-16 items-center justify-between">
<header className="shrink-0 z-50 w-full bg-[#2C1810] border-b-4 border-[#8B4513]">
<div className="container flex h-14 items-center justify-between px-4">
{/* Logo */}
<Link href="/kids" className="flex items-center gap-2 font-bold text-xl">
<span className="text-2xl">🤖</span>
<span className="bg-gradient-to-r from-primary to-purple-500 bg-clip-text text-transparent">
<Link href="/kids" className="flex items-center gap-2">
<PixelRobot className="w-8 h-10" />
<span className="text-[#FFD700] font-bold text-2xl pixel-text-shadow hidden sm:block">
{t("header.title")}
</span>
</Link>
{/* Stats & Nav */}
<div className="flex items-center gap-4">
<div className="flex items-center gap-3">
{/* Current level indicator */}
{levelNumber && (
<div className="flex items-center gap-1 px-3 h-8 bg-[#FFD700] border-2 border-[#DAA520] pixel-border-sm">
<span className="text-[#8B4513] text-sm font-bold">
{t("level.levelLabel", { number: levelNumber })}
</span>
</div>
)}
{/* Stars counter */}
<div className="flex items-center gap-1 px-3 py-1.5 bg-amber-100 dark:bg-amber-900/30 rounded-full text-amber-700 dark:text-amber-300">
<Star className="h-4 w-4 fill-current" />
<span className="font-semibold text-sm">{stars}</span>
<div className="flex items-center gap-1 px-3 h-8 bg-[#4A3728] border-2 border-[#8B4513] pixel-border-sm">
<PixelStar filled className="w-4 h-4" />
<span className="text-white text-sm">{stars}</span>
</div>
{/* Progress */}
<div className="hidden sm:flex items-center gap-1 px-3 py-1.5 bg-emerald-100 dark:bg-emerald-900/30 rounded-full text-emerald-700 dark:text-emerald-300 text-sm">
<span className="font-semibold">{completed}/{total}</span>
<span className="text-emerald-600 dark:text-emerald-400">{t("header.levels")}</span>
<div className="hidden sm:flex items-center gap-1 px-3 h-8 bg-[#4A3728] border-2 border-[#8B4513] pixel-border-sm">
<span className="text-[#22C55E] text-sm">{completed}/{total}</span>
</div>
{/* Nav buttons */}
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon" asChild className="rounded-full">
<Link href="/kids">
<Home className="h-5 w-5" />
<span className="sr-only">{t("header.home")}</span>
</Link>
</Button>
<Button variant="ghost" size="icon" asChild className="rounded-full">
<Link href="/kids/map">
<Map className="h-5 w-5" />
<span className="sr-only">{t("header.map")}</span>
</Link>
</Button>
</div>
{/* Back to main site */}
<Button variant="outline" size="sm" asChild className="hidden md:flex rounded-full">
<Link href="/">
{t("header.mainSite")}
<MusicButton />
<SettingsButton />
<Link
href="/kids"
className="pixel-btn px-3 py-1.5 text-sm h-8 flex items-center"
>
<PixelHomeIcon />
</Link>
</Button>
<Link
href="/kids/map"
className="pixel-btn pixel-btn-green px-3 py-1.5 text-sm h-8 flex items-center"
>
<PixelMapIcon />
</Link>
{/* Back to main site */}
<a
href="/"
className="hidden md:flex pixel-btn pixel-btn-amber px-3 py-1.5 text-sm h-8 items-center"
>
{t("header.mainSite")}
</a>
</div>
</div>
</div>
</header>
);
}
// Pixel art home icon
function PixelHomeIcon() {
return (
<svg viewBox="0 0 16 16" className="w-5 h-5" style={{ imageRendering: "pixelated" }}>
<rect x="7" y="1" width="2" height="2" fill="currentColor" />
<rect x="5" y="3" width="6" height="2" fill="currentColor" />
<rect x="3" y="5" width="10" height="2" fill="currentColor" />
<rect x="2" y="7" width="12" height="2" fill="currentColor" />
<rect x="3" y="9" width="10" height="6" fill="currentColor" />
<rect x="6" y="11" width="4" height="4" fill="#2C1810" />
</svg>
);
}
// Pixel art pin/location icon
function PixelMapIcon() {
return (
<svg viewBox="0 0 16 16" className="w-5 h-5" style={{ imageRendering: "pixelated" }}>
{/* Pin head - circle */}
<rect x="5" y="1" width="6" height="2" fill="currentColor" />
<rect x="4" y="2" width="8" height="2" fill="currentColor" />
<rect x="3" y="3" width="10" height="4" fill="currentColor" />
<rect x="4" y="7" width="8" height="2" fill="currentColor" />
<rect x="5" y="9" width="6" height="2" fill="currentColor" />
{/* Pin point */}
<rect x="6" y="11" width="4" height="2" fill="currentColor" />
<rect x="7" y="13" width="2" height="2" fill="currentColor" />
{/* Inner highlight */}
<rect x="5" y="4" width="2" height="2" fill="rgba(255,255,255,0.4)" />
</svg>
);
}

View File

@@ -0,0 +1,252 @@
"use client";
import { useState } from "react";
import Link from "next/link";
import { useTranslations } from "next-intl";
import { cn } from "@/lib/utils";
import { analyticsKids } from "@/lib/analytics";
import { PixelRobot, PixelStar, PixelTree, PixelCastle } from "@/components/kids/elements/pixel-art";
export function KidsHomeContent() {
const t = useTranslations("kids");
const [step, setStep] = useState(0);
const totalSteps = 3;
const nextStep = () => setStep((prev) => Math.min(prev + 1, totalSteps - 1));
const prevStep = () => setStep((prev) => Math.max(prev - 1, 0));
return (
<div className="h-full flex flex-col">
{/* Main content area */}
<div className="flex-1 min-h-0 overflow-y-auto flex items-center justify-center p-4">
<div className="w-full max-w-2xl my-auto">
{/* Step 0: Welcome */}
{step === 0 && (
<div className="text-center animate-in fade-in slide-in-from-right-4 duration-300">
<div className="inline-flex items-center gap-2 px-4 py-2 bg-[#ffffff] border-2 border-[#DAA520] pixel-border-sm text-[#8B4513] text-lg mb-4">
<PixelStar filled className="w-4 h-4" />
{t("home.badge")}
</div>
<h1 className="text-5xl md:text-6xl font-bold tracking-tight mb-4 text-[#2C1810] pixel-text-shadow">
{t("home.title")}
</h1>
<p className="text-3xl md:text-4xl text-[#5D4037] mb-8">
{t("home.subtitle")}
</p>
<div className="pixel-panel p-4 md:p-6">
<div className="flex flex-col sm:flex-row items-center gap-4 md:gap-6">
<div className="shrink-0">
<PixelRobot className="w-16 h-20" />
</div>
<div className="text-center sm:text-left">
<p className="text-2xl md:text-3xl font-bold text-[#2C1810] mb-2">{t("home.promiIntro.greeting")}</p>
<p className="text-xl md:text-2xl text-[#5D4037]">
{t("home.promiIntro.message")}
</p>
</div>
</div>
</div>
</div>
)}
{/* Step 1: Features */}
{step === 1 && (
<div className="text-center animate-in fade-in slide-in-from-right-4 duration-300">
<h2 className="text-4xl md:text-5xl font-bold mb-6 text-[#2C1810] pixel-text-shadow">
{t("home.whatYouLearn")}
</h2>
<div className="grid gap-4">
<div className="pixel-panel pixel-panel-green p-4 flex items-center gap-4">
<PixelGamepad />
<div className="text-left">
<h3 className="font-bold text-2xl md:text-3xl text-[#2C1810]">{t("home.features.games.title")}</h3>
<p className="text-xl md:text-2xl text-[#5D4037]">{t("home.features.games.description")}</p>
</div>
</div>
<div className="pixel-panel pixel-panel-blue p-4 flex items-center gap-4">
<PixelBook />
<div className="text-left">
<h3 className="font-bold text-2xl md:text-3xl text-[#2C1810]">{t("home.features.stories.title")}</h3>
<p className="text-xl md:text-2xl text-[#5D4037]">{t("home.features.stories.description")}</p>
</div>
</div>
<div className="pixel-panel p-4 flex items-center gap-4">
<div className="flex">
<PixelStar filled className="w-8 h-8" />
<PixelStar filled className="w-8 h-8" />
<PixelStar filled className="w-8 h-8" />
</div>
<div className="text-left">
<h3 className="font-bold text-2xl md:text-3xl text-[#2C1810]">{t("home.features.stars.title")}</h3>
<p className="text-xl md:text-2xl text-[#5D4037]">{t("home.features.stars.description")}</p>
</div>
</div>
</div>
</div>
)}
{/* Step 2: Ready to start */}
{step === 2 && (
<div className="text-center animate-in fade-in slide-in-from-right-4 duration-300">
<div className="mb-6 flex justify-center items-end gap-4">
<PixelTree className="w-10 h-14" />
<PixelRobot className="w-16 h-20 animate-bounce-slow" />
<PixelCastle className="w-12 h-12" />
</div>
<h2 className="text-3xl md:text-4xl font-bold mb-4 text-[#2C1810] pixel-text-shadow">
{t("home.readyTitle")}
</h2>
<p className="text-xl md:text-2xl text-[#5D4037] mb-8">
{t("home.readyMessage")}
</p>
<Link
href="/kids/map"
onClick={() => analyticsKids.startGame()}
className="inline-block pixel-btn pixel-btn-green text-xl md:text-2xl px-8 py-4"
>
<span className="flex items-center gap-2">
<PixelPlayIcon />
{t("home.startButton")}
</span>
</Link>
<p className="mt-6 text-lg text-[#8B7355]">
{t("home.ageNote")}
</p>
</div>
)}
</div>
</div>
{/* Navigation footer - pixel art style */}
<div className="shrink-0 bg-[#2C1810] border-t-4 border-[#8B4513]">
<div className="container py-3 flex items-center justify-between">
{/* Back button */}
<button
onClick={prevStep}
disabled={step === 0}
className={cn(
"pixel-btn px-6 py-3 text-lg",
step === 0 && "opacity-0 pointer-events-none"
)}
>
<span className="flex items-center gap-1">
<PixelArrowLeft />
{t("navigation.back")}
</span>
</button>
{/* Step indicators - pixel style */}
<div className="flex items-center gap-2">
{Array.from({ length: totalSteps }).map((_, i) => (
<button
key={i}
onClick={() => setStep(i)}
className={cn(
"w-4 h-4 border-2 transition-all",
i === step
? "bg-[#22C55E] border-[#16A34A]"
: "bg-[#4A3728] border-[#8B4513] hover:bg-[#5D4037]"
)}
style={{ clipPath: "polygon(2px 0, calc(100% - 2px) 0, 100% 2px, 100% calc(100% - 2px), calc(100% - 2px) 100%, 2px 100%, 0 calc(100% - 2px), 0 2px)" }}
aria-label={`Go to step ${i + 1}`}
/>
))}
</div>
{/* Next button */}
{step < totalSteps - 1 ? (
<button
onClick={nextStep}
className="pixel-btn pixel-btn-green px-6 py-3 text-lg"
>
<span className="flex items-center gap-1">
{t("navigation.next")}
<PixelArrowRight />
</span>
</button>
) : (
<Link
href="/kids/map"
className="pixel-btn pixel-btn-green px-6 py-3 text-lg"
>
<span className="flex items-center gap-1">
<PixelPlayIcon />
{t("home.startButton")}
</span>
</Link>
)}
</div>
</div>
</div>
);
}
// Pixel art icons
function PixelPlayIcon() {
return (
<svg viewBox="0 0 12 12" className="w-4 h-4" style={{ imageRendering: "pixelated" }}>
<rect x="2" y="1" width="2" height="10" fill="currentColor" />
<rect x="4" y="3" width="2" height="6" fill="currentColor" />
<rect x="6" y="4" width="2" height="4" fill="currentColor" />
<rect x="8" y="5" width="2" height="2" fill="currentColor" />
</svg>
);
}
function PixelArrowLeft() {
return (
<svg viewBox="0 0 12 12" className="w-4 h-4" style={{ imageRendering: "pixelated" }}>
<rect x="4" y="5" width="6" height="2" fill="currentColor" />
<rect x="2" y="5" width="2" height="2" fill="currentColor" />
<rect x="4" y="3" width="2" height="2" fill="currentColor" />
<rect x="4" y="7" width="2" height="2" fill="currentColor" />
</svg>
);
}
function PixelArrowRight() {
return (
<svg viewBox="0 0 12 12" className="w-4 h-4" style={{ imageRendering: "pixelated" }}>
<rect x="2" y="5" width="6" height="2" fill="currentColor" />
<rect x="8" y="5" width="2" height="2" fill="currentColor" />
<rect x="6" y="3" width="2" height="2" fill="currentColor" />
<rect x="6" y="7" width="2" height="2" fill="currentColor" />
</svg>
);
}
function PixelGamepad() {
return (
<svg viewBox="0 0 24 16" className="w-10 h-7" style={{ imageRendering: "pixelated" }}>
<rect x="4" y="2" width="16" height="12" fill="#333" />
<rect x="2" y="4" width="4" height="8" fill="#333" />
<rect x="18" y="4" width="4" height="8" fill="#333" />
<rect x="6" y="6" width="2" height="4" fill="#22C55E" />
<rect x="4" y="7" width="6" height="2" fill="#22C55E" />
<rect x="16" y="6" width="2" height="2" fill="#EF4444" />
<rect x="18" y="8" width="2" height="2" fill="#3B82F6" />
</svg>
);
}
function PixelBook() {
return (
<svg viewBox="0 0 20 16" className="w-8 h-6" style={{ imageRendering: "pixelated" }}>
<rect x="2" y="1" width="16" height="14" fill="#8B4513" />
<rect x="4" y="2" width="12" height="12" fill="#FEF3C7" />
<rect x="9" y="2" width="2" height="12" fill="#D97706" />
<rect x="5" y="4" width="3" height="1" fill="#333" />
<rect x="5" y="6" width="3" height="1" fill="#333" />
<rect x="12" y="4" width="3" height="1" fill="#333" />
<rect x="12" y="6" width="3" height="1" fill="#333" />
</svg>
);
}

View File

@@ -0,0 +1,217 @@
"use client";
import { useState, Children, isValidElement, ReactNode, ReactElement, useEffect } from "react";
import { cn } from "@/lib/utils";
import Link from "next/link";
import { useTranslations } from "next-intl";
import { Section } from "@/components/kids/elements";
import { useSetLevelSlug } from "@/components/kids/providers/level-context";
import { getLevelBySlug } from "@/lib/kids/levels";
import { analyticsKids } from "@/lib/analytics";
interface LevelContentWrapperProps {
children: ReactNode;
levelSlug: string;
levelNumber: string;
}
export function LevelContentWrapper({ children, levelSlug, levelNumber }: LevelContentWrapperProps) {
const t = useTranslations("kids");
const [currentSection, setCurrentSection] = useState(0);
const setLevelSlug = useSetLevelSlug();
// Set the level slug in context when component mounts
useEffect(() => {
setLevelSlug(levelSlug);
// Track level view
const level = getLevelBySlug(levelSlug);
if (level) {
analyticsKids.viewLevel(levelSlug, level.world);
}
return () => setLevelSlug(""); // Clear when unmounting
}, [levelSlug, setLevelSlug]);
// Extract Section components from children
const sections: ReactElement[] = [];
let hasExplicitSections = false;
// First pass: check if there are explicit Section components
Children.forEach(children, (child) => {
if (isValidElement(child) && child.type === Section) {
hasExplicitSections = true;
}
});
// Second pass: collect sections
if (hasExplicitSections) {
Children.forEach(children, (child) => {
if (isValidElement(child) && child.type === Section) {
sections.push(child);
}
});
} else {
Children.forEach(children, (child) => {
if (isValidElement(child)) {
sections.push(<Section key={sections.length}>{child}</Section>);
}
});
}
// If no sections found, show coming soon
if (sections.length === 0) {
return (
<div className="h-full flex items-center justify-center">
<div className="text-center pixel-panel p-6">
<p className="text-[#5D4037] mb-4">{t("level.comingSoon")}</p>
<Link href="/kids/map" className="pixel-btn pixel-btn-green px-4 py-2 inline-flex items-center gap-2">
<PixelMapIcon />
{t("level.backToMap")}
</Link>
</div>
</div>
);
}
const totalSections = sections.length;
const isFirstSection = currentSection === 0;
const isLastSection = currentSection === totalSections - 1;
const goToNext = () => {
if (!isLastSection) {
setCurrentSection((prev) => prev + 1);
}
};
const goToPrev = () => {
if (!isFirstSection) {
setCurrentSection((prev) => prev - 1);
}
};
// Reset to first section when level changes
useEffect(() => {
setCurrentSection(0);
}, [levelSlug]);
return (
<div className="h-full flex flex-col">
{/* Content area */}
<div className="flex-1 min-h-0 overflow-y-auto flex items-center justify-center p-4">
<div className="w-full max-w-2xl my-auto">
<div
key={currentSection}
className="animate-in fade-in slide-in-from-right-4 duration-300 prose max-w-none kids-prose-pixel"
>
{sections[currentSection]}
</div>
</div>
</div>
{/* Navigation footer - pixel art style */}
<div className="shrink-0 bg-[#2C1810] border-t-4 border-[#8B4513]">
<div className="max-w-2xl mx-auto py-3 px-4 flex items-center justify-between">
{/* Back button */}
<button
onClick={goToPrev}
disabled={isFirstSection}
className={cn(
"pixel-btn px-6 py-3 text-xl",
isFirstSection && "opacity-0 pointer-events-none"
)}
>
<span className="flex items-center gap-1">
<PixelArrowLeft />
{t("navigation.back")}
</span>
</button>
{/* Progress indicators - pixel style */}
<div className="flex items-center gap-2">
{Array.from({ length: totalSections }).map((_, i) => (
<button
key={i}
onClick={() => setCurrentSection(i)}
className={cn(
"w-4 h-4 border-2 transition-all",
i === currentSection
? "bg-[#22C55E] border-[#16A34A]"
: i < currentSection
? "bg-[#3B82F6] border-[#2563EB]"
: "bg-[#4A3728] border-[#8B4513] hover:bg-[#5D4037]"
)}
style={{ clipPath: "polygon(2px 0, calc(100% - 2px) 0, 100% 2px, 100% calc(100% - 2px), calc(100% - 2px) 100%, 2px 100%, 0 calc(100% - 2px), 0 2px)" }}
aria-label={`Go to section ${i + 1}`}
/>
))}
</div>
{/* Next button or Map link */}
{!isLastSection ? (
<button
onClick={goToNext}
className="pixel-btn pixel-btn-green px-6 py-3 text-xl"
>
<span className="flex items-center gap-1">
{t("navigation.next")}
<PixelArrowRight />
</span>
</button>
) : (
<Link
href="/kids/map"
className="pixel-btn pixel-btn-amber px-6 py-3 text-xl"
>
<span className="flex items-center gap-1">
<PixelMapIcon />
{t("level.map")}
</span>
</Link>
)}
</div>
</div>
</div>
);
}
// Pixel art icons
function PixelArrowLeft() {
return (
<svg viewBox="0 0 12 12" className="w-4 h-4" style={{ imageRendering: "pixelated" }}>
<rect x="4" y="5" width="6" height="2" fill="currentColor" />
<rect x="2" y="5" width="2" height="2" fill="currentColor" />
<rect x="4" y="3" width="2" height="2" fill="currentColor" />
<rect x="4" y="7" width="2" height="2" fill="currentColor" />
</svg>
);
}
function PixelArrowRight() {
return (
<svg viewBox="0 0 12 12" className="w-4 h-4" style={{ imageRendering: "pixelated" }}>
<rect x="2" y="5" width="6" height="2" fill="currentColor" />
<rect x="8" y="5" width="2" height="2" fill="currentColor" />
<rect x="6" y="3" width="2" height="2" fill="currentColor" />
<rect x="6" y="7" width="2" height="2" fill="currentColor" />
</svg>
);
}
function PixelMapIcon() {
return (
<svg viewBox="0 0 16 16" className="w-4 h-4" style={{ imageRendering: "pixelated" }}>
{/* Pin head - circle */}
<rect x="5" y="1" width="6" height="2" fill="currentColor" />
<rect x="4" y="2" width="8" height="2" fill="currentColor" />
<rect x="3" y="3" width="10" height="4" fill="currentColor" />
<rect x="4" y="7" width="8" height="2" fill="currentColor" />
<rect x="5" y="9" width="6" height="2" fill="currentColor" />
{/* Pin point */}
<rect x="6" y="11" width="4" height="2" fill="currentColor" />
<rect x="7" y="13" width="2" height="2" fill="currentColor" />
{/* Inner highlight */}
<rect x="5" y="4" width="2" height="2" fill="rgba(255,255,255,0.4)" />
</svg>
);
}

View File

@@ -0,0 +1,210 @@
"use client";
import { useState } from "react";
import { useTranslations, useLocale } from "next-intl";
import { cn } from "@/lib/utils";
import { clearAllProgress, getTotalStars, getCompletedLevelsCount } from "@/lib/kids/progress";
import { setLocale } from "@/lib/i18n/client";
import { analyticsKids } from "@/lib/analytics";
import { Settings, X, Globe, Trash2, Check } from "lucide-react";
const SUPPORTED_LOCALES = [
{ code: "en", label: "English", flag: "🇺🇸" },
{ code: "zh", label: "中文", flag: "🇨🇳" },
{ code: "es", label: "Español", flag: "🇪🇸" },
{ code: "pt", label: "Português", flag: "🇧🇷" },
{ code: "fr", label: "Français", flag: "🇫🇷" },
{ code: "de", label: "Deutsch", flag: "🇩🇪" },
{ code: "it", label: "Italiano", flag: "🇮🇹" },
{ code: "ja", label: "日本語", flag: "🇯🇵" },
{ code: "tr", label: "Türkçe", flag: "🇹🇷" },
{ code: "az", label: "Azərbaycan", flag: "🇦🇿" },
{ code: "ko", label: "한국어", flag: "🇰🇷" },
{ code: "ar", label: "العربية", flag: "🇸🇦" },
{ code: "fa", label: "فارسی", flag: "🇮🇷" },
{ code: "ru", label: "Русский", flag: "🇷🇺" },
{ code: "he", label: "עברית", flag: "🇮🇱" },
{ code: "el", label: "Ελληνικά", flag: "🇬🇷" },
];
export function SettingsButton() {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<button
onClick={() => {
setIsOpen(true);
analyticsKids.openSettings();
}}
className="pixel-btn pixel-btn-purple px-3 py-1.5 text-sm h-8 flex items-center"
aria-label="Settings"
>
<PixelSettingsIcon />
</button>
{isOpen && <SettingsModal onClose={() => setIsOpen(false)} />}
</>
);
}
function SettingsModal({ onClose }: { onClose: () => void }) {
const t = useTranslations("kids.settings");
const currentLocale = useLocale();
const [showResetConfirm, setShowResetConfirm] = useState(false);
const [resetComplete, setResetComplete] = useState(false);
const stars = getTotalStars();
const completed = getCompletedLevelsCount();
const handleLanguageChange = (locale: string) => {
analyticsKids.changeLanguage(locale);
setLocale(locale);
};
const handleResetProgress = () => {
if (!showResetConfirm) {
setShowResetConfirm(true);
return;
}
clearAllProgress();
analyticsKids.resetProgress();
setResetComplete(true);
setShowResetConfirm(false);
// Reload to reflect changes
setTimeout(() => {
window.location.reload();
}, 1000);
};
return (
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
onClick={onClose}
/>
{/* Modal */}
<div className="relative bg-[#FEF3C7] border-4 border-[#8B4513] rounded-xl p-6 w-full max-w-md animate-in zoom-in-95 fade-in duration-200">
{/* Close button */}
<button
onClick={onClose}
className="absolute top-3 right-3 p-2 text-[#8B4513] hover:bg-[#8B4513]/10 rounded-lg"
>
<X className="w-5 h-5" />
</button>
{/* Title */}
<h2 className="text-2xl font-bold text-[#8B4513] mb-6 flex items-center gap-2">
<Settings className="w-6 h-6" />
{t("title")}
</h2>
{/* Language Section */}
<div className="mb-6">
<h3 className="text-lg font-bold text-[#5D4037] mb-3 flex items-center gap-2">
<Globe className="w-5 h-5" />
{t("language")}
</h3>
<div className="grid grid-cols-3 gap-2">
{SUPPORTED_LOCALES.map((locale) => (
<button
key={locale.code}
onClick={() => handleLanguageChange(locale.code)}
className={cn(
"p-2 rounded-lg border-2 text-sm font-medium transition-all",
currentLocale === locale.code
? "bg-[#8B4513] border-[#5D4037] text-white"
: "bg-white border-[#D4A574] text-[#5D4037] hover:border-[#8B4513]"
)}
>
<span className="text-lg">{locale.flag}</span>
<div className="text-xs mt-1">{locale.label}</div>
</button>
))}
</div>
</div>
{/* Progress Info */}
<div className="mb-6 p-4 bg-white/50 rounded-lg border-2 border-[#D4A574]">
<h3 className="text-lg font-bold text-[#5D4037] mb-2">
{t("progress")}
</h3>
<div className="flex gap-4 text-[#5D4037]">
<div>
<span className="text-2xl font-bold text-[#FFD700]"> {stars}</span>
<div className="text-xs">{t("stars")}</div>
</div>
<div>
<span className="text-2xl font-bold text-[#22C55E]"> {completed}</span>
<div className="text-xs">{t("completed")}</div>
</div>
</div>
</div>
{/* Reset Progress */}
<div className="border-t-2 border-[#D4A574] pt-4">
<h3 className="text-lg font-bold text-[#5D4037] mb-3 flex items-center gap-2">
<Trash2 className="w-5 h-5" />
{t("resetTitle")}
</h3>
{resetComplete ? (
<div className="p-3 bg-green-100 border-2 border-green-500 rounded-lg text-green-700 font-medium flex items-center gap-2">
<Check className="w-5 h-5" />
{t("resetComplete")}
</div>
) : showResetConfirm ? (
<div className="space-y-2">
<p className="text-red-600 font-medium text-sm">
{t("resetWarning")}
</p>
<div className="flex gap-2">
<button
onClick={handleResetProgress}
className="flex-1 py-2 px-4 bg-red-500 hover:bg-red-600 text-white font-bold rounded-lg"
>
{t("resetConfirm")}
</button>
<button
onClick={() => setShowResetConfirm(false)}
className="flex-1 py-2 px-4 bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold rounded-lg"
>
{t("cancel")}
</button>
</div>
</div>
) : (
<button
onClick={handleResetProgress}
className="w-full py-2 px-4 bg-red-100 hover:bg-red-200 text-red-700 font-bold rounded-lg border-2 border-red-300"
>
{t("resetButton")}
</button>
)}
</div>
</div>
</div>
);
}
// Pixel art settings/gear icon
function PixelSettingsIcon() {
return (
<svg viewBox="0 0 16 16" className="w-5 h-5" style={{ imageRendering: "pixelated" }}>
<rect x="6" y="0" width="4" height="2" fill="currentColor" />
<rect x="6" y="14" width="4" height="2" fill="currentColor" />
<rect x="0" y="6" width="2" height="4" fill="currentColor" />
<rect x="14" y="6" width="2" height="4" fill="currentColor" />
<rect x="2" y="2" width="2" height="2" fill="currentColor" />
<rect x="12" y="2" width="2" height="2" fill="currentColor" />
<rect x="2" y="12" width="2" height="2" fill="currentColor" />
<rect x="12" y="12" width="2" height="2" fill="currentColor" />
<rect x="4" y="4" width="8" height="8" fill="currentColor" />
<rect x="6" y="6" width="4" height="4" fill="#2C1810" />
</svg>
);
}

View File

@@ -0,0 +1,46 @@
"use client";
import { createContext, useContext, useState, useEffect, ReactNode } from "react";
interface LevelContextType {
levelSlug: string;
setLevelSlug: (slug: string) => void;
}
const LevelContext = createContext<LevelContextType>({
levelSlug: "",
setLevelSlug: () => {},
});
export function LevelProvider({
children,
levelSlug: initialSlug = ""
}: {
children: ReactNode;
levelSlug?: string;
}) {
const [levelSlug, setLevelSlug] = useState(initialSlug);
// Update if initialSlug changes
useEffect(() => {
if (initialSlug) {
setLevelSlug(initialSlug);
}
}, [initialSlug]);
return (
<LevelContext.Provider value={{ levelSlug, setLevelSlug }}>
{children}
</LevelContext.Provider>
);
}
export function useLevelSlug(): string {
const context = useContext(LevelContext);
return context.levelSlug;
}
export function useSetLevelSlug(): (slug: string) => void {
const context = useContext(LevelContext);
return context.setLevelSlug;
}

View File

@@ -1,3 +1,4 @@
<Section>
<Panel character="promi" mood="happy">
مرحباً! أنا **Promi** 🤖، صديقك الروبوت! سعيد جداً بلقائك!
</Panel>
@@ -9,7 +10,9 @@
<Panel character="promi" mood="excited">
أنا ذكاء اصطناعي! يمكنني قراءة رسائلك ومحاولة مساعدتك. لكن هناك سر... أحتاج **تعليمات جيدة** لأقوم بأفضل عمل!
</Panel>
</Section>
<Section>
## ما هو البرومبت؟
**البرومبت** هو كلمة راقية للرسالة التي ترسلها لذكاء اصطناعي مثلي.
@@ -19,7 +22,9 @@
<Panel character="promi" mood="happy">
عندما تكتب برومبت جيد، أستطيع فهم ما تريد ومساعدتك بشكل أفضل! هيا نتدرب!
</Panel>
</Section>
<Section>
## لنجرب!
<PromptVsMistake
@@ -29,7 +34,9 @@
explanation="الرسالة الأولى تخبر Promi بالضبط أي نوع من القصص يكتب! الثانية قصيرة جداً - Promi لا يعرف أي نوع من القصص تريد."
promiMessage="رأيت؟ المزيد من التفاصيل يساعدني على فهم ما تريد!"
/>
</Section>
<Section>
## اختبار سريع!
<PromptVsMistake
@@ -39,15 +46,20 @@
explanation="البرومبت يستخدم الكلمات لإخبار الذكاء الاصطناعي ما تحتاجه. الإيموجي ممتعة لكنها لا تعطي معلومات كافية!"
promiMessage="الكلمات هي قوتي الخارقة! كلما أخبرتني أكثر، استطعت المساعدة أفضل!"
/>
</Section>
<Section>
## نجحت! 🎉
<Panel character="promi" mood="celebrating">
عمل رائع! تعلمت ما هو الذكاء الاصطناعي وما هو البرومبت. أنت تصبح خبيراً في البرومبت!
</Panel>
<LevelComplete
levelSlug="1-1-meet-promi"
stars={3}
message="تعلمت ما هو الذكاء الاصطناعي والبرومبت!"
/>
</Section>

View File

@@ -1,7 +1,10 @@
<Section>
<Panel character="promi" mood="happy">
مرحباً بعودتك يا صديقي! مستعد لكتابة أول برومبت حقيقي؟ هيا بنا! 🚀
</Panel>
</Section>
<Section>
## سحر الكلمات
عندما تتحدث مع الذكاء الاصطناعي، كل كلمة مهمة! دعنا نرى كيف إضافة المزيد من الكلمات تجعل البرومبت أفضل.
@@ -9,7 +12,9 @@
<Panel character="promi" mood="thinking">
انظر! إذا قال لي شخص فقط "قطة"، لا أعرف ماذا يريدون. هل يريدون صورة؟ قصة؟ حقائق عن القطط؟ أنا مرتبك! 😵‍💫
</Panel>
</Section>
<Section>
## بناء برومبت أفضل
البرومبت الجيد له **ثلاثة أجزاء**:
@@ -21,7 +26,9 @@
<Panel character="promi" mood="excited">
هيا نبني برومبت معاً!
</Panel>
</Section>
<Section>
## اسحب القطع!
<DragDropPrompt
@@ -36,11 +43,14 @@
correctOrder={[0, 1, 2, 3]}
successMessage="ممتاز! هذا برومبت رائع!"
/>
</Section>
<Section>
## املأ الفراغات!
الآن حاول صنع برومبتك بسحب الكلمات السحرية:
<MagicWords
title="أنشئ برومبتك! ✨"
sentence="من فضلك اكتب {{type}} عن {{character}} الذي {{action}}"
@@ -51,7 +61,9 @@
]}
successMessage="واو! صنعت برومبت رائع!"
/>
</Section>
<Section>
## دورك للاختيار!
<PromptVsMistake
@@ -61,15 +73,20 @@
explanation="البرومبت الأول يخبرني أنها يجب أن تكون مضحكة، عن بطريق، وماذا يريد البطريق أن يفعل!"
promiMessage="التفاصيل تجعل كل شيء أفضل! أحب معرفة بالضبط ما تريد!"
/>
</Section>
<Section>
## عمل رائع! 🌟
<Panel character="promi" mood="celebrating">
كتبت أول برومبت لك! تعلمت أن البرومبت الجيد يحتاج: ما تريد، موضوع، وتفاصيل. أنت تتحسن!
</Panel>
<LevelComplete
levelSlug="1-2-first-words"
stars={3}
message="تعلمت كيف تكتب أول برومبت!"
/>
</Section>

View File

@@ -1,7 +1,10 @@
<Section>
<Panel character="promi" mood="happy">
مرحباً يا نجم! 🌟 اليوم سنتعلم أهم مهارة: أن تكون **واضحاً**!
</Panel>
</Section>
<Section>
## لماذا الوضوح مهم
تخيل أن تطلب من أمك "طعام" مقابل طلب "ساندويتش زبدة فول سوداني بدون قشرة." أيهما يعطيك بالضبط ما تريد؟
@@ -9,11 +12,14 @@
<Panel character="promi" mood="thinking">
معي نفس الشيء! عندما تكون واضحاً، أعرف بالضبط كيف أساعد. عندما تكون غامضاً، علي أن أخمن... وقد أخمن خطأ!
</Panel>
</Section>
<Section>
## واضح مقابل غير واضح
هيا نتدرب على اكتشاف الفرق!
<PromptVsMistake
question="أي برومبت أوضح؟"
good="اكتب قصيدة من 4 أسطر عن الفراشات في حديقة، باستخدام كلمات مقفاة"
@@ -22,6 +28,7 @@
promiMessage="برومبت واضح = نتائج أفضل! إنه مثل السحر!"
/>
<PromptVsMistake
question="أيهما يخبرني بالضبط ما تحتاج؟"
good="ساعدني في كتابة 3 حقائق ممتعة عن الدلافين يستمتع بها طفل عمره 10 سنوات"
@@ -29,11 +36,14 @@
explanation="البرومبت الأول يخبرني: كم حقيقة (3)، أي نوع (ممتعة)، ولمن (طفل 10 سنوات). هذا يساعد كثيراً!"
promiMessage="عندما تخبرني لمن، أستطيع جعله مثالياً لهم!"
/>
</Section>
<Section>
## تحدي الوضوح
هيا نبني أوضح برومبت على الإطلاق!
<DragDropPrompt
title="اجعله واضحاً كالكريستال! 💎"
instruction="رتب هذه القطع لصنع برومبت واضح جداً"
@@ -47,7 +57,9 @@
correctOrder={[0, 1, 2, 3, 4]}
successMessage="هذا أوضح برومبت على الإطلاق! مذهل!"
/>
</Section>
<Section>
## أضف تفاصيل واضحة
<MagicWords
@@ -61,7 +73,9 @@
]}
successMessage="أضفت كل التفاصيل المهمة! عمل رائع!"
/>
</Section>
<Section>
## القواعد الذهبية للوضوح
<Panel character="promi" mood="excited">
@@ -74,6 +88,7 @@
2. **كيف** يجب أن يكون؟ (قصير، مضحك، بسيط)
3. **لمن** هو؟ (أنا، صديقي، صفي)
<PromptVsMistake
question="التحدي الأخير! أيهما يستخدم القواعد الثلاث كلها؟"
good="اكتب نكتة قصيرة ومضحكة عن البيتزا يمكنني إخبارها لأصدقائي في الغداء"
@@ -81,7 +96,9 @@
explanation="البرومبت الرائع يحتوي على ماذا (نكتة عن البيتزا)، كيف (قصيرة ومضحكة)، ولمن (لإخبار الأصدقاء في الغداء)!"
promiMessage="أنت بطل الوضوح! 🏆"
/>
</Section>
<Section>
## العالم 1 اكتمل! 🎊
<Panel character="promi" mood="celebrating">
@@ -94,8 +111,11 @@
أنت جاهز لمغامرات جديدة!
</Panel>
<LevelComplete
levelSlug="1-3-being-clear"
stars={3}
message="أتقنت فن الوضوح! العالم 1 اكتمل!"
/>
</Section>

View File

@@ -1,3 +1,4 @@
<Section>
<Panel character="promi" mood="happy">
Salam! Mən **Promi** 🤖, sənin robot dostun! Səninlə tanış olmağıma çox şadam!
</Panel>
@@ -9,7 +10,9 @@ Salam! Mən **Promi** 🤖, sənin robot dostun! Səninlə tanış olmağıma ç
<Panel character="promi" mood="excited">
Mən AI-yam! Sənin mesajlarını oxuya və kömək etməyə çalışa bilərəm. Amma budur sirr... Ən yaxşı işi görmək üçün **yaxşı təlimatlara** ehtiyacım var!
</Panel>
</Section>
<Section>
## Prompt Nədir?
**Prompt** sadəcə mənim kimi AI-ya göndərdiyin mesaj deməkdir.
@@ -19,7 +22,9 @@ Bunu dostuna yol göstərmək kimi düşün. "Ora get!" desən, dostun hara getm
<Panel character="promi" mood="happy">
Yaxşı prompt yazdığında, nə istədiyini başa düşə və sənə daha yaxşı kömək edə bilərəm! Gəl məşq edək!
</Panel>
</Section>
<Section>
## Gəl Cəhd Edək!
<PromptVsMistake
@@ -29,7 +34,9 @@ Yaxşı prompt yazdığında, nə istədiyini başa düşə və sənə daha yax
explanation="Birinci mesaj Promi-yə dəqiq hansı növ hekayə yazacağını deyir! İkincisi çox qısadır - Promi hansı növ hekayə istədiyini bilmir."
promiMessage="Gördün? Daha çox detal nə istədiyini başa düşməyimə kömək edir!"
/>
</Section>
<Section>
## Sürətli Test!
<PromptVsMistake
@@ -39,15 +46,20 @@ Yaxşı prompt yazdığında, nə istədiyini başa düşə və sənə daha yax
explanation="Prompt AI-ya nəyə ehtiyacın olduğunu söyləmək üçün sözlər istifadə edir. Emojilər əyləncəlidir amma kifayət qədər məlumat vermir!"
promiMessage="Sözlər mənim super gücümdür! Mənə nə qədər çox desən, o qədər yaxşı kömək edə bilərəm!"
/>
</Section>
<Section>
## Bacardın! 🎉
<Panel character="promi" mood="celebrating">
Əla iş! AI-ın nə olduğunu və promptun nə olduğunu öyrəndin. Artıq prompt mütəxəssisi olursan!
</Panel>
<LevelComplete
levelSlug="1-1-meet-promi"
stars={3}
message="AI və promptların nə olduğunu öyrəndin!"
/>
</Section>

View File

@@ -1,7 +1,10 @@
<Section>
<Panel character="promi" mood="happy">
Xoş gəldin geri, dost! İlk əsl promptlarını yazmağa hazırsan? Gedək! 🚀
</Panel>
</Section>
<Section>
## Sözlərin Sehri
AI ilə danışanda hər söz önəmlidir! Gəl görək daha çox söz əlavə etmək promptları necə yaxşılaşdırır.
@@ -9,7 +12,9 @@ AI ilə danışanda hər söz önəmlidir! Gəl görək daha çox söz əlavə e
<Panel character="promi" mood="thinking">
Bax! Kimsə mənə sadəcə "pişik" desə, nə istədiklərini bilmirəm. Şəkil istəyirlər? Hekayə? Pişiklər haqqında faktlar? Çaşmışam! 😵‍💫
</Panel>
</Section>
<Section>
## Daha Yaxşı Promptlar Qurmaq
Yaxşı promptun **üç hissəsi** var:
@@ -21,7 +26,9 @@ Yaxşı promptun **üç hissəsi** var:
<Panel character="promi" mood="excited">
Gəl birlikdə prompt quraq!
</Panel>
</Section>
<Section>
## Parçaları Sürüklə!
<DragDropPrompt
@@ -36,11 +43,14 @@ Gəl birlikdə prompt quraq!
correctOrder={[0, 1, 2, 3]}
successMessage="Mükəmməl! Bu əla promptdur!"
/>
</Section>
<Section>
## Boşluqları Doldur!
İndi sehrli sözləri sürükləyərək öz promptunu etməyə çalış:
<MagicWords
title="Öz promptunu yarat! ✨"
sentence="Zəhmət olmasa {{type}} yaz, {{character}} haqqında, {{action}}"
@@ -51,7 +61,9 @@ Gəl birlikdə prompt quraq!
]}
successMessage="Vay! Möhtəşəm prompt yaratdın!"
/>
</Section>
<Section>
## Seçmə Növbəsi Səndədir!
<PromptVsMistake
@@ -61,15 +73,20 @@ Gəl birlikdə prompt quraq!
explanation="Birinci prompt mənə gülməli olmalı olduğunu, pinqvin haqqında olduğunu VƏ pinqvinin nə etmək istədiyini deyir!"
promiMessage="Detaillar hər şeyi daha yaxşı edir! Dəqiq nə istədiyini bilməyi sevirəm!"
/>
</Section>
<Section>
## Əla İş! 🌟
<Panel character="promi" mood="celebrating">
İlk promptlarını yazdın! Yaxşı promptlara lazım olanı öyrəndin: nə istəyirsən, mövzu və detaillar. Həqiqətən yaxşılaşırsan!
</Panel>
<LevelComplete
levelSlug="1-2-first-words"
stars={3}
message="İlk promptlarını yazmağı öyrəndin!"
/>
</Section>

View File

@@ -1,7 +1,10 @@
<Section>
<Panel character="promi" mood="happy">
Salam superstar! 🌟 Bu gün ən vacib bacarığı öyrənəcəyik: **AYDIN** olmaq!
</Panel>
</Section>
<Section>
## Niyə Aydın Olmaq Vacibdir
Anandan "yemək" istəməklə "qabıqsız fıstıq yağı sendviçi" istəməyin fərqini təsəvvür et. Hansı sənə dəqiq istədiyini verir?
@@ -9,11 +12,14 @@ Anandan "yemək" istəməklə "qabıqsız fıstıq yağı sendviçi" istəməyin
<Panel character="promi" mood="thinking">
Mənimlə eynidir! Aydın olanda, dəqiq necə kömək edəcəyimi bilirəm. Qeyri-müəyyən olanda, təxmin etməliyəm... və səhv edə bilərəm!
</Panel>
</Section>
<Section>
## Aydın vs. Aydın Deyil
Gəl fərqi tapmağı məşq edək!
<PromptVsMistake
question="Hansı prompt daha aydındır?"
good="Bağçada kəpənəklər haqqında qafiyəli sözlərlə 4 sətirlik şeir yaz"
@@ -22,6 +28,7 @@ Gəl fərqi tapmağı məşq edək!
promiMessage="Aydın promptlar = daha yaxşı nəticələr! Bu sehr kimidir!"
/>
<PromptVsMistake
question="Hansı mənə dəqiq nəyə ehtiyacın olduğunu deyir?"
good="10 yaşlı uşağın bəyənəcəyi delfinlər haqqında 3 əyləncəli fakt yazmağıma kömək et"
@@ -29,11 +36,14 @@ Gəl fərqi tapmağı məşq edək!
explanation="Birinci prompt mənə deyir: neçə fakt (3), hansı növ (əyləncəli), və kimin üçün (10 yaşlı). Bu çox kömək edir!"
promiMessage="Kimin üçün olduğunu desən, onlar üçün mükəmməl edə bilərəm!"
/>
</Section>
<Section>
## Aydınlıq Çağırışı
Gəl ən aydın promptu quraq!
<DragDropPrompt
title="Kristal kimi aydın et! 💎"
instruction="Super aydın prompt etmək üçün bu parçaları düzəlt"
@@ -47,7 +57,9 @@ Gəl ən aydın promptu quraq!
correctOrder={[0, 1, 2, 3, 4]}
successMessage="Bu indiyə qədər ən aydın promptdur! Möhtəşəm!"
/>
</Section>
<Section>
## Aydın Detaillar Əlavə Et
<MagicWords
@@ -61,7 +73,9 @@ Gəl ən aydın promptu quraq!
]}
successMessage="Bütün vacib detailları əlavə etdin! Əla iş!"
/>
</Section>
<Section>
## Aydınlığın Qızıl Qaydaları
<Panel character="promi" mood="excited">
@@ -74,6 +88,7 @@ Prompt yazanda bu üç sualı xatırla:
2. **NECƏ** olmalıdır? (qısa, gülməli, sadə)
3. **KİM** üçündür? (mən, dostum, sinifim)
<PromptVsMistake
question="Son çağırış! Hansı üç qaydanın hamısını istifadə edir?"
good="Nahar vaxtı dostlarıma deyə biləcəyim pizza haqqında qısa, gülməli zarafat yaz"
@@ -81,7 +96,9 @@ Prompt yazanda bu üç sualı xatırla:
explanation="Əla promptda NƏ (pizza zarafatı), NECƏ (qısa və gülməli), və KİM üçün (naharda dostlara demək) var!"
promiMessage="Sən aydınlıq çempionusan! 🏆"
/>
</Section>
<Section>
## Dünya 1 Tamamlandı! 🎊
<Panel character="promi" mood="celebrating">
@@ -94,8 +111,11 @@ VAY! Bütün Dünya 1-i bitirdin! Öyrəndiklərin:
Yeni macəralara hazırsan!
</Panel>
<LevelComplete
levelSlug="1-3-being-clear"
stars={3}
message="Aydın olmaq sənətini mənimsədin! Dünya 1 tamamlandı!"
/>
</Section>

View File

@@ -1,3 +1,4 @@
<Section>
<Panel character="promi" mood="happy">
Hallo! Ich bin **Promi** 🤖, dein Roboter-Freund! Ich freue mich so, dich kennenzulernen!
</Panel>
@@ -9,7 +10,9 @@ Weißt du, was **KI** bedeutet? KI steht für **Künstliche Intelligenz**. Das i
<Panel character="promi" mood="excited">
Ich bin eine KI! Ich kann deine Nachrichten lesen und versuchen dir zu helfen. Aber hier ist das Geheimnis... Ich brauche **gute Anweisungen** um meine beste Arbeit zu machen!
</Panel>
</Section>
<Section>
## Was ist ein Prompt?
Ein **Prompt** ist einfach ein schickes Wort für die Nachricht, die du an eine KI wie mich sendest.
@@ -19,7 +22,9 @@ Stell dir vor, du gibst einem Freund Wegbeschreibungen. Wenn du sagst "Geh dorth
<Panel character="promi" mood="happy">
Wenn du einen guten Prompt schreibst, kann ich verstehen, was du willst und dir besser helfen! Lass uns üben!
</Panel>
</Section>
<Section>
## Probieren wir es!
<PromptVsMistake
@@ -29,7 +34,9 @@ Wenn du einen guten Prompt schreibst, kann ich verstehen, was du willst und dir
explanation="Die erste Nachricht sagt Promi genau, welche Art von Geschichte geschrieben werden soll! Die zweite ist zu kurz - Promi weiß nicht, welche Art von Geschichte du willst."
promiMessage="Siehst du? Mehr Details helfen mir zu verstehen, was du willst!"
/>
</Section>
<Section>
## Schnelles Quiz!
<PromptVsMistake
@@ -39,15 +46,20 @@ Wenn du einen guten Prompt schreibst, kann ich verstehen, was du willst und dir
explanation="Ein Prompt benutzt Worte, um der KI zu sagen, was du brauchst. Emojis sind lustig, aber geben nicht genug Informationen!"
promiMessage="Worte sind meine Superkraft! Je mehr du mir sagst, desto besser kann ich helfen!"
/>
</Section>
<Section>
## Du hast es geschafft! 🎉
<Panel character="promi" mood="celebrating">
Tolle Arbeit! Du hast gelernt, was KI ist und was ein Prompt ist. Du wirst schon ein Prompt-Experte!
</Panel>
<LevelComplete
levelSlug="1-1-meet-promi"
stars={3}
message="Du hast gelernt, was KI und Prompts sind!"
/>
</Section>

View File

@@ -1,7 +1,10 @@
<Section>
<Panel character="promi" mood="happy">
Willkommen zurück, Freund! Bereit, deine ersten echten Prompts zu schreiben? Los geht's! 🚀
</Panel>
</Section>
<Section>
## Die Magie der Worte
Wenn du mit KI sprichst, zählt jedes Wort! Lass uns sehen, wie mehr Worte Prompts besser machen.
@@ -9,7 +12,9 @@ Wenn du mit KI sprichst, zählt jedes Wort! Lass uns sehen, wie mehr Worte Promp
<Panel character="promi" mood="thinking">
Schau mal! Wenn jemand nur "Katze" zu mir sagt, weiß ich nicht, was sie wollen. Wollen sie ein Bild? Eine Geschichte? Fakten über Katzen? Ich bin verwirrt! 😵‍💫
</Panel>
</Section>
<Section>
## Bessere Prompts Bauen
Ein guter Prompt hat **drei Teile**:
@@ -21,7 +26,9 @@ Ein guter Prompt hat **drei Teile**:
<Panel character="promi" mood="excited">
Lass uns zusammen einen Prompt bauen!
</Panel>
</Section>
<Section>
## Ziehe die Teile!
<DragDropPrompt
@@ -36,11 +43,14 @@ Lass uns zusammen einen Prompt bauen!
correctOrder={[0, 1, 2, 3]}
successMessage="Perfekt! Das ist ein toller Prompt!"
/>
</Section>
<Section>
## Fülle die Lücken!
Versuche jetzt, deinen eigenen Prompt zu machen, indem du die Zauberwörter ziehst:
<MagicWords
title="Erstelle deinen eigenen Prompt! ✨"
sentence="Bitte schreibe eine {{type}} über einen {{character}}, der {{action}}"
@@ -51,7 +61,9 @@ Versuche jetzt, deinen eigenen Prompt zu machen, indem du die Zauberwörter zieh
]}
successMessage="Wow! Du hast einen tollen Prompt erstellt!"
/>
</Section>
<Section>
## Du bist dran!
<PromptVsMistake
@@ -61,15 +73,20 @@ Versuche jetzt, deinen eigenen Prompt zu machen, indem du die Zauberwörter zieh
explanation="Der erste Prompt sagt mir, es soll lustig sein, es geht um einen Pinguin, UND was der Pinguin machen will!"
promiMessage="Details machen alles besser! Ich liebe es, genau zu wissen, was du willst!"
/>
</Section>
<Section>
## Tolle Arbeit! 🌟
<Panel character="promi" mood="celebrating">
Du hast deine ersten Prompts geschrieben! Du hast gelernt, dass gute Prompts brauchen: was du willst, ein Thema und Details. Du wirst wirklich gut darin!
</Panel>
<LevelComplete
levelSlug="1-2-first-words"
stars={3}
message="Du hast gelernt, wie man erste Prompts schreibt!"
/>
</Section>

View File

@@ -1,7 +1,10 @@
<Section>
<Panel character="promi" mood="happy">
Hey Superstar! 🌟 Heute lernen wir die wichtigste Fähigkeit: **KLAR** sein!
</Panel>
</Section>
<Section>
## Warum Klar Sein Wichtig Ist
Stell dir vor, du bittest deine Mama um "Essen" vs. um "ein Erdnussbutter-Sandwich ohne Kruste." Was gibt dir genau das, was du willst?
@@ -9,11 +12,14 @@ Stell dir vor, du bittest deine Mama um "Essen" vs. um "ein Erdnussbutter-Sandwi
<Panel character="promi" mood="thinking">
Bei mir ist es genauso! Wenn du klar bist, weiß ich genau, wie ich helfen kann. Wenn du vage bist, muss ich raten... und ich könnte falsch raten!
</Panel>
</Section>
<Section>
## Klar vs. Unklar
Lass uns üben, den Unterschied zu erkennen!
<PromptVsMistake
question="Welcher Prompt ist klarer?"
good="Schreibe ein 4-zeiliges Gedicht über Schmetterlinge in einem Garten mit reimenden Worten"
@@ -22,6 +28,7 @@ Lass uns üben, den Unterschied zu erkennen!
promiMessage="Klare Prompts = bessere Ergebnisse! Es ist wie Magie!"
/>
<PromptVsMistake
question="Welcher sagt mir genau, was du brauchst?"
good="Hilf mir, 3 lustige Fakten über Delfine zu schreiben, die ein 10-Jähriger mögen würde"
@@ -29,11 +36,14 @@ Lass uns üben, den Unterschied zu erkennen!
explanation="Der erste Prompt sagt mir: wie viele Fakten (3), welche Art (lustig), und für wen (10-Jähriger). Das hilft sehr!"
promiMessage="Wenn du mir sagst, für wen es ist, kann ich es perfekt für sie machen!"
/>
</Section>
<Section>
## Die Klarheits-Herausforderung
Lass uns den klarsten Prompt aller Zeiten bauen!
<DragDropPrompt
title="Mach es kristallklar! 💎"
instruction="Ordne diese Teile an, um einen super klaren Prompt zu machen"
@@ -47,7 +57,9 @@ Lass uns den klarsten Prompt aller Zeiten bauen!
correctOrder={[0, 1, 2, 3, 4]}
successMessage="Das ist der klarste Prompt aller Zeiten! Toll!"
/>
</Section>
<Section>
## Füge Klare Details Hinzu
<MagicWords
@@ -61,7 +73,9 @@ Lass uns den klarsten Prompt aller Zeiten bauen!
]}
successMessage="Du hast alle wichtigen Details hinzugefügt! Gute Arbeit!"
/>
</Section>
<Section>
## Die Goldenen Regeln der Klarheit
<Panel character="promi" mood="excited">
@@ -74,6 +88,7 @@ Erinnere dich an diese drei Fragen, wenn du einen Prompt schreibst:
2. **WIE** soll es sein? (kurz, lustig, einfach)
3. **FÜR WEN** ist es? (mich, meinen Freund, meine Klasse)
<PromptVsMistake
question="Letzte Herausforderung! Welcher benutzt alle drei Regeln?"
good="Schreibe einen kurzen, lustigen Witz über Pizza, den ich meinen Freunden beim Mittagessen erzählen kann"
@@ -81,7 +96,9 @@ Erinnere dich an diese drei Fragen, wenn du einen Prompt schreibst:
explanation="Der tolle Prompt hat WAS (ein Witz über Pizza), WIE (kurz und lustig), und FÜR WEN (Freunden beim Mittagessen erzählen)!"
promiMessage="Du bist ein Klarheits-Champion! 🏆"
/>
</Section>
<Section>
## Welt 1 Abgeschlossen! 🎊
<Panel character="promi" mood="celebrating">
@@ -94,8 +111,11 @@ WOW! Du hast die ganze Welt 1 beendet! Du hast gelernt:
Du bist bereit für neue Abenteuer!
</Panel>
<LevelComplete
levelSlug="1-3-being-clear"
stars={3}
message="Du hast die Kunst des Klar-Seins gemeistert! Welt 1 abgeschlossen!"
/>
</Section>

View File

@@ -1,3 +1,4 @@
<Section>
<Panel character="promi" mood="happy">
Γεια! Είμαι ο **Promi** 🤖, ο ρομπότ φίλος σου! Χαίρομαι πολύ που σε γνωρίζω!
</Panel>
@@ -9,7 +10,9 @@
<Panel character="promi" mood="excited">
Είμαι AI! Μπορώ να διαβάζω τα μηνύματά σου και να προσπαθώ να βοηθήσω. Αλλά να το μυστικό... Χρειάζομαι **καλές οδηγίες** για να κάνω τη καλύτερη δουλειά!
</Panel>
</Section>
<Section>
## Τι είναι ένα Prompt;
Ένα **prompt** είναι απλά μια κομψή λέξη για το μήνυμα που στέλνεις σε ένα AI σαν εμένα.
@@ -19,7 +22,9 @@
<Panel character="promi" mood="happy">
Όταν γράφεις καλό prompt, μπορώ να καταλάβω τι θέλεις και να σε βοηθήσω καλύτερα! Ας εξασκηθούμε!
</Panel>
</Section>
<Section>
## Ας Δοκιμάσουμε!
<PromptVsMistake
@@ -29,7 +34,9 @@
explanation="Το πρώτο μήνυμα λέει στον Promi ακριβώς τι είδους ιστορία να γράψει! Το δεύτερο είναι πολύ μικρό - ο Promi δεν ξέρει τι είδους ιστορία θέλεις."
promiMessage="Βλέπεις; Περισσότερες λεπτομέρειες με βοηθούν να καταλάβω τι θέλεις!"
/>
</Section>
<Section>
## Γρήγορο Κουίζ!
<PromptVsMistake
@@ -39,15 +46,20 @@
explanation="Ένα prompt χρησιμοποιεί λέξεις για να πει στο AI τι χρειάζεσαι. Τα emoji είναι διασκεδαστικά αλλά δεν δίνουν αρκετές πληροφορίες!"
promiMessage="Οι λέξεις είναι η υπερδύναμή μου! Όσο περισσότερα μου λες, τόσο καλύτερα μπορώ να βοηθήσω!"
/>
</Section>
<Section>
## Τα Κατάφερες! 🎉
<Panel character="promi" mood="celebrating">
Καταπληκτική δουλειά! Έμαθες τι είναι το AI και τι είναι prompt. Γίνεσαι ήδη ειδικός στα prompts!
</Panel>
<LevelComplete
levelSlug="1-1-meet-promi"
stars={3}
message="Έμαθες τι είναι AI και prompts!"
/>
</Section>

View File

@@ -1,7 +1,10 @@
<Section>
<Panel character="promi" mood="happy">
Καλώς ήρθες πάλι, φίλε! Έτοιμος να γράψεις τα πρώτα σου αληθινά prompts; Πάμε! 🚀
</Panel>
</Section>
<Section>
## Η Μαγεία των Λέξεων
Όταν μιλάς με το AI, κάθε λέξη μετράει! Ας δούμε πώς η προσθήκη λέξεων κάνει τα prompts καλύτερα.
@@ -9,7 +12,9 @@
<Panel character="promi" mood="thinking">
Κοίτα! Αν κάποιος μου πει μόνο "γάτα", δεν ξέρω τι θέλουν. Θέλουν εικόνα; Ιστορία; Γεγονότα για γάτες; Είμαι μπερδεμένος! 😵‍💫
</Panel>
</Section>
<Section>
## Χτίζοντας Καλύτερα Prompts
Ένα καλό prompt έχει **τρία μέρη**:
@@ -21,7 +26,9 @@
<Panel character="promi" mood="excited">
Ας χτίσουμε ένα prompt μαζί!
</Panel>
</Section>
<Section>
## Σύρε τα Κομμάτια!
<DragDropPrompt
@@ -36,11 +43,14 @@
correctOrder={[0, 1, 2, 3]}
successMessage="Τέλειο! Αυτό είναι εξαιρετικό prompt!"
/>
</Section>
<Section>
## Συμπλήρωσε τα Κενά!
Τώρα δοκίμασε να φτιάξεις το δικό σου prompt σύροντας τις μαγικές λέξεις:
<MagicWords
title="Δημιούργησε το δικό σου prompt! ✨"
sentence="Παρακαλώ γράψε {{type}} για {{character}} που {{action}}"
@@ -51,7 +61,9 @@
]}
successMessage="Ουάου! Δημιούργησες καταπληκτικό prompt!"
/>
</Section>
<Section>
## Η Σειρά Σου να Διαλέξεις!
<PromptVsMistake
@@ -61,15 +73,20 @@
explanation="Το πρώτο prompt μου λέει ότι πρέπει να είναι αστείο, είναι για πιγκουίνο, ΚΑΙ τι θέλει να κάνει ο πιγκουίνος!"
promiMessage="Οι λεπτομέρειες κάνουν τα πάντα καλύτερα! Λατρεύω να ξέρω ακριβώς τι θέλεις!"
/>
</Section>
<Section>
## Εξαιρετική Δουλειά! 🌟
<Panel character="promi" mood="celebrating">
Έγραψες τα πρώτα σου prompts! Έμαθες ότι τα καλά prompts χρειάζονται: τι θέλεις, θέμα και λεπτομέρειες. Γίνεσαι πολύ καλός!
</Panel>
<LevelComplete
levelSlug="1-2-first-words"
stars={3}
message="Έμαθες πώς να γράφεις τα πρώτα σου prompts!"
/>
</Section>

View File

@@ -1,7 +1,10 @@
<Section>
<Panel character="promi" mood="happy">
Γεια σούπερ αστέρι! 🌟 Σήμερα θα μάθουμε την πιο σημαντική ικανότητα: να είσαι **ΣΑΦΗΣ**!
</Panel>
</Section>
<Section>
## Γιατί το να Είσαι Σαφής Είναι Σημαντικό
Φαντάσου να ζητάς από τη μαμά σου "φαγητό" έναντι "σάντουιτς με φιστικοβούτυρο χωρίς κόρα." Ποιο σου δίνει ακριβώς αυτό που θέλεις;
@@ -9,11 +12,14 @@
<Panel character="promi" mood="thinking">
Το ίδιο είναι και μαζί μου! Όταν είσαι σαφής, ξέρω ακριβώς πώς να βοηθήσω. Όταν είσαι αόριστος, πρέπει να μαντέψω... και μπορεί να κάνω λάθος!
</Panel>
</Section>
<Section>
## Σαφές vs. Ασαφές
Ας εξασκηθούμε να βρίσκουμε τη διαφορά!
<PromptVsMistake
question="Ποιο prompt είναι πιο σαφές;"
good="Γράψε ένα ποίημα 4 γραμμών για πεταλούδες σε κήπο, με ομοιοκαταληξία"
@@ -22,6 +28,7 @@
promiMessage="Σαφή prompts = καλύτερα αποτελέσματα! Είναι σαν μαγεία!"
/>
<PromptVsMistake
question="Ποιο μου λέει ακριβώς τι χρειάζεσαι;"
good="Βοήθησέ με να γράψω 3 διασκεδαστικά γεγονότα για δελφίνια που θα άρεσαν σε παιδί 10 ετών"
@@ -29,11 +36,14 @@
explanation="Το πρώτο prompt μου λέει: πόσα γεγονότα (3), τι είδους (διασκεδαστικά), και για ποιον (παιδί 10 ετών). Αυτό βοηθάει πολύ!"
promiMessage="Όταν μου λες για ποιον είναι, μπορώ να το κάνω τέλειο για αυτούς!"
/>
</Section>
<Section>
## Η Πρόκληση της Σαφήνειας
Ας χτίσουμε το πιο σαφές prompt!
<DragDropPrompt
title="Κάντο κρυστάλλινο! 💎"
instruction="Τακτοποίησε αυτά τα κομμάτια για να φτιάξεις σούπερ σαφές prompt"
@@ -47,7 +57,9 @@
correctOrder={[0, 1, 2, 3, 4]}
successMessage="Αυτό είναι το πιο σαφές prompt! Καταπληκτικό!"
/>
</Section>
<Section>
## Πρόσθεσε Σαφείς Λεπτομέρειες
<MagicWords
@@ -61,7 +73,9 @@
]}
successMessage="Πρόσθεσες όλες τις σημαντικές λεπτομέρειες! Εξαιρετική δουλειά!"
/>
</Section>
<Section>
## Οι Χρυσοί Κανόνες της Σαφήνειας
<Panel character="promi" mood="excited">
@@ -74,6 +88,7 @@
2. **ΠΩΣ** πρέπει να είναι; (μικρό, αστείο, απλό)
3. **ΓΙΑ ΠΟΙΟΝ** είναι; (εμένα, τον φίλο μου, την τάξη μου)
<PromptVsMistake
question="Τελική πρόκληση! Ποιο χρησιμοποιεί και τους τρεις κανόνες;"
good="Γράψε ένα μικρό, αστείο αστείο για πίτσα που μπορώ να πω στους φίλους μου στο μεσημεριανό"
@@ -81,7 +96,9 @@
explanation="Το εξαιρετικό prompt έχει ΤΙ (αστείο για πίτσα), ΠΩΣ (μικρό και αστείο), και ΓΙΑ ΠΟΙΟΝ (να πω στους φίλους στο μεσημεριανό)!"
promiMessage="Είσαι πρωταθλητής σαφήνειας! 🏆"
/>
</Section>
<Section>
## Κόσμος 1 Ολοκληρώθηκε! 🎊
<Panel character="promi" mood="celebrating">
@@ -94,8 +111,11 @@
Είσαι έτοιμος για νέες περιπέτειες!
</Panel>
<LevelComplete
levelSlug="1-3-being-clear"
stars={3}
message="Κατέκτησες την τέχνη του να είσαι σαφής! Κόσμος 1 ολοκληρώθηκε!"
/>
</Section>

View File

@@ -1,25 +1,67 @@
<Section>
<Panel character="promi" mood="happy">
Hi there! I'm **Promi** 🤖, your robot friend! I'm so happy to meet you!
</Panel>
</Section>
<Section>
<Panel character="promi" mood="thinking">
Do you know what **AI** means? AI stands for **Artificial Intelligence**. That's a fancy way of saying "a computer that can think and talk!"
</Panel>
</Section>
<Section>
<Panel character="promi" mood="excited">
I'm an AI! I can read your messages and try to help you. But here's the secret... I need **good instructions** to do my best work!
</Panel>
</Section>
<Section>
## How Do I Think? 🧠
Want to know my secret? I don't actually "think" like you do. I read words and guess what word should come next!
<WordPredictor
title="Think Like AI!"
instruction="I read patterns and guess the next word. What would YOU guess?"
sentence="The cat sat on the ___"
options={["mat", "moon", "purple", "keyboard"]}
correctAnswer="mat"
explanation="'Mat' is the most common word that follows 'The cat sat on the' because it's a famous rhyme! AI learns these patterns from reading lots of text."
aiThinking="I've seen 'The cat sat on the mat' many times before..."
successMessage="You think like AI! I pick the most likely word based on patterns."
/>
</Section>
<Section>
<WordPredictor
title="One More Try!"
instruction="Now you're getting it! What word fits best here?"
sentence="Once upon a ___"
options={["time", "banana", "quickly", "green"]}
correctAnswer="time"
explanation="'Once upon a time' is how almost every fairy tale starts! AI has read thousands of stories that begin this way."
aiThinking="This sounds like a story beginning... I've seen this pattern so many times!"
successMessage="Perfect! You're already thinking like AI - recognizing patterns!"
/>
</Section>
<Section>
## What is a Prompt?
A **prompt** is just a fancy word for the message you send to an AI like me.
Think of it like giving directions to a friend. If you say "Go there!" your friend won't know where to go. But if you say "Go to the red house on Maple Street," they'll know exactly where!
</Section>
<Section>
<Panel character="promi" mood="happy">
When you write a good prompt, I can understand what you want and help you better! Let's practice!
</Panel>
</Section>
<Section>
## Let's Try It!
<PromptVsMistake
@@ -29,7 +71,9 @@ When you write a good prompt, I can understand what you want and help you better
explanation="The first message tells Promi exactly what kind of story to write! The second one is too short - Promi doesn't know what kind of story you want."
promiMessage="See? More details help me understand what you want!"
/>
</Section>
<Section>
## Quick Quiz!
<PromptVsMistake
@@ -39,7 +83,9 @@ When you write a good prompt, I can understand what you want and help you better
explanation="A prompt uses words to tell the AI what you need. Emojis are fun but they don't give enough information!"
promiMessage="Words are my superpower! The more you tell me, the better I can help!"
/>
</Section>
<Section>
## You Did It! 🎉
<Panel character="promi" mood="celebrating">
@@ -51,3 +97,4 @@ Amazing job! You learned what AI is and what a prompt is. You're already becomin
stars={3}
message="You learned what AI and prompts are!"
/>
</Section>

View File

@@ -1,15 +1,31 @@
<Section>
<Panel character="promi" mood="happy">
Welcome back, friend! Ready to write your first real prompts? Let's go! 🚀
</Panel>
</Section>
<Section>
## The Magic of Words
When you talk to AI, every word matters! Let's see how adding more words makes prompts better.
<WordPredictor
title="Why Words Matter"
instruction="Remember how I guess the next word? Watch what happens with prompts!"
sentence="Write a story about a ___"
options={["dragon", "thing", "the", "very"]}
correctAnswer="dragon"
explanation="'Dragon' makes the most sense because stories are usually about characters or things! 'Thing' is too vague, and 'the' or 'very' don't fit the pattern."
aiThinking="Stories are usually about someone or something interesting..."
successMessage="Exactly! Specific words guide my predictions - that's why details matter in prompts!"
/>
<Panel character="promi" mood="thinking">
Watch this! If someone just says "cat" to me, I don't know what they want. Do they want a picture? A story? Facts about cats? I'm confused! 😵‍💫
</Panel>
</Section>
<Section>
## Building Better Prompts
A good prompt has **three parts**:
@@ -21,7 +37,9 @@ A good prompt has **three parts**:
<Panel character="promi" mood="excited">
Let's build a prompt together!
</Panel>
</Section>
<Section>
## Drag the Pieces!
<DragDropPrompt
@@ -36,10 +54,12 @@ Let's build a prompt together!
correctOrder={[0, 1, 2, 3]}
successMessage="Perfect! That's a great prompt!"
/>
</Section>
<Section>
## Fill in the Blanks!
Now try making your own prompt by filling in the magic words:
Now try making your own prompt by dragging the magic words:
<MagicWords
title="Create your own prompt! ✨"
@@ -51,7 +71,9 @@ Now try making your own prompt by filling in the magic words:
]}
successMessage="Wow! You created an awesome prompt!"
/>
</Section>
<Section>
## Your Turn to Choose!
<PromptVsMistake
@@ -61,7 +83,9 @@ Now try making your own prompt by filling in the magic words:
explanation="The first prompt tells me it should be funny, it's about a penguin, AND what the penguin wants to do!"
promiMessage="Details make everything better! I love knowing exactly what you want!"
/>
</Section>
<Section>
## Great Job! 🌟
<Panel character="promi" mood="celebrating">
@@ -73,3 +97,4 @@ You wrote your first prompts! You learned that good prompts need: what you want,
stars={3}
message="You learned how to write your first prompts!"
/>
</Section>

View File

@@ -1,7 +1,10 @@
<Section>
<Panel character="promi" mood="happy">
Hey superstar! 🌟 Today we're going to learn the most important skill: being **CLEAR**!
</Panel>
</Section>
<Section>
## Why Being Clear Matters
Imagine asking your mom for "food" vs asking for "a peanut butter sandwich with no crust." Which one gets you exactly what you want?
@@ -9,11 +12,11 @@ Imagine asking your mom for "food" vs asking for "a peanut butter sandwich with
<Panel character="promi" mood="thinking">
It's the same with me! When you're clear, I know exactly how to help. When you're vague, I have to guess... and I might guess wrong!
</Panel>
</Section>
<Section>
## Clear vs. Unclear
Let's practice spotting the difference!
<PromptVsMistake
question="Which prompt is clearer?"
good="Write a 4-line poem about butterflies in a garden, using rhyming words"
@@ -21,7 +24,9 @@ Let's practice spotting the difference!
explanation="The clear prompt tells me: how long (4 lines), what about (butterflies in a garden), and a special rule (rhyming). Much better!"
promiMessage="Clear prompts = better results! It's like magic!"
/>
</Section>
<Section>
<PromptVsMistake
question="Which one tells me exactly what you need?"
good="Help me write 3 fun facts about dolphins that a 10-year-old would enjoy"
@@ -29,11 +34,11 @@ Let's practice spotting the difference!
explanation="The first prompt tells me: how many facts (3), what kind (fun), and who it's for (10-year-old). That helps a lot!"
promiMessage="When you tell me who it's for, I can make it perfect for them!"
/>
</Section>
<Section>
## The Clarity Challenge
Let's build the clearest prompt ever!
<DragDropPrompt
title="Make it crystal clear! 💎"
instruction="Arrange these pieces to make a super clear prompt"
@@ -47,7 +52,9 @@ Let's build the clearest prompt ever!
correctOrder={[0, 1, 2, 3, 4]}
successMessage="That's the clearest prompt ever! Amazing!"
/>
</Section>
<Section>
## Fill in Clear Details
<MagicWords
@@ -61,7 +68,9 @@ Let's build the clearest prompt ever!
]}
successMessage="You added all the important details! Great job!"
/>
</Section>
<Section>
## The Golden Rules of Clarity
<Panel character="promi" mood="excited">
@@ -73,7 +82,9 @@ Remember these three questions when writing a prompt:
1. **WHAT** do I want? (story, help, information)
2. **HOW** should it be? (short, funny, simple)
3. **WHO** is it for? (me, my friend, my class)
</Section>
<Section>
<PromptVsMistake
question="Final challenge! Which uses all three rules?"
good="Write a short, funny joke about pizza that I can tell my friends at lunch"
@@ -81,7 +92,9 @@ Remember these three questions when writing a prompt:
explanation="The great prompt has WHAT (a joke about pizza), HOW (short and funny), and WHO (for telling friends at lunch)!"
promiMessage="You're a clarity champion! 🏆"
/>
</Section>
<Section>
## World 1 Complete! 🎊
<Panel character="promi" mood="celebrating">
@@ -99,3 +112,4 @@ You're ready for new adventures!
stars={3}
message="You mastered the art of being clear! World 1 complete!"
/>
</Section>

View File

@@ -0,0 +1,81 @@
<Section>
<Panel character="promi" mood="excited">
Welcome to **Clarity Castle**! 🏰 I'm so glad you made it here! In this world, we'll learn the magic of **details**!
</Panel>
</Section>
<Section>
<Panel character="promi" mood="thinking">
Have you ever asked someone for something, but they didn't understand what you meant? That happens to AI too!
</Panel>
</Section>
<Section>
## The Problem with Vague Prompts
Look at these two prompts:
❌ **Vague:** "Draw a picture"
✅ **Specific:** "Draw a picture of a happy puppy playing in a sunny park"
Which one gives me more information to work with?
</Section>
<Section>
<Panel character="promi" mood="happy">
The second one tells me **what** to draw (puppy), **what it's doing** (playing), and **where** (sunny park). That's the power of details!
</Panel>
</Section>
<Section>
## Let's Practice!
<PromptVsMistake
question="Which prompt has better details?"
good="Write a funny joke about a penguin who wants to learn how to fly"
bad="Tell me a joke"
explanation="The detailed prompt tells me exactly what kind of joke you want - funny, about a penguin, with a specific story!"
promiMessage="Details are like clues that help me understand exactly what you want!"
/>
</Section>
<Section>
## Why Details Matter
<PromptVsMistake
question="Which would help Promi make a better birthday card?"
good="Make a birthday card for my grandma who loves gardening and the color purple"
bad="Make a birthday card"
explanation="Knowing it's for grandma, that she loves gardening, and her favorite color helps create something special and personal!"
promiMessage="The more I know about what you want, the better I can help!"
/>
</Section>
<Section>
## Fill in the Details!
<MagicWords
sentence="Please write a story about a ___ who lives in a ___ and loves to ___"
blanks={[
{ hint: "🐱 an animal", answers: ["cat", "dog", "rabbit", "dragon", "unicorn"] },
{ hint: "🏠 a place", answers: ["castle", "forest", "city", "treehouse", "cave"] },
{ hint: "⭐ an activity", answers: ["sing", "dance", "cook", "paint", "explore"] }
]}
successMessage="Great job adding details!"
/>
</Section>
<Section>
## You're Learning! 🎉
<Panel character="promi" mood="celebrating">
Awesome work! You discovered that **details make prompts better**. Vague prompts = confused AI. Detailed prompts = amazing results!
</Panel>
<LevelComplete
levelSlug="2-1-missing-details"
stars={3}
message="You learned why details matter in prompts!"
/>
</Section>

View File

@@ -0,0 +1,97 @@
<Section>
<Panel character="promi" mood="happy">
Welcome back to Clarity Castle! Today we're learning about **WHO** and **WHAT** - two super important details!
</Panel>
</Section>
<Section>
## The WHO Question
When you ask AI for help, think: **Who is involved?**
- A person? (boy, girl, grandma, superhero)
- An animal? (cat, dragon, whale)
- A character? (robot, wizard, alien)
</Section>
<Section>
<Panel character="promi" mood="thinking">
If you say "Write a story," I don't know who it's about! But "Write a story about a brave knight" tells me exactly who the main character is!
</Panel>
</Section>
<Section>
## Let's Practice WHO!
<PromptVsMistake
question="Which prompt tells us WHO the story is about?"
good="Write a story about a curious little mouse named Pip"
bad="Write a story about something"
explanation="The first prompt tells us WHO (a mouse), WHAT kind (curious, little), and even their NAME (Pip)!"
promiMessage="Names and descriptions help me picture the character!"
/>
</Section>
<Section>
## The WHAT Question
Now think: **What is happening? What do you want?**
- What action? (running, singing, building)
- What object? (a cake, a spaceship, a poem)
- What type? (funny, scary, colorful)
</Section>
<Section>
## Let's Practice WHAT!
<PromptVsMistake
question="Which prompt tells us WHAT to create?"
good="Write a funny poem about pizza with lots of rhymes"
bad="Write something funny"
explanation="The detailed prompt tells us WHAT (a poem), WHAT about (pizza), WHAT style (funny with rhymes)!"
promiMessage="Being specific about WHAT you want helps me create exactly that!"
/>
</Section>
<Section>
## Build Your Own Prompt!
Put WHO and WHAT together:
<DragDropPrompt
title="Build a Prompt"
instruction="Drag the pieces into the right order"
pieces={["Write a story", "about a friendly dragon", "who learns", "to make ice cream"]}
correctOrder={[0, 1, 2, 3]}
successMessage="Perfect! You combined WHO (friendly dragon) and WHAT (learns to make ice cream)!"
/>
</Section>
<Section>
## Create Your Own!
<MagicWords
sentence="Please write a ___ about a ___ who wants to ___"
blanks={[
{ hint: "📝 type of writing", answers: ["story", "poem", "song", "joke"] },
{ hint: "🦸 a character", answers: ["superhero", "princess", "robot", "wizard", "pirate"] },
{ hint: "🎯 a goal", answers: ["find treasure", "make friends", "save the world", "learn magic", "win a race"] }
]}
successMessage="You added WHO and WHAT perfectly!"
/>
</Section>
<Section>
## Amazing Progress! 🎉
<Panel character="promi" mood="celebrating">
You're getting so good at this! Now you know to always include **WHO** is in your prompt and **WHAT** you want. Keep it up!
</Panel>
<LevelComplete
levelSlug="2-2-who-and-what"
stars={3}
message="You mastered WHO and WHAT in prompts!"
/>
</Section>

View File

@@ -0,0 +1,96 @@
<Section>
<Panel character="promi" mood="excited">
Great to see you again! Today's lesson is about **WHEN** and **WHERE** - these details make your prompts come alive!
</Panel>
</Section>
<Section>
## The WHEN Question
**When** does your story happen?
- Time of day: morning, night, sunset
- Season: summer, winter, spring
- Era: long ago, future, present day
- Special time: birthday, holiday, first day of school
</Section>
<Section>
<Panel character="promi" mood="thinking">
"A story about a cat" is okay, but "A story about a cat on a spooky Halloween night" is SO much more interesting!
</Panel>
</Section>
<Section>
## Let's Practice WHEN!
<PromptVsMistake
question="Which prompt has a better sense of WHEN?"
good="Write about a snowman who wakes up on a warm spring morning"
bad="Write about a snowman"
explanation="The first prompt tells us WHEN (spring morning) which creates an interesting problem - a snowman in spring! That's a great story idea!"
promiMessage="WHEN can create exciting situations in your stories!"
/>
</Section>
<Section>
## The WHERE Question
**Where** does the action take place?
- Location: beach, forest, city, space
- Building: school, castle, treehouse
- Fantasy place: underwater kingdom, cloud city
- Specific spot: under a bridge, on top of a mountain
</Section>
<Section>
## Let's Practice WHERE!
<PromptVsMistake
question="Which prompt paints a better picture of WHERE?"
good="Write about pirates searching for treasure on a mysterious foggy island"
bad="Write about pirates searching for treasure"
explanation="Adding WHERE (mysterious foggy island) helps me imagine the scene and write a more vivid story!"
promiMessage="WHERE helps set the mood and atmosphere of your story!"
/>
</Section>
<Section>
## Combine WHEN and WHERE!
<DragDropPrompt
title="Time and Place"
instruction="Arrange these pieces to create a prompt with WHEN and WHERE"
pieces={["Tell me a story", "set in a magical forest", "on a starry night", "about an owl"]}
correctOrder={[0, 3, 1, 2]}
successMessage="Excellent! You set the scene with WHERE (magical forest) and WHEN (starry night)!"
/>
</Section>
<Section>
## Build a Complete Scene!
<MagicWords
sentence="Write a story about an adventure that happens in a ___ during ___"
blanks={[
{ hint: "🗺️ a place", answers: ["haunted house", "underwater city", "cloud kingdom", "dinosaur park", "candy land"] },
{ hint: "⏰ a time", answers: ["a thunderstorm", "sunset", "winter break", "a full moon", "the future"] }
]}
successMessage="You created a vivid setting with WHEN and WHERE!"
/>
</Section>
<Section>
## You're a Scene Setter! 🎉
<Panel character="promi" mood="celebrating">
Fantastic work! You now know how to add **WHEN** (time) and **WHERE** (place) to your prompts. This makes your requests so much more interesting!
</Panel>
<LevelComplete
levelSlug="2-3-when-and-where"
stars={3}
message="You mastered WHEN and WHERE in prompts!"
/>
</Section>

View File

@@ -0,0 +1,104 @@
<Section>
<Panel character="promi" mood="excited">
Welcome to the final level of Clarity Castle! 🏰 You've learned about WHO, WHAT, WHEN, and WHERE. Now let's put it ALL together!
</Panel>
</Section>
<Section>
## The Detail Detective Checklist
Before you send a prompt, ask yourself:
✅ **WHO** - Who is involved?
✅ **WHAT** - What do I want? What's happening?
✅ **WHEN** - When does it happen?
✅ **WHERE** - Where does it take place?
</Section>
<Section>
## Sort the Prompt Parts!
<PromptParts
title="Match Each Piece!"
instruction="Tap a piece, then pick if it's a Role, Task, Context, or Constraint!"
parts={[
{ text: "Write a funny story", type: "task" },
{ text: "about a clumsy wizard", type: "context" },
{ text: "at a magic school", type: "context" },
{ text: "Keep it short", type: "constraint" }
]}
successMessage="You identified all the prompt parts! Now you can build prompts like a pro!"
/>
</Section>
<Section>
<Panel character="promi" mood="thinking">
You don't need ALL four every time, but the more details you add, the better I can help you!
</Panel>
</Section>
<Section>
## Detective Challenge #1
<PromptVsMistake
question="Which prompt has the most helpful details?"
good="Write a funny story about a clumsy wizard named Zap who accidentally turns his cat into a giant during a magic show at the royal castle"
bad="Write a story about magic"
explanation="The detailed prompt has WHO (wizard Zap, his cat), WHAT (turns cat giant, magic show), WHERE (royal castle), and even a style (funny)!"
promiMessage="Wow! That prompt gives me so much to work with. I can picture the whole scene!"
/>
</Section>
<Section>
## Detective Challenge #2
<PromptVsMistake
question="Which prompt would get a better poem?"
good="Write a short rhyming poem about a sleepy bear getting ready for winter hibernation in his cozy cave"
bad="Write a poem about an animal"
explanation="The first prompt tells me WHO (sleepy bear), WHAT (getting ready for hibernation), WHEN (winter), WHERE (cozy cave), and style (short, rhyming)!"
promiMessage="Every detail helps me create something special just for you!"
/>
</Section>
<Section>
## Build the Ultimate Prompt!
<DragDropPrompt
title="Master Detective"
instruction="Arrange ALL the details into a perfect prompt"
pieces={["Write an exciting story", "about a young inventor", "who builds a robot best friend", "in a futuristic city", "on the first day of summer"]}
correctOrder={[0, 1, 2, 3, 4]}
successMessage="Perfect! You included WHO, WHAT, WHERE, and WHEN!"
/>
</Section>
<Section>
## Create Your Masterpiece!
<MagicWords
sentence="Please write a ___ story about a ___ who ___ in a ___ during ___"
blanks={[
{ hint: "😊 a mood/style", answers: ["funny", "exciting", "mysterious", "heartwarming", "silly"] },
{ hint: "🦸 a character", answers: ["brave knight", "tiny fairy", "clever fox", "young astronaut", "friendly ghost"] },
{ hint: "🎬 an action", answers: ["discovers a secret", "goes on a quest", "makes a new friend", "solves a mystery", "learns to fly"] },
{ hint: "🏰 a place", answers: ["magical kingdom", "deep jungle", "space station", "underwater city", "enchanted forest"] },
{ hint: "🌙 a time", answers: ["a full moon", "their birthday", "a big storm", "the last day of school", "midnight"] }
]}
successMessage="You're a true Detail Detective! That's an amazing prompt!"
/>
</Section>
<Section>
## Congratulations, Detective! 🎉
<Panel character="promi" mood="celebrating">
You did it! You've completed Clarity Castle and become a **Detail Detective**! You now know the secret: WHO + WHAT + WHEN + WHERE = Amazing Prompts!
</Panel>
<LevelComplete
levelSlug="2-4-detail-detective"
stars={3}
message="You completed Clarity Castle! You're a Detail Detective!"
/>
</Section>

View File

@@ -0,0 +1,71 @@
<Section>
<Panel character="promi" mood="excited">
Welcome to the **Context Caves**! 🕳️ Here we'll discover the magic of **background information**!
</Panel>
</Section>
<Section>
<Panel character="promi" mood="thinking">
Have you ever told someone a joke, but they didn't laugh because they didn't understand the background? Context is like giving someone the backstory!
</Panel>
</Section>
<Section>
## What is Context?
**Context** is the extra information that helps AI understand your request better.
Think of it like telling a story - if you just say "and then he won!" nobody knows who won, what they won, or why it matters!
</Section>
<Section>
## See the Difference
<PromptVsMistake
question="Which prompt gives better context?"
good="I'm writing a birthday card for my 8-year-old sister who loves unicorns. Can you help me write a short, fun message?"
bad="Write a birthday message"
explanation="The first prompt tells Promi WHO it's for (8-year-old sister), WHAT she likes (unicorns), and WHAT style you want (short, fun)!"
promiMessage="Context helps me understand the situation and give you exactly what you need!"
/>
</Section>
<Section>
## Setting the Scene
<PromptVsMistake
question="Which prompt sets a better scene?"
good="I'm a 10-year-old working on a school project about dinosaurs. Can you explain what T-Rex ate in simple words?"
bad="What did T-Rex eat?"
explanation="By telling me you're 10 and it's for school, I know to use simple words and make it educational!"
promiMessage="When I know who I'm helping and why, I can adjust my answers perfectly!"
/>
</Section>
<Section>
## Practice Setting Context!
<MagicWords
sentence="I'm a ___ and I need help with ___. Can you explain it in a ___ way?"
blanks={[
{ hint: "👤 who you are", answers: ["student", "kid", "beginner", "young artist", "curious learner"] },
{ hint: "📚 what you need", answers: ["math homework", "a science project", "writing a story", "learning to draw", "understanding space"] },
{ hint: "✨ how to explain", answers: ["simple", "fun", "step-by-step", "easy to understand", "creative"] }
]}
successMessage="Great context setting! Now the AI knows exactly how to help you!"
/>
</Section>
<Section>
## Excellent Start! 🎉
<Panel character="promi" mood="celebrating">
You're learning to set the scene! Remember: giving me context helps me understand your situation and give you better answers!
</Panel>
<LevelComplete
levelSlug="3-1-setting-the-scene"
stars={3}
message="You learned how context helps AI understand you!"
/>
</Section>

View File

@@ -0,0 +1,104 @@
<Section>
<Panel character="promi" mood="happy">
Welcome back! Today's lesson is super important: **Show, Don't Tell**! Using examples is one of the best ways to help AI understand!
</Panel>
</Section>
<Section>
## The Power of Examples
Instead of just describing what you want, **show me an example**!
It's like when you're teaching someone a game - it's easier to show them how to play than just tell them the rules!
</Section>
<Section>
## Learn the Pattern!
<ExampleMatcher
title="Pattern Power"
instruction="AI learns from examples! See the pattern and pick what comes next."
examples={[
{ input: "happy", output: "😊" },
{ input: "sad", output: "😢" },
{ input: "sleepy", output: "😴" }
]}
question="angry"
options={["😠", "😊", "🎉", "😴"]}
correctAnswer="😠"
explanation="The AI learned: feeling word → matching emoji! This is called 'learning by example' - just like you!"
/>
</Section>
<Section>
<Panel character="promi" mood="thinking">
If you say "make it sound cool," I might not know what "cool" means to you. But if you show me an example, I'll understand perfectly!
</Panel>
</Section>
<Section>
## See the Difference
<PromptVsMistake
question="Which prompt uses examples better?"
good="Write a fun product name like 'Super Sparkle Shampoo' or 'Magic Cloud Pillows' - something catchy and playful!"
bad="Write a fun product name"
explanation="The examples show exactly what style of name you're looking for - catchy, playful, with fun adjectives!"
promiMessage="Examples are like treasure maps - they show me exactly where you want to go!"
/>
</Section>
<Section>
## Show Your Style
<PromptVsMistake
question="Which prompt better shows the writing style?"
good="Write a joke in this style: 'Why did the banana go to the doctor? Because it wasn't peeling well!' - something silly with a pun!"
bad="Write a funny joke"
explanation="By showing an example joke with a pun, I know you want that same silly wordplay style!"
promiMessage="When you show me what you like, I can match that style!"
/>
</Section>
<Section>
## Give Examples!
<DragDropPrompt
title="Build with Examples"
instruction="Arrange the pieces to create a prompt with a helpful example"
pieces={["Write a superhero name", "like 'Captain Courage'", "or 'Lightning Lady'", "- something heroic and memorable"]}
correctOrder={[0, 1, 2, 3]}
successMessage="Perfect! Your examples show exactly what kind of name you want!"
/>
</Section>
<Section>
## Create Your Own Example-Prompt!
<MagicWords
sentence="Write a ___ like '___ ___ ___' - something ___ and ___"
blanks={[
{ hint: "📝 what to write", answers: ["team name", "pet name", "band name", "book title", "restaurant name"] },
{ hint: "✨ adjective", answers: ["Happy", "Super", "Golden", "Magical", "Mighty"] },
{ hint: "🌟 noun", answers: ["Stars", "Dragons", "Dreams", "Thunder", "Phoenix"] },
{ hint: "🎯 word", answers: ["Club", "Squad", "Crew", "Kingdom", "Adventure"] },
{ hint: "😊 style 1", answers: ["fun", "exciting", "cool", "creative", "catchy"] },
{ hint: "🎨 style 2", answers: ["memorable", "unique", "powerful", "friendly", "bold"] }
]}
successMessage="Awesome! Your example shows exactly what style you want!"
/>
</Section>
<Section>
## Examples Are Powerful! 🎉
<Panel character="promi" mood="celebrating">
Amazing work! You learned that **showing examples** is one of the best ways to help AI understand exactly what you want!
</Panel>
<LevelComplete
levelSlug="3-2-show-dont-tell"
stars={3}
message="You mastered using examples in prompts!"
/>
</Section>

View File

@@ -0,0 +1,87 @@
<Section>
<Panel character="promi" mood="excited">
Today we learn about **formats**! 📝 Did you know you can ask AI to respond in different ways?
</Panel>
</Section>
<Section>
## What's a Format?
A **format** is HOW you want the answer presented:
- 📋 **List** - numbered or bullet points
- 📖 **Story** - a narrative with beginning, middle, end
- 🎵 **Poem** - with rhymes and rhythm
- ❓ **Q&A** - question and answer style
- 📊 **Table** - organized in rows and columns
</Section>
<Section>
<Panel character="promi" mood="happy">
By telling me the format, you help me organize my answer in the way that's most helpful for you!
</Panel>
</Section>
<Section>
## Format Makes a Difference!
<PromptVsMistake
question="Which prompt asks for a clear format?"
good="Give me 5 fun facts about dolphins in a numbered list"
bad="Tell me about dolphins"
explanation="The first prompt tells me exactly how to organize the answer - as 5 numbered facts!"
promiMessage="When you ask for a specific format, you get exactly what you need!"
/>
</Section>
<Section>
## Choose Your Format!
<PromptVsMistake
question="Which format would be best for remembering things?"
good="List the planets in order from the sun as bullet points"
bad="Tell me about the planets"
explanation="A list format makes it easy to remember and count the planets in order!"
promiMessage="Lists are great for learning and remembering!"
/>
</Section>
<Section>
## Format Practice!
<DragDropPrompt
title="Add a Format"
instruction="Arrange to ask for a specific format"
pieces={["as a short poem", "with rhyming lines", "Write about rain", "that's 4 lines long"]}
correctOrder={[2, 0, 1, 3]}
successMessage="Perfect! You asked for a poem format with specific details!"
/>
</Section>
<Section>
## Pick Your Format!
<MagicWords
sentence="Give me ___ about ___ as a ___"
blanks={[
{ hint: "🔢 how many", answers: ["5 tips", "3 ideas", "10 facts", "7 steps", "4 examples"] },
{ hint: "📚 topic", answers: ["being a good friend", "saving the planet", "staying healthy", "being creative", "learning new things"] },
{ hint: "📋 format", answers: ["numbered list", "bullet points", "short poem", "simple story", "easy steps"] }
]}
successMessage="Great job picking a format! This makes answers easier to read!"
/>
</Section>
<Section>
## Format Master! 🎉
<Panel character="promi" mood="celebrating">
Wonderful! You learned that asking for a specific **format** helps you get answers organized exactly how you need them!
</Panel>
<LevelComplete
levelSlug="3-3-format-finder"
stars={3}
message="You learned to ask for different formats!"
/>
</Section>

View File

@@ -0,0 +1,87 @@
<Section>
<Panel character="promi" mood="excited">
Welcome to the final level of Context Caves! 🕳️ Time to become a **Context Champion** by combining everything you've learned!
</Panel>
</Section>
<Section>
## The Context Champion Checklist
Great context includes:
✅ **Background** - Who you are, what situation you're in
✅ **Examples** - Show what you want
✅ **Format** - How you want the answer organized
</Section>
<Section>
<Panel character="promi" mood="thinking">
Put all three together and you'll get amazing results every time!
</Panel>
</Section>
<Section>
## Champion Challenge #1
<PromptVsMistake
question="Which prompt has the best context?"
good="I'm a 9-year-old making a poster about recycling for Earth Day. Can you give me 5 catchy slogans like 'Reduce, Reuse, Recycle!' - short and easy to remember?"
bad="Give me recycling slogans"
explanation="The champion prompt includes: Background (9-year-old, Earth Day poster), Example ('Reduce, Reuse, Recycle!'), and Format (5 slogans, short)!"
promiMessage="This prompt tells me everything I need to give you perfect slogans!"
/>
</Section>
<Section>
## Champion Challenge #2
<PromptVsMistake
question="Which prompt combines context elements best?"
good="I'm helping my little brother learn animal sounds. Can you make a fun list of 6 animals with their sounds, like 'Cow - Moo!' - keep it silly and simple?"
bad="List animal sounds"
explanation="This includes: Background (helping little brother learn), Example (Cow - Moo!), and Format (list of 6, silly and simple)!"
promiMessage="Perfect context! I know exactly what will work for your little brother!"
/>
</Section>
<Section>
## Build a Champion Prompt!
<DragDropPrompt
title="Context Champion"
instruction="Arrange ALL context elements into a perfect prompt"
pieces={["I'm writing a story for my class.", "Can you suggest 3 magical pet names", "like 'Sparkle' or 'Moonbeam'?", "Something cute but not too long."]}
correctOrder={[0, 1, 2, 3]}
successMessage="Champion prompt! You included background, examples, and format preferences!"
/>
</Section>
<Section>
## Create Your Champion Prompt!
<MagicWords
sentence="I'm a ___ working on ___. Can you give me ___ like ___? Make it ___."
blanks={[
{ hint: "👤 who you are", answers: ["student", "young writer", "curious kid", "creative artist", "beginner cook"] },
{ hint: "📚 your project", answers: ["a school project", "a birthday card", "a funny story", "a science experiment", "a comic book"] },
{ hint: "🎯 what you need", answers: ["3 good ideas", "5 fun titles", "some cool names", "a few examples", "helpful tips"] },
{ hint: "💡 an example", answers: ["'Super Star'", "'Magic Moment'", "'Wonder World'", "'Happy Helper'", "'Dream Team'"] },
{ hint: "✨ the style", answers: ["fun and short", "creative and catchy", "simple and clear", "exciting and bold", "friendly and warm"] }
]}
successMessage="You're a true Context Champion! That prompt has it all!"
/>
</Section>
<Section>
## Congratulations, Champion! 🎉
<Panel character="promi" mood="celebrating">
You did it! You've completed Context Caves and become a **Context Champion**! You now know how to combine background, examples, and format for amazing prompts!
</Panel>
<LevelComplete
levelSlug="3-4-context-champion"
stars={3}
message="You completed Context Caves! You're a Context Champion!"
/>
</Section>

View File

@@ -0,0 +1,86 @@
<Section>
<Panel character="promi" mood="excited">
Welcome to **Creation Canyon**! 🎨 This is where creativity comes alive! Today we learn about **role-play prompts**!
</Panel>
</Section>
<Section>
## What is Role-Play?
**Role-play** means asking the AI to pretend to be someone or something!
You can say things like:
- "Act as a pirate..."
- "Pretend you're a teacher..."
- "You are a friendly chef..."
</Section>
<Section>
<Panel character="promi" mood="happy">
When you ask me to play a role, I can give answers in that character's voice and style! It's like we're playing pretend together!
</Panel>
</Section>
<Section>
## See Role-Play in Action!
<PromptVsMistake
question="Which prompt uses role-play?"
good="Pretend you're a friendly space explorer. Tell me about your adventures on Mars!"
bad="Tell me about Mars"
explanation="The first prompt asks me to BE a space explorer, so I can tell you about 'my' adventures in a fun, personal way!"
promiMessage="Role-play makes our conversations so much more fun and creative!"
/>
</Section>
<Section>
## Choose Your Character!
<PromptVsMistake
question="Which role would be best for learning about the ocean?"
good="Act as a wise old sea turtle who has lived for 100 years. What cool things have you seen in the ocean?"
bad="What lives in the ocean?"
explanation="A 100-year-old sea turtle has seen so much! This makes learning about the ocean feel like hearing stories from a friend!"
promiMessage="Different roles give different perspectives and make learning more interesting!"
/>
</Section>
<Section>
## Create a Role-Play Prompt!
<DragDropPrompt
title="Pretend Time!"
instruction="Arrange the pieces to create a fun role-play prompt"
pieces={["and tell me", "Pretend you're a dragon", "about your favorite treasure", "who loves collecting shiny things"]}
correctOrder={[1, 3, 0, 2]}
successMessage="Great role-play prompt! Now the dragon can share its treasure stories!"
/>
</Section>
<Section>
## Build Your Own Role-Play!
<MagicWords
sentence="Pretend you're a ___ who ___. Tell me about ___."
blanks={[
{ hint: "🎭 a character", answers: ["wizard", "superhero", "talking cat", "time traveler", "fairy"] },
{ hint: "⭐ what they do", answers: ["grants wishes", "saves the day", "explores magical lands", "invents cool gadgets", "makes potions"] },
{ hint: "📖 what to share", answers: ["your greatest adventure", "your best day ever", "a funny mistake you made", "your secret hideout", "your best friend"] }
]}
successMessage="Awesome role-play! Now we can have a fun pretend conversation!"
/>
</Section>
<Section>
## Role-Play Master! 🎉
<Panel character="promi" mood="celebrating">
Fantastic! You learned that **role-play prompts** make conversations creative and fun! Just ask me to "act as" or "pretend to be" someone!
</Panel>
<LevelComplete
levelSlug="4-1-pretend-time"
stars={3}
message="You learned to use role-play prompts!"
/>
</Section>

View File

@@ -0,0 +1,87 @@
<Section>
<Panel character="promi" mood="happy">
Welcome back to Creation Canyon! Today we're going to create amazing **stories** together! 📚
</Panel>
</Section>
<Section>
## AI as Your Co-Author
You don't have to write stories alone! AI can help you:
- Start a story with an exciting opening
- Continue a story you've begun
- Add new characters or plot twists
- Give you ideas when you're stuck
</Section>
<Section>
<Panel character="promi" mood="thinking">
The best stories come from your imagination AND my help working together!
</Panel>
</Section>
<Section>
## Story Starters That Work!
<PromptVsMistake
question="Which is a better story starter prompt?"
good="Start a mystery story about a kid who finds a glowing map in their grandma's attic. Make the first paragraph exciting and mysterious!"
bad="Write a story"
explanation="The detailed prompt gives me a character (a kid), setting (grandma's attic), object (glowing map), and style (exciting, mysterious)!"
promiMessage="Great story starters give me just enough to begin, but leave room for adventure!"
/>
</Section>
<Section>
## Continue the Adventure!
<PromptVsMistake
question="Which prompt helps continue a story better?"
good="Continue my story: 'Luna found a tiny door behind her bookshelf. She heard music coming from inside.' What happens when she opens it? Make it magical!"
bad="Continue a story about a door"
explanation="By sharing what you've written so far, I can continue it in the same style!"
promiMessage="Share your story so far and I'll keep the adventure going!"
/>
</Section>
<Section>
## Start Your Story!
<DragDropPrompt
title="Story Starter"
instruction="Arrange these pieces to create an exciting story prompt"
pieces={["Write the opening", "who discovers they can talk to animals", "Make it funny and surprising!", "of a story about a kid"]}
correctOrder={[0, 3, 1, 2]}
successMessage="That's a great story starter! I can't wait to write this adventure!"
/>
</Section>
<Section>
## Create Your Story Prompt!
<MagicWords
sentence="Write a ___ story about a ___ who finds a ___ that can ___. Start with an exciting opening!"
blanks={[
{ hint: "✨ story type", answers: ["magical", "funny", "mysterious", "adventurous", "heartwarming"] },
{ hint: "🧒 main character", answers: ["young inventor", "brave princess", "curious robot", "shy dragon", "clever fox"] },
{ hint: "🎁 special object", answers: ["ancient book", "glowing crystal", "magic wand", "mysterious key", "talking pet"] },
{ hint: "🌟 special power", answers: ["grant wishes", "open portals", "show the future", "bring drawings to life", "speak any language"] }
]}
successMessage="What an amazing story idea! Let the adventure begin!"
/>
</Section>
<Section>
## Story Creator! 🎉
<Panel character="promi" mood="celebrating">
Amazing! You learned how to use AI as your **co-author**! Together, we can create the most incredible stories!
</Panel>
<LevelComplete
levelSlug="4-2-story-starters"
stars={3}
message="You learned to create stories with AI!"
/>
</Section>

View File

@@ -0,0 +1,88 @@
<Section>
<Panel character="promi" mood="excited">
Today we learn to give AI a **personality**! 🎭 This makes conversations SO much more fun!
</Panel>
</Section>
<Section>
## Give AI a Personality!
You can tell AI what personality to have:
- **Friendly and cheerful** - always positive!
- **Silly and goofy** - makes lots of jokes
- **Wise and calm** - gives thoughtful answers
- **Excited and energetic** - uses lots of exclamation points!
</Section>
<Section>
<Panel character="promi" mood="happy">
When you tell me what personality to have, I can match the mood you're looking for!
</Panel>
</Section>
<Section>
## Personality Makes a Difference!
<PromptVsMistake
question="Which prompt gives a fun personality?"
good="Act as a super excited robot who LOVES science! Use lots of exclamation points and say 'AMAZING!' a lot. Tell me about volcanoes!"
bad="Tell me about volcanoes"
explanation="The first prompt creates a fun, excited personality that makes learning feel like an adventure!"
promiMessage="AMAZING! See how much more fun science is when I'm excited about it?!"
/>
</Section>
<Section>
## Match the Mood!
<PromptVsMistake
question="Which personality would be best for a bedtime story?"
good="Tell me a bedtime story in a calm, soothing voice. Speak gently and make it peaceful and sleepy."
bad="Tell me a bedtime story"
explanation="By asking for a calm, soothing personality, the story will help you relax!"
promiMessage="Different moods work better for different situations!"
/>
</Section>
<Section>
## Create a Character!
<DragDropPrompt
title="Character Personality"
instruction="Arrange to create a fun personality"
pieces={["Act as a wise old owl", "Use lots of 'hoo hoo' sounds!", "who loves teaching young birds.", "Be patient and kind."]}
correctOrder={[0, 2, 3, 1]}
successMessage="What a wonderful character! Hoo hoo! This owl is ready to teach!"
/>
</Section>
<Section>
## Design Your AI Personality!
<MagicWords
sentence="Act as a ___ who is always ___. Use ___ and be ___. Tell me about ___."
blanks={[
{ hint: "🎭 character type", answers: ["friendly robot", "wise wizard", "silly clown", "brave knight", "curious alien"] },
{ hint: "😊 personality trait", answers: ["excited", "calm", "funny", "encouraging", "mysterious"] },
{ hint: "💬 speaking style", answers: ["lots of jokes", "gentle words", "rhymes", "big words", "sound effects"] },
{ hint: "❤️ another trait", answers: ["helpful", "patient", "creative", "adventurous", "caring"] },
{ hint: "📚 a topic", answers: ["space", "animals", "music", "friendship", "nature"] }
]}
successMessage="You created an amazing personality! Let's chat!"
/>
</Section>
<Section>
## Personality Master! 🎉
<Panel character="promi" mood="celebrating">
Wonderful! You learned that giving AI a **personality** makes conversations more fun and engaging! Try different personalities for different needs!
</Panel>
<LevelComplete
levelSlug="4-3-character-creator"
stars={3}
message="You learned to give AI personalities!"
/>
</Section>

View File

@@ -0,0 +1,85 @@
<Section>
<Panel character="promi" mood="excited">
Welcome to the final level of Creation Canyon! 🎨 Let's become **World Builders** and create amazing imaginative scenarios!
</Panel>
</Section>
<Section>
## Building Imaginary Worlds
You can ask AI to help you create entire worlds:
- Fantasy kingdoms with magic
- Futuristic cities with robots
- Underwater civilizations
- Planets in distant galaxies
</Section>
<Section>
<Panel character="promi" mood="thinking">
The only limit is your imagination! Tell me about your world and I'll help bring it to life!
</Panel>
</Section>
<Section>
## World Building in Action!
<PromptVsMistake
question="Which prompt builds a better world?"
good="Help me create a world where all animals can talk and they have their own cities. The cats run the libraries and the dogs are firefighters. What else might be there?"
bad="Make up a world"
explanation="The detailed prompt starts building the world (talking animals with jobs) and asks me to add more!"
promiMessage="I love this world! Maybe the birds deliver mail and the bunnies run bakeries!"
/>
</Section>
<Section>
## Expand Your World!
<PromptVsMistake
question="Which prompt helps grow a world?"
good="In my magical forest world, there are trees that grow candy. What kinds of candy trees might exist? Who takes care of them? What adventures could happen there?"
bad="Tell me about a forest"
explanation="By asking specific questions about your world, I can help you expand it with new ideas!"
promiMessage="Asking 'what if' questions helps worlds grow bigger and more interesting!"
/>
</Section>
<Section>
## Build Your World!
<DragDropPrompt
title="World Builder"
instruction="Arrange to create an imaginative world"
pieces={["Help me create a world", "where all weather is controlled by", "What might go wrong?", "friendly weather wizards."]}
correctOrder={[0, 1, 3, 2]}
successMessage="What a creative world! I'm imagining so many fun scenarios!"
/>
</Section>
<Section>
## Create Your Ultimate World!
<MagicWords
sentence="Help me build a world where ___. The main thing that makes it special is ___. Who lives there? What adventures could happen?"
blanks={[
{ hint: "🌍 world concept", answers: ["dreams come to life", "everyone has superpowers", "toys can move at night", "music creates magic", "colors have feelings"] },
{ hint: "✨ special feature", answers: ["a giant magic tree in the center", "floating islands in the sky", "rivers that flow with starlight", "buildings made of clouds", "doorways to other dimensions"] }
]}
successMessage="What an incredible world! The adventures are endless!"
/>
</Section>
<Section>
## Congratulations, World Builder! 🎉
<Panel character="promi" mood="celebrating">
You did it! You've completed Creation Canyon and become a **World Builder**! Your imagination combined with AI can create endless amazing worlds!
</Panel>
<LevelComplete
levelSlug="4-4-world-builder"
stars={3}
message="You completed Creation Canyon! You're a World Builder!"
/>
</Section>

View File

@@ -0,0 +1,107 @@
<Section>
<Panel character="promi" mood="excited">
Welcome to **Master Mountain**! ⛰️ You've come so far! Now it's time to combine EVERYTHING you've learned into perfect prompts!
</Panel>
</Section>
<Section>
## The Master Checklist
A perfect prompt can include:
✅ **Clarity** - Be specific, not vague
✅ **Details** - WHO, WHAT, WHEN, WHERE
✅ **Context** - Background info, examples, format
✅ **Creativity** - Role-play, personality, imagination
</Section>
<Section>
## Magic Words: "Think Step by Step" 🧠
<StepByStep
title="Step-by-Step Magic"
problem="I have 3 bags with 5 apples each, and I eat 2 apples. How many are left?"
wrongAnswer="15 apples left (AI guessed without thinking!)"
steps={[
"First: 3 bags × 5 apples = 15 apples total",
"Next: I eat 2 apples",
"Finally: 15 - 2 = 13 apples left"
]}
rightAnswer="13 apples left - and we can check the work!"
magicWords="Let's think step by step"
successMessage="Magic words help AI show its thinking, so you can check if it's right!"
/>
</Section>
<Section>
<Panel character="promi" mood="thinking">
You don't need ALL of these every time, but knowing when to use each one makes you a Prompt Master!
</Panel>
</Section>
<Section>
## Master Challenge #1
<PromptVsMistake
question="Which prompt combines the most skills?"
good="I'm a 10-year-old who loves space. Act as a friendly astronaut and tell me 5 amazing facts about Saturn. Make it exciting and use simple words a kid would understand!"
bad="Tell me about Saturn"
explanation="This prompt has: Context (10-year-old, loves space), Role-play (friendly astronaut), Format (5 facts), Personality (exciting), and Clarity (simple words)!"
promiMessage="This is a master-level prompt! It tells me exactly what you need!"
/>
</Section>
<Section>
## Master Challenge #2
<PromptVsMistake
question="Which is closer to a perfect prompt?"
good="Pretend you're a wise old tree in an enchanted forest. I'm a young adventurer who just found you. Tell me a short story about the magical creatures who live nearby. Make it mysterious but not scary, with a happy ending!"
bad="Tell me about a forest"
explanation="This has: Role-play (wise old tree), Characters (young adventurer), Setting (enchanted forest), Topic (magical creatures), Style (mysterious, not scary, happy ending)!"
promiMessage="So many great elements combined! This will be an amazing story!"
/>
</Section>
<Section>
## Build a Master Prompt!
<DragDropPrompt
title="Perfect Prompt"
instruction="Arrange all elements into one perfect prompt"
pieces={["I'm making a birthday card for my dad who loves fishing.", "Give me 3 funny fishing jokes", "like 'Why did the fish blush? Because it saw the ocean's bottom!'", "Keep them family-friendly and short!"]}
correctOrder={[0, 1, 2, 3]}
successMessage="Perfect! Context, format, example, and style all in one prompt!"
/>
</Section>
<Section>
## Create Your Master Prompt!
<MagicWords
sentence="I'm a ___ who needs help with ___. Act as a ___ and give me ___ like ___. Make it ___!"
blanks={[
{ hint: "👤 who you are", answers: ["young artist", "curious student", "creative writer", "nature lover", "joke collector"] },
{ hint: "📚 your goal", answers: ["a school project", "a creative story", "learning something new", "making someone laugh", "planning an adventure"] },
{ hint: "🎭 a character", answers: ["friendly expert", "silly comedian", "wise teacher", "creative inventor", "storytelling grandma"] },
{ hint: "🔢 what you need", answers: ["5 fun ideas", "3 helpful tips", "some creative examples", "a short story", "an easy explanation"] },
{ hint: "💡 an example", answers: ["'Super Star Strategy'", "'Happy Helper Hint'", "'Magic Moment'", "'Wonder Word'", "'Creative Spark'"] },
{ hint: "✨ the style", answers: ["fun and easy to understand", "exciting and memorable", "creative and colorful", "helpful and encouraging", "silly but useful"] }
]}
successMessage="AMAZING! That's a true Master Prompt with all the elements!"
/>
</Section>
<Section>
## Master Skills Unlocked! 🎉
<Panel character="promi" mood="celebrating">
Incredible! You learned to combine all your skills into **perfect prompts**! You're well on your way to becoming a Prompt Master!
</Panel>
<LevelComplete
levelSlug="5-1-perfect-prompt"
stars={3}
message="You learned to create perfect prompts!"
/>
</Section>

View File

@@ -0,0 +1,108 @@
<Section>
<Panel character="promi" mood="happy">
Welcome back! Today we learn a super important skill: **finding and fixing weak prompts**! 🔧
</Panel>
</Section>
<Section>
## Spot the Problems!
Weak prompts often have:
❌ Too vague - "Write something"
❌ Missing details - "Tell me about a person"
❌ No format - Just asking without structure
❌ Unclear style - Not saying how you want it
</Section>
<Section>
## Be the Prompt Doctor! 🏥
<PromptDoctor
title="Heal This Prompt"
brokenPrompt="Write something"
problems={[
{ issue: "Too Vague", symptom: "What should I write? A story? A poem? A joke?", fix: "Write a story" },
{ issue: "No Topic", symptom: "What should the story be about?", fix: "Write a story about a dragon" },
{ issue: "No Details", symptom: "What kind of dragon? What happens?", fix: "Write a short story about a friendly dragon who bakes cookies" }
]}
healedPrompt="Write a short story about a friendly dragon who bakes cookies"
successMessage="You healed the prompt! From 2 words to a great detailed request!"
/>
</Section>
<Section>
<Panel character="promi" mood="thinking">
Learning to spot weak prompts helps you write better ones! It's like being a prompt detective!
</Panel>
</Section>
<Section>
## Find the Fix!
<PromptVsMistake
question="This prompt is weak: 'Write a poem.' How would you fix it?"
good="Write a short 4-line rhyming poem about a rainbow after a storm. Make it cheerful and use colorful words!"
bad="Write a really good poem please"
explanation="Just saying 'really good' doesn't help! Instead, add details: topic (rainbow), format (4 lines, rhyming), and style (cheerful, colorful)!"
promiMessage="Adding specific details transforms a weak prompt into a strong one!"
/>
</Section>
<Section>
## Improve This Prompt!
<PromptVsMistake
question="Weak prompt: 'Help with my homework.' How would you make it better?"
good="I'm in 4th grade and need help understanding fractions. Can you explain what 1/2 means using pizza as an example? Keep it simple!"
bad="Please help with my homework more"
explanation="The good version tells me: your level (4th grade), topic (fractions), asks for an example (pizza), and style (simple)!"
promiMessage="The more specific you are, the better I can help!"
/>
</Section>
<Section>
## Fix the Prompt!
<DragDropPrompt
title="Prompt Repair"
instruction="The prompt 'Tell me a story' is too weak. Arrange these additions to fix it:"
pieces={["about a brave little mouse", "Tell me a short bedtime story", "who helps a lost baby bird find home.", "Make it gentle and end with everyone safe."]}
correctOrder={[1, 0, 2, 3]}
successMessage="You transformed a weak prompt into a great one!"
/>
</Section>
<Section>
## Upgrade Challenge!
Take this weak prompt and make it strong:
**Weak:** "Give me ideas"
<MagicWords
sentence="Give me ___ creative ideas for ___ that are ___ and ___. Something like ___!"
blanks={[
{ hint: "🔢 how many", answers: ["5", "3", "7", "10", "4"] },
{ hint: "🎯 what for", answers: ["a birthday party", "a science project", "a fun game", "a story to write", "decorating my room"] },
{ hint: "✨ style 1", answers: ["fun", "easy to do", "creative", "surprising", "colorful"] },
{ hint: "🌟 style 2", answers: ["kid-friendly", "not too expensive", "unique", "exciting", "simple"] },
{ hint: "💡 example", answers: ["a treasure hunt", "making slime", "a superhero theme", "glow-in-the-dark stuff", "a magic show"] }
]}
successMessage="You turned a 2-word weak prompt into an amazing detailed one!"
/>
</Section>
<Section>
## Prompt Fixer! 🎉
<Panel character="promi" mood="celebrating">
Excellent! You learned to spot weak prompts and **fix them** by adding details, format, and style! This skill will help you forever!
</Panel>
<LevelComplete
levelSlug="5-2-fix-it-up"
stars={3}
message="You learned to find and fix weak prompts!"
/>
</Section>

View File

@@ -0,0 +1,106 @@
<Section>
<Panel character="promi" mood="excited">
Today we learn **Prompt Remix**! 🎵 This means changing prompts to get different results!
</Panel>
</Section>
<Section>
## Same Topic, Different Results!
You can ask about the same thing in different ways to get different answers:
- **For fun**: "Tell me about dolphins in a silly way"
- **For school**: "Give me 5 educational facts about dolphins"
- **For creativity**: "Write a poem from a dolphin's point of view"
</Section>
<Section>
## Try the Prompt Lab! 🔬
<PromptLab
title="Improve Your Prompt"
scenario="Let's make a simple prompt better by adding details one at a time!"
basePrompt="Tell me about dogs"
baseResponse="Dogs are animals. They have four legs and fur."
improvements={[
{ label: "Add a specific breed", prompt: "Tell me about Golden Retriever dogs", response: "Golden Retrievers are wonderful dogs known for their friendly personality, golden fur, and love for swimming!" },
{ label: "Add an audience", prompt: "Tell me about Golden Retriever dogs for a 10-year-old", response: "Golden Retrievers are super friendly dogs that love to play fetch and swim! They have soft golden fur and big happy smiles!" },
{ label: "Add a format", prompt: "Give me 3 fun facts about Golden Retriever dogs for a 10-year-old", response: "Here are 3 fun facts about Golden Retrievers: 1) They were trained to fetch birds for hunters, 2) They can learn over 200 words, 3) They have webbed feet which makes them great swimmers!" }
]}
successMessage="Each detail you added made the response better and more useful!"
/>
</Section>
<Section>
<Panel character="promi" mood="thinking">
Remixing prompts means you can explore the same topic in many exciting ways!
</Panel>
</Section>
<Section>
## See the Remix!
<PromptVsMistake
question="You want to learn about dinosaurs but in a FUN way. Which remix is best?"
good="Pretend you're a T-Rex who just woke up. Tell me about your day - what do you eat, who are your friends, and what makes you grumpy?"
bad="Give me facts about T-Rex"
explanation="Same topic (T-Rex), but the remix makes it a fun story from the dinosaur's perspective!"
promiMessage="Remixing the same topic gives you fresh, fun ways to learn!"
/>
</Section>
<Section>
## Remix for Different Goals!
<PromptVsMistake
question="You learned about the moon. Now you want to write a creative story. Which remix works?"
good="Write a bedtime story about a little star who wants to visit the moon. Make it magical with a sweet ending!"
bad="Tell me more facts about the moon"
explanation="You remixed from 'learning facts' to 'creative story' - same topic, different purpose!"
promiMessage="Remixing lets you use what you know in new creative ways!"
/>
</Section>
<Section>
## Try Different Remixes!
<DragDropPrompt
title="Prompt Remix"
instruction="Remix 'Tell me about cats' into a creative prompt:"
pieces={["and describe your favorite napping spot", "Write from the view of a lazy cat", "who lives in a cozy bookshop.", "Make it funny and relaxed!!"]}
correctOrder={[1, 2, 0, 3]}
successMessage="Great remix! Same topic, totally different and fun approach!"
/>
</Section>
<Section>
## Create Your Remix!
Take the basic topic "robots" and remix it:
<MagicWords
sentence="___ a ___ robot who ___. Describe ___. Make it ___!"
blanks={[
{ hint: "📝 what to write", answers: ["Tell a story about", "Write a diary entry from", "Create a song about", "Describe a day for", "Interview"] },
{ hint: "🤖 robot type", answers: ["tiny helper", "dancing", "cooking", "space explorer", "friendly classroom"] },
{ hint: "⭐ what it does", answers: ["just learned to make friends", "discovered it loves music", "made a silly mistake", "is going on its first adventure", "met a human for the first time"] },
{ hint: "🎯 what to include", answers: ["how it feels about its new discovery", "the funniest moment of its day", "what it dreams about", "its favorite memory", "what it learned today"] },
{ hint: "✨ the style", answers: ["heartwarming and sweet", "silly and fun", "exciting and adventurous", "peaceful and calm", "mysterious and curious"] }
]}
successMessage="Awesome remix! You turned 'robots' into something totally unique!"
/>
</Section>
<Section>
## Remix Master! 🎉
<Panel character="promi" mood="celebrating">
Fantastic! You learned to **remix prompts** to get different results from the same topic! This makes your creativity endless!
</Panel>
<LevelComplete
levelSlug="5-3-prompt-remix"
stars={3}
message="You learned to remix prompts for different results!"
/>
</Section>

View File

@@ -0,0 +1,102 @@
<Section>
<Panel character="promi" mood="excited">
🎉 **CONGRATULATIONS!** 🎉 You've made it to the FINAL LEVEL! This is **Graduation Day**!
</Panel>
</Section>
<Section>
## Look How Far You've Come!
You've mastered:
⭐ **World 1** - What AI is and why clarity matters
⭐ **World 2** - WHO, WHAT, WHEN, WHERE details
⭐ **World 3** - Context, examples, and formats
⭐ **World 4** - Role-play, stories, and creativity
⭐ **World 5** - Combining everything perfectly!
</Section>
<Section>
<Panel character="promi" mood="happy">
I'm SO proud of you! You've become a true Prompt Master! Let's celebrate with some final challenges!
</Panel>
</Section>
<Section>
## Final Challenge #1
<PromptVsMistake
question="Show me your best prompt skills! Which is the master-level prompt?"
good="I'm a creative 11-year-old working on a comic book. Act as a fun superhero coach and help me create 3 unique superhero names with cool powers. Something like 'Starblaze - controls cosmic fire!' Make them exciting but kid-friendly!"
bad="Give me superhero names"
explanation="This master prompt has EVERYTHING: context (11-year-old, comic book), role (superhero coach), format (3 names with powers), example (Starblaze), and style (exciting, kid-friendly)!"
promiMessage="THAT is a Prompt Master at work! Perfect in every way!"
/>
</Section>
<Section>
## Final Challenge #2
<PromptVsMistake
question="Create the ultimate helpful prompt!"
good="I'm nervous about my first day at a new school tomorrow. Pretend you're a kind older kid who's been through this before. Give me 5 tips to feel brave and make new friends. Be encouraging and remind me it's okay to be nervous!"
bad="Help me with school"
explanation="This prompt is heartfelt AND masterful: real situation (nervous, new school), role (kind older kid), format (5 tips), and emotional style (encouraging, understanding)!"
promiMessage="This shows prompts can help with real feelings too! Beautiful work!"
/>
</Section>
<Section>
## The Ultimate Prompt Challenge!
<DragDropPrompt
title="Graduation Challenge"
instruction="Build the most complete prompt possible!"
pieces={["I'm a young chef who loves baking.", "Act as a friendly baking instructor", "and give me 3 easy cookie recipes for beginners,", "like 'Simple Sugar Cookies - only 5 ingredients!'", "Make the instructions clear and fun!"]}
correctOrder={[0, 1, 2, 3, 4]}
successMessage="PERFECT! Context, role, format, example, AND style! You're a true master!"
/>
</Section>
<Section>
## Create Your Graduation Masterpiece!
<MagicWords
sentence="I'm a ___ who wants to ___. Act as a ___ and help me with ___. Give me something like '___'. Make it ___ and ___!"
blanks={[
{ hint: "👤 who you are", answers: ["creative kid", "young explorer", "curious learner", "aspiring artist", "future inventor"] },
{ hint: "🎯 your goal", answers: ["create something amazing", "learn something new", "solve a problem", "make people smile", "start an adventure"] },
{ hint: "🎭 a helpful role", answers: ["wise mentor", "fun coach", "creative guide", "patient teacher", "encouraging friend"] },
{ hint: "📚 what you need", answers: ["3 brilliant ideas", "a step-by-step plan", "an inspiring story", "helpful tips", "a creative project"] },
{ hint: "💡 an example", answers: ["Build a cardboard rocket!", "Write a song about kindness", "Design a dream treehouse", "Create a new game", "Invent a helpful robot"] },
{ hint: "✨ style 1", answers: ["fun", "inspiring", "creative", "exciting", "encouraging"] },
{ hint: "🌟 style 2", answers: ["easy to follow", "full of imagination", "memorable", "unique", "joyful"] }
]}
successMessage="🎉 MASTERPIECE! You've created the ultimate prompt! You are officially a PROMPT MASTER!"
/>
</Section>
<Section>
## 🎓 YOU DID IT! 🎓
<Panel character="promi" mood="celebrating">
**CONGRATULATIONS, PROMPT MASTER!** 🏆
You've completed ALL levels! You now know how to talk to AI in the best ways possible. You can:
- Be clear and specific
- Add amazing details
- Give helpful context
- Be creative and imaginative
- And so much more!
Remember: The better your prompts, the better AI can help you. Go out there and create amazing things!
**Thank you for learning with me! 🤖❤️**
</Panel>
<LevelComplete
levelSlug="5-4-graduation-day"
stars={3}
message="🎓 CONGRATULATIONS! You've graduated as a PROMPT MASTER! 🏆"
/>
</Section>

View File

@@ -1,3 +1,4 @@
<Section>
<Panel character="promi" mood="happy">
¡Hola! Soy **Promi** 🤖, ¡tu amigo robot! ¡Estoy muy feliz de conocerte!
</Panel>
@@ -9,7 +10,9 @@
<Panel character="promi" mood="excited">
¡Soy una IA! Puedo leer tus mensajes e intentar ayudarte. Pero aquí está el secreto... ¡Necesito **buenas instrucciones** para hacer mi mejor trabajo!
</Panel>
</Section>
<Section>
## ¿Qué es un Prompt?
Un **prompt** es simplemente una palabra elegante para el mensaje que envías a una IA como yo.
@@ -19,7 +22,9 @@ Piénsalo como dar direcciones a un amigo. Si dices "¡Ve allí!" tu amigo no sa
<Panel character="promi" mood="happy">
¡Cuando escribes un buen prompt, puedo entender lo que quieres y ayudarte mejor! ¡Practiquemos!
</Panel>
</Section>
<Section>
## ¡Intentémoslo!
<PromptVsMistake
@@ -29,7 +34,9 @@ Piénsalo como dar direcciones a un amigo. Si dices "¡Ve allí!" tu amigo no sa
explanation="¡El primer mensaje le dice a Promi exactamente qué tipo de historia escribir! El segundo es muy corto - Promi no sabe qué tipo de historia quieres."
promiMessage="¿Ves? ¡Más detalles me ayudan a entender lo que quieres!"
/>
</Section>
<Section>
## ¡Quiz Rápido!
<PromptVsMistake
@@ -39,15 +46,20 @@ Piénsalo como dar direcciones a un amigo. Si dices "¡Ve allí!" tu amigo no sa
explanation="¡Un prompt usa palabras para decirle a la IA lo que necesitas. Los emojis son divertidos pero no dan suficiente información!"
promiMessage="¡Las palabras son mi superpoder! ¡Cuanto más me digas, mejor puedo ayudar!"
/>
</Section>
<Section>
## ¡Lo Lograste! 🎉
<Panel character="promi" mood="celebrating">
¡Increíble trabajo! Aprendiste qué es la IA y qué es un prompt. ¡Ya te estás convirtiendo en un experto en prompts!
</Panel>
<LevelComplete
levelSlug="1-1-meet-promi"
stars={3}
message="¡Aprendiste qué son la IA y los prompts!"
/>
</Section>

View File

@@ -1,7 +1,10 @@
<Section>
<Panel character="promi" mood="happy">
¡Bienvenido de nuevo, amigo! ¿Listo para escribir tus primeros prompts reales? ¡Vamos! 🚀
</Panel>
</Section>
<Section>
## La Magia de las Palabras
Cuando hablas con la IA, ¡cada palabra importa! Veamos cómo agregar más palabras hace mejores los prompts.
@@ -9,7 +12,9 @@ Cuando hablas con la IA, ¡cada palabra importa! Veamos cómo agregar más palab
<Panel character="promi" mood="thinking">
¡Mira esto! Si alguien solo me dice "gato", no sé qué quieren. ¿Quieren una imagen? ¿Una historia? ¿Datos sobre gatos? ¡Estoy confundido! 😵‍💫
</Panel>
</Section>
<Section>
## Construyendo Mejores Prompts
Un buen prompt tiene **tres partes**:
@@ -21,7 +26,9 @@ Un buen prompt tiene **tres partes**:
<Panel character="promi" mood="excited">
¡Construyamos un prompt juntos!
</Panel>
</Section>
<Section>
## ¡Arrastra las Piezas!
<DragDropPrompt
@@ -36,11 +43,14 @@ Un buen prompt tiene **tres partes**:
correctOrder={[0, 1, 2, 3]}
successMessage="¡Perfecto! ¡Ese es un gran prompt!"
/>
</Section>
<Section>
## ¡Llena los Espacios!
Ahora intenta hacer tu propio prompt arrastrando las palabras mágicas:
<MagicWords
title="¡Crea tu propio prompt! ✨"
sentence="Por favor escribe un {{type}} sobre un {{character}} que {{action}}"
@@ -51,7 +61,9 @@ Ahora intenta hacer tu propio prompt arrastrando las palabras mágicas:
]}
successMessage="¡Wow! ¡Creaste un prompt increíble!"
/>
</Section>
<Section>
## ¡Tu Turno de Elegir!
<PromptVsMistake
@@ -61,15 +73,20 @@ Ahora intenta hacer tu propio prompt arrastrando las palabras mágicas:
explanation="¡El primer prompt me dice que debe ser gracioso, es sobre un pingüino, Y lo que el pingüino quiere hacer!"
promiMessage="¡Los detalles hacen todo mejor! ¡Me encanta saber exactamente lo que quieres!"
/>
</Section>
<Section>
## ¡Gran Trabajo! 🌟
<Panel character="promi" mood="celebrating">
¡Escribiste tus primeros prompts! Aprendiste que los buenos prompts necesitan: lo que quieres, un tema y detalles. ¡Te estás volviendo muy bueno en esto!
</Panel>
<LevelComplete
levelSlug="1-2-first-words"
stars={3}
message="¡Aprendiste a escribir tus primeros prompts!"
/>
</Section>

View File

@@ -1,7 +1,10 @@
<Section>
<Panel character="promi" mood="happy">
¡Hola superestrella! 🌟 Hoy vamos a aprender la habilidad más importante: ¡ser **CLARO**!
</Panel>
</Section>
<Section>
## Por Qué Ser Claro Importa
Imagina pedirle a tu mamá "comida" vs pedirle "un sándwich de mantequilla de maní sin corteza." ¿Cuál te da exactamente lo que quieres?
@@ -9,11 +12,14 @@ Imagina pedirle a tu mamá "comida" vs pedirle "un sándwich de mantequilla de m
<Panel character="promi" mood="thinking">
¡Es lo mismo conmigo! Cuando eres claro, sé exactamente cómo ayudar. Cuando eres vago, tengo que adivinar... ¡y podría adivinar mal!
</Panel>
</Section>
<Section>
## Claro vs. No Claro
¡Practiquemos detectando la diferencia!
<PromptVsMistake
question="¿Qué prompt es más claro?"
good="Escribe un poema de 4 líneas sobre mariposas en un jardín, usando palabras que rimen"
@@ -22,6 +28,7 @@ Imagina pedirle a tu mamá "comida" vs pedirle "un sándwich de mantequilla de m
promiMessage="¡Prompts claros = mejores resultados! ¡Es como magia!"
/>
<PromptVsMistake
question="¿Cuál me dice exactamente lo que necesitas?"
good="Ayúdame a escribir 3 datos divertidos sobre delfines que un niño de 10 años disfrutaría"
@@ -29,11 +36,14 @@ Imagina pedirle a tu mamá "comida" vs pedirle "un sándwich de mantequilla de m
explanation="El primer prompt me dice: cuántos datos (3), qué tipo (divertidos), y para quién (niño de 10 años). ¡Eso ayuda mucho!"
promiMessage="¡Cuando me dices para quién es, puedo hacerlo perfecto para ellos!"
/>
</Section>
<Section>
## El Desafío de Claridad
¡Construyamos el prompt más claro de todos!
<DragDropPrompt
title="¡Hazlo cristalino! 💎"
instruction="Organiza estas piezas para hacer un prompt súper claro"
@@ -47,7 +57,9 @@ Imagina pedirle a tu mamá "comida" vs pedirle "un sándwich de mantequilla de m
correctOrder={[0, 1, 2, 3, 4]}
successMessage="¡Ese es el prompt más claro! ¡Increíble!"
/>
</Section>
<Section>
## Agrega Detalles Claros
<MagicWords
@@ -61,7 +73,9 @@ Imagina pedirle a tu mamá "comida" vs pedirle "un sándwich de mantequilla de m
]}
successMessage="¡Agregaste todos los detalles importantes! ¡Buen trabajo!"
/>
</Section>
<Section>
## Las Reglas de Oro de la Claridad
<Panel character="promi" mood="excited">
@@ -74,6 +88,7 @@ Recuerda estas tres preguntas cuando escribas un prompt:
2. **¿CÓMO** debe ser? (corto, gracioso, simple)
3. **¿PARA QUIÉN** es? (yo, mi amigo, mi clase)
<PromptVsMistake
question="¡Desafío final! ¿Cuál usa las tres reglas?"
good="Escribe un chiste corto y gracioso sobre pizza que pueda contar a mis amigos en el almuerzo"
@@ -81,7 +96,9 @@ Recuerda estas tres preguntas cuando escribas un prompt:
explanation="¡El gran prompt tiene QUÉ (un chiste sobre pizza), CÓMO (corto y gracioso), y PARA QUIÉN (para contar a amigos en el almuerzo)!"
promiMessage="¡Eres un campeón de la claridad! 🏆"
/>
</Section>
<Section>
## ¡Mundo 1 Completo! 🎊
<Panel character="promi" mood="celebrating">
@@ -94,8 +111,11 @@ Recuerda estas tres preguntas cuando escribas un prompt:
¡Estás listo para nuevas aventuras!
</Panel>
<LevelComplete
levelSlug="1-3-being-clear"
stars={3}
message="¡Dominaste el arte de ser claro! ¡Mundo 1 completo!"
/>
</Section>

View File

@@ -1,3 +1,4 @@
<Section>
<Panel character="promi" mood="happy">
سلام! من **Promi** 🤖 هستم، دوست رباتت! خیلی خوشحالم که باهات آشنا شدم!
</Panel>
@@ -9,7 +10,9 @@
<Panel character="promi" mood="excited">
من یه هوش مصنوعی‌ام! می‌تونم پیام‌هات رو بخونم و سعی کنم کمکت کنم. ولی اینجا یه راز هست... برای بهترین کار به **دستورالعمل‌های خوب** نیاز دارم!
</Panel>
</Section>
<Section>
## پرامپت چیه؟
**پرامپت** فقط یه کلمه شیک برای پیامی‌ه که به یه هوش مصنوعی مثل من می‌فرستی.
@@ -19,7 +22,9 @@
<Panel character="promi" mood="happy">
وقتی پرامپت خوب می‌نویسی، می‌تونم بفهمم چی می‌خوای و بهتر کمکت کنم! بیا تمرین کنیم!
</Panel>
</Section>
<Section>
## بیا امتحان کنیم!
<PromptVsMistake
@@ -29,7 +34,9 @@
explanation="پیام اول به Promi دقیقاً می‌گه چه نوع داستانی بنویسه! دومی خیلی کوتاهه - Promi نمی‌دونه چه جور داستانی می‌خوای."
promiMessage="دیدی؟ جزئیات بیشتر کمکم می‌کنه بفهمم چی می‌خوای!"
/>
</Section>
<Section>
## آزمون سریع!
<PromptVsMistake
@@ -39,15 +46,20 @@
explanation="پرامپت از کلمات استفاده می‌کنه تا به هوش مصنوعی بگه چی نیاز داری. ایموجی‌ها سرگرم‌کننده‌ن ولی اطلاعات کافی نمی‌دن!"
promiMessage="کلمات ابرقدرت منن! هرچی بیشتر بگی، بهتر می‌تونم کمک کنم!"
/>
</Section>
<Section>
## موفق شدی! 🎉
<Panel character="promi" mood="celebrating">
کار عالی! یاد گرفتی هوش مصنوعی چیه و پرامپت چیه. داری متخصص پرامپت می‌شی!
</Panel>
<LevelComplete
levelSlug="1-1-meet-promi"
stars={3}
message="یاد گرفتی هوش مصنوعی و پرامپت چین!"
/>
</Section>

View File

@@ -1,7 +1,10 @@
<Section>
<Panel character="promi" mood="happy">
خوش اومدی دوباره، دوست! آماده‌ای اولین پرامپت‌های واقعیت رو بنویسی؟ بریم! 🚀
</Panel>
</Section>
<Section>
## جادوی کلمات
وقتی با هوش مصنوعی حرف می‌زنی، هر کلمه مهمه! بیا ببینیم چطور اضافه کردن کلمات بیشتر پرامپت‌ها رو بهتر می‌کنه.
@@ -9,7 +12,9 @@
<Panel character="promi" mood="thinking">
ببین! اگه کسی فقط بگه "گربه"، نمی‌دونم چی می‌خوان. عکس می‌خوان؟ داستان؟ حقایق درباره گربه‌ها؟ گیج شدم! 😵‍💫
</Panel>
</Section>
<Section>
## ساختن پرامپت‌های بهتر
یه پرامپت خوب **سه بخش** داره:
@@ -21,7 +26,9 @@
<Panel character="promi" mood="excited">
بیا با هم یه پرامپت بسازیم!
</Panel>
</Section>
<Section>
## تکه‌ها رو بکش!
<DragDropPrompt
@@ -36,11 +43,14 @@
correctOrder={[0, 1, 2, 3]}
successMessage="عالی! این یه پرامپت فوق‌العاده‌ست!"
/>
</Section>
<Section>
## جای خالی رو پر کن!
حالا سعی کن پرامپت خودت رو با کشیدن کلمات جادویی بسازی:
<MagicWords
title="پرامپت خودت رو بساز! ✨"
sentence="لطفاً یه {{type}} بنویس، درباره یه {{character}} که {{action}}"
@@ -51,7 +61,9 @@
]}
successMessage="وای! یه پرامپت عالی ساختی!"
/>
</Section>
<Section>
## نوبت تو برای انتخاب!
<PromptVsMistake
@@ -61,15 +73,20 @@
explanation="پرامپت اول بهم می‌گه باید خنده‌دار باشه، درباره پنگوئنه، و پنگوئن چیکار می‌خواد بکنه!"
promiMessage="جزئیات همه چیز رو بهتر می‌کنه! دوست دارم دقیقاً بدونم چی می‌خوای!"
/>
</Section>
<Section>
## کار عالی! 🌟
<Panel character="promi" mood="celebrating">
اولین پرامپت‌هات رو نوشتی! یاد گرفتی پرامپت‌های خوب به چی نیاز دارن: چی می‌خوای، موضوع و جزئیات. داری خیلی خوب می‌شی!
</Panel>
<LevelComplete
levelSlug="1-2-first-words"
stars={3}
message="یاد گرفتی چطور اولین پرامپت‌هات رو بنویسی!"
/>
</Section>

View File

@@ -1,7 +1,10 @@
<Section>
<Panel character="promi" mood="happy">
سلام سوپراستار! 🌟 امروز می‌خوایم مهم‌ترین مهارت رو یاد بگیریم: **واضح** بودن!
</Panel>
</Section>
<Section>
## چرا واضح بودن مهمه
فکر کن از مامانت "غذا" بخوای یا "ساندویچ کره بادام‌زمینی بدون لبه." کدوم دقیقاً چیزی که می‌خوای رو بهت می‌ده؟
@@ -9,11 +12,14 @@
<Panel character="promi" mood="thinking">
با منم همینطوره! وقتی واضحی، دقیقاً می‌دونم چطور کمک کنم. وقتی مبهمی، باید حدس بزنم... و ممکنه اشتباه کنم!
</Panel>
</Section>
<Section>
## واضح در مقابل نامفهوم
بیا تمرین کنیم فرق رو پیدا کنیم!
<PromptVsMistake
question="کدوم پرامپت واضح‌تره؟"
good="یه شعر ۴ خطی درباره پروانه‌ها توی باغ بنویس، با کلمات قافیه‌دار"
@@ -22,6 +28,7 @@
promiMessage="پرامپت‌های واضح = نتایج بهتر! مثل جادوئه!"
/>
<PromptVsMistake
question="کدوم دقیقاً می‌گه چی نیاز داری؟"
good="کمکم کن ۳ حقیقت جالب درباره دلفین‌ها بنویسم که بچه ۱۰ ساله ازش لذت ببره"
@@ -29,11 +36,14 @@
explanation="پرامپت اول بهم می‌گه: چند تا حقیقت (۳)، چه جور (جالب)، و برای کی (۱۰ ساله). این خیلی کمک می‌کنه!"
promiMessage="وقتی بگی برای کیه، می‌تونم برای اون‌ها عالی بسازمش!"
/>
</Section>
<Section>
## چالش وضوح
بیا واضح‌ترین پرامپت رو بسازیم!
<DragDropPrompt
title="مثل بلور واضحش کن! 💎"
instruction="این تکه‌ها رو مرتب کن تا یه پرامپت فوق‌العاده واضح بسازی"
@@ -47,7 +57,9 @@
correctOrder={[0, 1, 2, 3, 4]}
successMessage="این واضح‌ترین پرامپت تا حالاست! عالی!"
/>
</Section>
<Section>
## جزئیات واضح اضافه کن
<MagicWords
@@ -61,7 +73,9 @@
]}
successMessage="همه جزئیات مهم رو اضافه کردی! کار عالی!"
/>
</Section>
<Section>
## قوانین طلایی وضوح
<Panel character="promi" mood="excited">
@@ -74,6 +88,7 @@
2. **چطور** باشه؟ (کوتاه، خنده‌دار، ساده)
3. **برای کی**؟ (من، دوستم، کلاسم)
<PromptVsMistake
question="چالش آخر! کدوم از هر سه قانون استفاده می‌کنه؟"
good="یه جوک کوتاه و خنده‌دار درباره پیتزا بنویس که بتونم موقع ناهار به دوستام بگم"
@@ -81,7 +96,9 @@
explanation="پرامپت عالی چی داره (جوک پیتزا)، چطور (کوتاه و خنده‌دار)، و برای کی (برای گفتن به دوستا موقع ناهار)!"
promiMessage="تو قهرمان وضوحی! 🏆"
/>
</Section>
<Section>
## دنیای ۱ تموم شد! 🎊
<Panel character="promi" mood="celebrating">
@@ -94,8 +111,11 @@
آماده ماجراجویی‌های جدیدی!
</Panel>
<LevelComplete
levelSlug="1-3-being-clear"
stars={3}
message="هنر واضح بودن رو یاد گرفتی! دنیای ۱ تموم شد!"
/>
</Section>

View File

@@ -1,3 +1,4 @@
<Section>
<Panel character="promi" mood="happy">
Salut ! Je suis **Promi** 🤖, ton ami robot ! Je suis tellement content de te rencontrer !
</Panel>
@@ -9,7 +10,9 @@ Tu sais ce que veut dire **IA** ? IA signifie **Intelligence Artificielle**. C'e
<Panel character="promi" mood="excited">
Je suis une IA ! Je peux lire tes messages et essayer de t'aider. Mais voici le secret... J'ai besoin de **bonnes instructions** pour faire mon meilleur travail !
</Panel>
</Section>
<Section>
## C'est quoi un Prompt ?
Un **prompt** est juste un mot chic pour le message que tu envoies à une IA comme moi.
@@ -19,7 +22,9 @@ Pense à ça comme donner des directions à un ami. Si tu dis "Va là-bas !" ton
<Panel character="promi" mood="happy">
Quand tu écris un bon prompt, je peux comprendre ce que tu veux et mieux t'aider ! Pratiquons !
</Panel>
</Section>
<Section>
## Essayons !
<PromptVsMistake
@@ -29,7 +34,9 @@ Quand tu écris un bon prompt, je peux comprendre ce que tu veux et mieux t'aide
explanation="Le premier message dit à Promi exactement quel type d'histoire écrire ! Le deuxième est trop court - Promi ne sait pas quel genre d'histoire tu veux."
promiMessage="Tu vois ? Plus de détails m'aident à comprendre ce que tu veux !"
/>
</Section>
<Section>
## Quiz Rapide !
<PromptVsMistake
@@ -39,15 +46,20 @@ Quand tu écris un bon prompt, je peux comprendre ce que tu veux et mieux t'aide
explanation="Un prompt utilise des mots pour dire à l'IA ce dont tu as besoin. Les emojis sont amusants mais ne donnent pas assez d'informations !"
promiMessage="Les mots sont mon super pouvoir ! Plus tu me dis, mieux je peux t'aider !"
/>
</Section>
<Section>
## Tu as réussi ! 🎉
<Panel character="promi" mood="celebrating">
Super travail ! Tu as appris ce qu'est l'IA et ce qu'est un prompt. Tu deviens déjà un expert en prompts !
</Panel>
<LevelComplete
levelSlug="1-1-meet-promi"
stars={3}
message="Tu as appris ce que sont l'IA et les prompts !"
/>
</Section>

View File

@@ -1,7 +1,10 @@
<Section>
<Panel character="promi" mood="happy">
Bon retour, ami ! Prêt à écrire tes premiers vrais prompts ? C'est parti ! 🚀
</Panel>
</Section>
<Section>
## La Magie des Mots
Quand tu parles à l'IA, chaque mot compte ! Voyons comment ajouter plus de mots rend les prompts meilleurs.
@@ -9,7 +12,9 @@ Quand tu parles à l'IA, chaque mot compte ! Voyons comment ajouter plus de mots
<Panel character="promi" mood="thinking">
Regarde ! Si quelqu'un me dit juste "chat", je ne sais pas ce qu'ils veulent. Ils veulent une image ? Une histoire ? Des faits sur les chats ? Je suis confus ! 😵‍💫
</Panel>
</Section>
<Section>
## Construire de Meilleurs Prompts
Un bon prompt a **trois parties** :
@@ -21,7 +26,9 @@ Un bon prompt a **trois parties** :
<Panel character="promi" mood="excited">
Construisons un prompt ensemble !
</Panel>
</Section>
<Section>
## Glisse les Pièces !
<DragDropPrompt
@@ -36,11 +43,14 @@ Construisons un prompt ensemble !
correctOrder={[0, 1, 2, 3]}
successMessage="Parfait ! C'est un super prompt !"
/>
</Section>
<Section>
## Remplis les Blancs !
Maintenant essaie de faire ton propre prompt en glissant les mots magiques :
<MagicWords
title="Crée ton propre prompt ! ✨"
sentence="S'il te plaît écris un {{type}} sur un {{character}} qui {{action}}"
@@ -51,7 +61,9 @@ Maintenant essaie de faire ton propre prompt en glissant les mots magiques :
]}
successMessage="Wow ! Tu as créé un super prompt !"
/>
</Section>
<Section>
## À Toi de Choisir !
<PromptVsMistake
@@ -61,15 +73,20 @@ Maintenant essaie de faire ton propre prompt en glissant les mots magiques :
explanation="Le premier prompt me dit que ça doit être drôle, c'est sur un pingouin, ET ce que le pingouin veut faire !"
promiMessage="Les détails rendent tout meilleur ! J'adore savoir exactement ce que tu veux !"
/>
</Section>
<Section>
## Super Travail ! 🌟
<Panel character="promi" mood="celebrating">
Tu as écrit tes premiers prompts ! Tu as appris que les bons prompts ont besoin de : ce que tu veux, un sujet, et des détails. Tu deviens vraiment bon !
</Panel>
<LevelComplete
levelSlug="1-2-first-words"
stars={3}
message="Tu as appris à écrire tes premiers prompts !"
/>
</Section>

View File

@@ -1,7 +1,10 @@
<Section>
<Panel character="promi" mood="happy">
Salut superstar ! 🌟 Aujourd'hui on va apprendre la compétence la plus importante : être **CLAIR** !
</Panel>
</Section>
<Section>
## Pourquoi Être Clair est Important
Imagine demander à ta maman "de la nourriture" vs demander "un sandwich au beurre de cacahuète sans croûte." Lequel te donne exactement ce que tu veux ?
@@ -9,11 +12,14 @@ Imagine demander à ta maman "de la nourriture" vs demander "un sandwich au beur
<Panel character="promi" mood="thinking">
C'est pareil avec moi ! Quand tu es clair, je sais exactement comment t'aider. Quand tu es vague, je dois deviner... et je pourrais me tromper !
</Panel>
</Section>
<Section>
## Clair vs. Pas Clair
Pratiquons à repérer la différence !
<PromptVsMistake
question="Quel prompt est plus clair ?"
good="Écris un poème de 4 lignes sur des papillons dans un jardin, en utilisant des mots qui riment"
@@ -22,6 +28,7 @@ Pratiquons à repérer la différence !
promiMessage="Prompts clairs = meilleurs résultats ! C'est comme de la magie !"
/>
<PromptVsMistake
question="Lequel me dit exactement ce dont tu as besoin ?"
good="Aide-moi à écrire 3 faits amusants sur les dauphins qu'un enfant de 10 ans aimerait"
@@ -29,11 +36,14 @@ Pratiquons à repérer la différence !
explanation="Le premier prompt me dit : combien de faits (3), quel type (amusants), et pour qui (enfant de 10 ans). Ça aide beaucoup !"
promiMessage="Quand tu me dis pour qui c'est, je peux le rendre parfait pour eux !"
/>
</Section>
<Section>
## Le Défi de la Clarté
Construisons le prompt le plus clair jamais fait !
<DragDropPrompt
title="Rends-le cristallin ! 💎"
instruction="Arrange ces pièces pour faire un prompt super clair"
@@ -47,7 +57,9 @@ Construisons le prompt le plus clair jamais fait !
correctOrder={[0, 1, 2, 3, 4]}
successMessage="C'est le prompt le plus clair jamais fait ! Incroyable !"
/>
</Section>
<Section>
## Ajoute des Détails Clairs
<MagicWords
@@ -61,7 +73,9 @@ Construisons le prompt le plus clair jamais fait !
]}
successMessage="Tu as ajouté tous les détails importants ! Bon travail !"
/>
</Section>
<Section>
## Les Règles d'Or de la Clarté
<Panel character="promi" mood="excited">
@@ -74,6 +88,7 @@ Rappelle-toi ces trois questions quand tu écris un prompt :
2. **COMMENT** ça doit être ? (court, drôle, simple)
3. **POUR QUI** c'est ? (moi, mon ami, ma classe)
<PromptVsMistake
question="Défi final ! Lequel utilise les trois règles ?"
good="Écris une blague courte et drôle sur la pizza que je peux raconter à mes amis au déjeuner"
@@ -81,7 +96,9 @@ Rappelle-toi ces trois questions quand tu écris un prompt :
explanation="Le super prompt a QUOI (une blague sur la pizza), COMMENT (courte et drôle), et POUR QUI (raconter à des amis au déjeuner) !"
promiMessage="Tu es un champion de la clarté ! 🏆"
/>
</Section>
<Section>
## Monde 1 Terminé ! 🎊
<Panel character="promi" mood="celebrating">
@@ -94,8 +111,11 @@ WOW ! Tu as terminé tout le Monde 1 ! Tu as appris :
Tu es prêt pour de nouvelles aventures !
</Panel>
<LevelComplete
levelSlug="1-3-being-clear"
stars={3}
message="Tu as maîtrisé l'art d'être clair ! Monde 1 terminé !"
/>
</Section>

View File

@@ -1,3 +1,4 @@
<Section>
<Panel character="promi" mood="happy">
שלום! אני **Promi** 🤖, החבר הרובוט שלך! שמח מאוד להכיר אותך!
</Panel>
@@ -9,7 +10,9 @@
<Panel character="promi" mood="excited">
אני AI! אני יכול לקרוא את ההודעות שלך ולנסות לעזור. אבל הנה הסוד... אני צריך **הוראות טובות** כדי לעשות את העבודה הכי טובה!
</Panel>
</Section>
<Section>
## מה זה פרומפט?
**פרומפט** זו פשוט מילה מפוארת להודעה שאתה שולח ל-AI כמוני.
@@ -19,7 +22,9 @@
<Panel character="promi" mood="happy">
כשאתה כותב פרומפט טוב, אני יכול להבין מה אתה רוצה ולעזור לך יותר טוב! בוא נתרגל!
</Panel>
</Section>
<Section>
## בוא ננסה!
<PromptVsMistake
@@ -29,7 +34,9 @@
explanation="ההודעה הראשונה אומרת ל-Promi בדיוק איזה סוג סיפור לכתוב! השנייה קצרה מדי - Promi לא יודע איזה סוג סיפור אתה רוצה."
promiMessage="רואה? יותר פרטים עוזרים לי להבין מה אתה רוצה!"
/>
</Section>
<Section>
## חידון מהיר!
<PromptVsMistake
@@ -39,15 +46,20 @@
explanation="פרומפט משתמש במילים כדי להגיד ל-AI מה אתה צריך. אמוג'ים כיפיים אבל לא נותנים מספיק מידע!"
promiMessage="מילים הן כוח העל שלי! ככל שתספר לי יותר, כך אוכל לעזור יותר טוב!"
/>
</Section>
<Section>
## הצלחת! 🎉
<Panel character="promi" mood="celebrating">
עבודה מדהימה! למדת מה זה AI ומה זה פרומפט. אתה כבר הופך למומחה פרומפטים!
</Panel>
<LevelComplete
levelSlug="1-1-meet-promi"
stars={3}
message="למדת מה זה AI ופרומפטים!"
/>
</Section>

View File

@@ -1,7 +1,10 @@
<Section>
<Panel character="promi" mood="happy">
ברוך הבא בחזרה, חבר! מוכן לכתוב את הפרומפטים האמיתיים הראשונים שלך? יאללה! 🚀
</Panel>
</Section>
<Section>
## הקסם של מילים
כשאתה מדבר עם AI, כל מילה חשובה! בוא נראה איך הוספת יותר מילים עושה פרומפטים יותר טובים.
@@ -9,7 +12,9 @@
<Panel character="promi" mood="thinking">
תראה! אם מישהו רק אומר לי "חתול", אני לא יודע מה הם רוצים. הם רוצים תמונה? סיפור? עובדות על חתולים? אני מבולבל! 😵‍💫
</Panel>
</Section>
<Section>
## בונים פרומפטים יותר טובים
לפרומפט טוב יש **שלושה חלקים**:
@@ -21,7 +26,9 @@
<Panel character="promi" mood="excited">
בוא נבנה פרומפט ביחד!
</Panel>
</Section>
<Section>
## גרור את החלקים!
<DragDropPrompt
@@ -36,11 +43,14 @@
correctOrder={[0, 1, 2, 3]}
successMessage="מושלם! זה פרומפט נהדר!"
/>
</Section>
<Section>
## מלא את החסר!
עכשיו נסה לעשות פרומפט משלך על ידי גרירת מילות הקסם:
<MagicWords
title="צור את הפרומפט שלך! ✨"
sentence="בבקשה כתוב {{type}} על {{character}} ש{{action}}"
@@ -51,7 +61,9 @@
]}
successMessage="וואו! יצרת פרומפט מדהים!"
/>
</Section>
<Section>
## תורך לבחור!
<PromptVsMistake
@@ -61,15 +73,20 @@
explanation="הפרומפט הראשון אומר לי שזה צריך להיות מצחיק, על פינגווין, ומה הפינגווין רוצה לעשות!"
promiMessage="פרטים עושים הכל יותר טוב! אני אוהב לדעת בדיוק מה אתה רוצה!"
/>
</Section>
<Section>
## עבודה נהדרת! 🌟
<Panel character="promi" mood="celebrating">
כתבת את הפרומפטים הראשונים שלך! למדת שפרומפטים טובים צריכים: מה אתה רוצה, נושא, ופרטים. אתה ממש משתפר!
</Panel>
<LevelComplete
levelSlug="1-2-first-words"
stars={3}
message="למדת איך לכתוב את הפרומפטים הראשונים שלך!"
/>
</Section>

View File

@@ -1,7 +1,10 @@
<Section>
<Panel character="promi" mood="happy">
היי סופרסטאר! 🌟 היום נלמד את הכישור החשוב ביותר: להיות **ברור**!
</Panel>
</Section>
<Section>
## למה להיות ברור זה חשוב
דמיין לבקש מאמא "אוכל" לעומת לבקש "כריך חמאת בוטנים בלי קרום." מה נותן לך בדיוק מה שאתה רוצה?
@@ -9,11 +12,14 @@
<Panel character="promi" mood="thinking">
גם איתי ככה! כשאתה ברור, אני יודע בדיוק איך לעזור. כשאתה מעורפל, אני צריך לנחש... ואני עלול לטעות!
</Panel>
</Section>
<Section>
## ברור לעומת לא ברור
בוא נתרגל לזהות את ההבדל!
<PromptVsMistake
question="איזה פרומפט יותר ברור?"
good="כתוב שיר של 4 שורות על פרפרים בגינה, עם חריזה"
@@ -22,6 +28,7 @@
promiMessage="פרומפטים ברורים = תוצאות יותר טובות! זה כמו קסם!"
/>
<PromptVsMistake
question="מה אומר לי בדיוק מה אתה צריך?"
good="עזור לי לכתוב 3 עובדות מעניינות על דולפינים שילד בן 10 ייהנה מהן"
@@ -29,11 +36,14 @@
explanation="הפרומפט הראשון אומר לי: כמה עובדות (3), איזה סוג (מעניינות), ולמי (ילד בן 10). זה עוזר המון!"
promiMessage="כשאתה אומר לי למי זה, אני יכול להפוך את זה למושלם בשבילם!"
/>
</Section>
<Section>
## אתגר הבהירות
בוא נבנה את הפרומפט הכי ברור אי פעם!
<DragDropPrompt
title="עשה את זה בהיר כמו קריסטל! 💎"
instruction="סדר את החלקים האלה כדי לעשות פרומפט סופר ברור"
@@ -47,7 +57,9 @@
correctOrder={[0, 1, 2, 3, 4]}
successMessage="זה הפרומפט הכי ברור אי פעם! מדהים!"
/>
</Section>
<Section>
## הוסף פרטים ברורים
<MagicWords
@@ -61,7 +73,9 @@
]}
successMessage="הוספת את כל הפרטים החשובים! עבודה נהדרת!"
/>
</Section>
<Section>
## כללי הזהב של הבהירות
<Panel character="promi" mood="excited">
@@ -74,6 +88,7 @@
2. **איך** זה צריך להיות? (קצר, מצחיק, פשוט)
3. **למי** זה? (לי, לחבר שלי, לכיתה שלי)
<PromptVsMistake
question="אתגר אחרון! מי משתמש בכל שלושת הכללים?"
good="כתוב בדיחה קצרה ומצחיקה על פיצה שאני יכול לספר לחברים שלי בארוחת צהריים"
@@ -81,7 +96,9 @@
explanation="הפרומפט הנהדר כולל מה (בדיחה על פיצה), איך (קצרה ומצחיקה), ולמי (לספר לחברים בארוחת צהריים)!"
promiMessage="אתה אלוף בהירות! 🏆"
/>
</Section>
<Section>
## עולם 1 הושלם! 🎊
<Panel character="promi" mood="celebrating">
@@ -94,8 +111,11 @@
אתה מוכן להרפתקאות חדשות!
</Panel>
<LevelComplete
levelSlug="1-3-being-clear"
stars={3}
message="שלטת באומנות להיות ברור! עולם 1 הושלם!"
/>
</Section>

View File

@@ -1,3 +1,4 @@
<Section>
<Panel character="promi" mood="happy">
Ciao! Sono **Promi** 🤖, il tuo amico robot! Sono così felice di conoscerti!
</Panel>
@@ -9,7 +10,9 @@ Sai cosa significa **IA**? IA sta per **Intelligenza Artificiale**. È un modo e
<Panel character="promi" mood="excited">
Sono un'IA! Posso leggere i tuoi messaggi e cercare di aiutarti. Ma ecco il segreto... Ho bisogno di **buone istruzioni** per fare il mio lavoro al meglio!
</Panel>
</Section>
<Section>
## Cos'è un Prompt?
Un **prompt** è semplicemente una parola elegante per il messaggio che invii a un'IA come me.
@@ -19,7 +22,9 @@ Pensalo come dare indicazioni a un amico. Se dici "Vai lì!" il tuo amico non sa
<Panel character="promi" mood="happy">
Quando scrivi un buon prompt, posso capire cosa vuoi e aiutarti meglio! Pratichiamo!
</Panel>
</Section>
<Section>
## Proviamo!
<PromptVsMistake
@@ -29,7 +34,9 @@ Quando scrivi un buon prompt, posso capire cosa vuoi e aiutarti meglio! Pratichi
explanation="Il primo messaggio dice a Promi esattamente che tipo di storia scrivere! Il secondo è troppo corto - Promi non sa che tipo di storia vuoi."
promiMessage="Vedi? Più dettagli mi aiutano a capire cosa vuoi!"
/>
</Section>
<Section>
## Quiz Veloce!
<PromptVsMistake
@@ -39,15 +46,20 @@ Quando scrivi un buon prompt, posso capire cosa vuoi e aiutarti meglio! Pratichi
explanation="Un prompt usa parole per dire all'IA di cosa hai bisogno. Gli emoji sono divertenti ma non danno abbastanza informazioni!"
promiMessage="Le parole sono il mio super potere! Più mi dici, meglio posso aiutare!"
/>
</Section>
<Section>
## Ce l'hai fatta! 🎉
<Panel character="promi" mood="celebrating">
Ottimo lavoro! Hai imparato cos'è l'IA e cos'è un prompt. Stai già diventando un esperto di prompt!
</Panel>
<LevelComplete
levelSlug="1-1-meet-promi"
stars={3}
message="Hai imparato cosa sono l'IA e i prompt!"
/>
</Section>

View File

@@ -1,7 +1,10 @@
<Section>
<Panel character="promi" mood="happy">
Bentornato, amico! Pronto a scrivere i tuoi primi veri prompt? Andiamo! 🚀
</Panel>
</Section>
<Section>
## La Magia delle Parole
Quando parli con l'IA, ogni parola conta! Vediamo come aggiungere più parole rende i prompt migliori.
@@ -9,7 +12,9 @@ Quando parli con l'IA, ogni parola conta! Vediamo come aggiungere più parole re
<Panel character="promi" mood="thinking">
Guarda! Se qualcuno mi dice solo "gatto", non so cosa vogliono. Vogliono un'immagine? Una storia? Fatti sui gatti? Sono confuso! 😵‍💫
</Panel>
</Section>
<Section>
## Costruire Prompt Migliori
Un buon prompt ha **tre parti**:
@@ -21,7 +26,9 @@ Un buon prompt ha **tre parti**:
<Panel character="promi" mood="excited">
Costruiamo un prompt insieme!
</Panel>
</Section>
<Section>
## Trascina i Pezzi!
<DragDropPrompt
@@ -36,11 +43,14 @@ Costruiamo un prompt insieme!
correctOrder={[0, 1, 2, 3]}
successMessage="Perfetto! È un ottimo prompt!"
/>
</Section>
<Section>
## Riempi gli Spazi!
Ora prova a fare il tuo prompt trascinando le parole magiche:
<MagicWords
title="Crea il tuo prompt! ✨"
sentence="Per favore scrivi una {{type}} su un {{character}} che {{action}}"
@@ -51,7 +61,9 @@ Ora prova a fare il tuo prompt trascinando le parole magiche:
]}
successMessage="Wow! Hai creato un prompt fantastico!"
/>
</Section>
<Section>
## Tocca a Te Scegliere!
<PromptVsMistake
@@ -61,15 +73,20 @@ Ora prova a fare il tuo prompt trascinando le parole magiche:
explanation="Il primo prompt mi dice che deve essere divertente, è su un pinguino, E cosa vuole fare il pinguino!"
promiMessage="I dettagli rendono tutto migliore! Adoro sapere esattamente cosa vuoi!"
/>
</Section>
<Section>
## Ottimo Lavoro! 🌟
<Panel character="promi" mood="celebrating">
Hai scritto i tuoi primi prompt! Hai imparato che i buoni prompt hanno bisogno di: cosa vuoi, un argomento e dettagli. Stai diventando davvero bravo!
</Panel>
<LevelComplete
levelSlug="1-2-first-words"
stars={3}
message="Hai imparato a scrivere i tuoi primi prompt!"
/>
</Section>

View File

@@ -1,7 +1,10 @@
<Section>
<Panel character="promi" mood="happy">
Ciao superstar! 🌟 Oggi impareremo l'abilità più importante: essere **CHIARI**!
</Panel>
</Section>
<Section>
## Perché Essere Chiari è Importante
Immagina chiedere a tua mamma "cibo" vs chiedere "un panino al burro d'arachidi senza crosta." Quale ti dà esattamente quello che vuoi?
@@ -9,11 +12,14 @@ Immagina chiedere a tua mamma "cibo" vs chiedere "un panino al burro d'arachidi
<Panel character="promi" mood="thinking">
È lo stesso con me! Quando sei chiaro, so esattamente come aiutare. Quando sei vago, devo indovinare... e potrei sbagliare!
</Panel>
</Section>
<Section>
## Chiaro vs. Non Chiaro
Pratichiamo a trovare la differenza!
<PromptVsMistake
question="Quale prompt è più chiaro?"
good="Scrivi una poesia di 4 righe sulle farfalle in un giardino, usando parole in rima"
@@ -22,6 +28,7 @@ Pratichiamo a trovare la differenza!
promiMessage="Prompt chiari = risultati migliori! È come magia!"
/>
<PromptVsMistake
question="Quale mi dice esattamente di cosa hai bisogno?"
good="Aiutami a scrivere 3 fatti divertenti sui delfini che piacciono a un bambino di 10 anni"
@@ -29,11 +36,14 @@ Pratichiamo a trovare la differenza!
explanation="Il primo prompt mi dice: quanti fatti (3), che tipo (divertenti), e per chi (bambino di 10 anni). Questo aiuta molto!"
promiMessage="Quando mi dici per chi è, posso renderlo perfetto per loro!"
/>
</Section>
<Section>
## La Sfida della Chiarezza
Costruiamo il prompt più chiaro di sempre!
<DragDropPrompt
title="Rendilo cristallino! 💎"
instruction="Sistema questi pezzi per fare un prompt super chiaro"
@@ -47,7 +57,9 @@ Costruiamo il prompt più chiaro di sempre!
correctOrder={[0, 1, 2, 3, 4]}
successMessage="È il prompt più chiaro di sempre! Fantastico!"
/>
</Section>
<Section>
## Aggiungi Dettagli Chiari
<MagicWords
@@ -61,7 +73,9 @@ Costruiamo il prompt più chiaro di sempre!
]}
successMessage="Hai aggiunto tutti i dettagli importanti! Ottimo lavoro!"
/>
</Section>
<Section>
## Le Regole d'Oro della Chiarezza
<Panel character="promi" mood="excited">
@@ -74,6 +88,7 @@ Ricorda queste tre domande quando scrivi un prompt:
2. **COME** deve essere? (corto, divertente, semplice)
3. **PER CHI** è? (me, il mio amico, la mia classe)
<PromptVsMistake
question="Sfida finale! Quale usa tutte e tre le regole?"
good="Scrivi una barzelletta corta e divertente sulla pizza che posso raccontare ai miei amici a pranzo"
@@ -81,7 +96,9 @@ Ricorda queste tre domande quando scrivi un prompt:
explanation="L'ottimo prompt ha COSA (una barzelletta sulla pizza), COME (corta e divertente), e PER CHI (raccontare agli amici a pranzo)!"
promiMessage="Sei un campione di chiarezza! 🏆"
/>
</Section>
<Section>
## Mondo 1 Completato! 🎊
<Panel character="promi" mood="celebrating">
@@ -94,8 +111,11 @@ WOW! Hai finito tutto il Mondo 1! Hai imparato:
Sei pronto per nuove avventure!
</Panel>
<LevelComplete
levelSlug="1-3-being-clear"
stars={3}
message="Hai padroneggiato l'arte di essere chiaro! Mondo 1 completato!"
/>
</Section>

View File

@@ -1,3 +1,4 @@
<Section>
<Panel character="promi" mood="happy">
こんにちは!私は **Promi** 🤖、あなたのロボット友達だよ!会えてとっても嬉しい!
</Panel>
@@ -9,7 +10,9 @@
<Panel character="promi" mood="excited">
私はAIなのあなたのメッセージを読んで助けようとするよ。でもね、秘密があるの...私が一番いい仕事をするには**いい指示**が必要なんだ!
</Panel>
</Section>
<Section>
## プロンプトって何?
**プロンプト**は、私みたいなAIに送るメッセージのことだよ。
@@ -19,7 +22,9 @@
<Panel character="promi" mood="happy">
いいプロンプトを書くと、あなたが何を望んでいるかわかって、もっと上手に助けられるよ!練習しよう!
</Panel>
</Section>
<Section>
## やってみよう!
<PromptVsMistake
@@ -29,7 +34,9 @@
explanation="最初のメッセージはPromiにどんな物語を書くか正確に教えているよ2番目は短すぎる - Promiはどんな物語が欲しいかわからないよ。"
promiMessage="わかった?詳しく教えてくれると何が欲しいかわかるんだ!"
/>
</Section>
<Section>
## クイックテスト!
<PromptVsMistake
@@ -39,15 +46,20 @@
explanation="プロンプトは言葉を使ってAIに何が必要か教えるんだよ。絵文字は楽しいけど十分な情報がないの"
promiMessage="言葉は私のスーパーパワー!たくさん教えてくれるほど、もっと助けられるよ!"
/>
</Section>
<Section>
## やったね!🎉
<Panel character="promi" mood="celebrating">
すごいAIが何かとプロンプトが何かを学んだよ。もうプロンプトの専門家になりつつあるね
</Panel>
<LevelComplete
levelSlug="1-1-meet-promi"
stars={3}
message="AIとプロンプトが何かを学んだよ"
/>
</Section>

View File

@@ -1,7 +1,10 @@
<Section>
<Panel character="promi" mood="happy">
おかえり、友達!本当のプロンプトを書く準備はできた?行こう!🚀
</Panel>
</Section>
<Section>
## 言葉の魔法
AIと話すとき、すべての言葉が大事言葉を増やすとプロンプトがどう良くなるか見てみよう。
@@ -9,7 +12,9 @@ AIと話すとき、すべての言葉が大事言葉を増やすとプロン
<Panel character="promi" mood="thinking">
見て!誰かが「猫」としか言わないと、何が欲しいかわからないよ。絵?物語?猫についての事実?困っちゃう!😵‍💫
</Panel>
</Section>
<Section>
## もっといいプロンプトを作ろう
いいプロンプトには**3つの部分**があるよ:
@@ -21,7 +26,9 @@ AIと話すとき、すべての言葉が大事言葉を増やすとプロン
<Panel character="promi" mood="excited">
一緒にプロンプトを作ろう!
</Panel>
</Section>
<Section>
## ピースをドラッグしよう!
<DragDropPrompt
@@ -36,11 +43,14 @@ AIと話すとき、すべての言葉が大事言葉を増やすとプロン
correctOrder={[0, 1, 2, 3]}
successMessage="完璧!それは素晴らしいプロンプトだよ!"
/>
</Section>
<Section>
## 空欄を埋めよう!
魔法の言葉をドラッグして自分のプロンプトを作ってみよう:
<MagicWords
title="自分のプロンプトを作ろう!✨"
sentence="{{type}}を書いてください、{{character}}について、{{action}}"
@@ -51,7 +61,9 @@ AIと話すとき、すべての言葉が大事言葉を増やすとプロン
]}
successMessage="わぁ!すごいプロンプトを作ったね!"
/>
</Section>
<Section>
## あなたの番!
<PromptVsMistake
@@ -61,15 +73,20 @@ AIと話すとき、すべての言葉が大事言葉を増やすとプロン
explanation="最初のプロンプトは、おもしろくするべきこと、ペンギンについてであること、そしてペンギンが何をしたいかを教えているよ!"
promiMessage="詳細があるとすべてが良くなる!何が欲しいか正確に知るのが大好き!"
/>
</Section>
<Section>
## よくやったね!🌟
<Panel character="promi" mood="celebrating">
最初のプロンプトを書いたよ!いいプロンプトには欲しいもの、テーマ、詳細が必要だって学んだね。本当に上手になってる!
</Panel>
<LevelComplete
levelSlug="1-2-first-words"
stars={3}
message="最初のプロンプトの書き方を学んだよ!"
/>
</Section>

View File

@@ -1,7 +1,10 @@
<Section>
<Panel character="promi" mood="happy">
やあスーパースター!🌟 今日は一番大事なスキルを学ぶよ:**はっきり伝える**こと!
</Panel>
</Section>
<Section>
## なぜはっきり伝えることが大事?
お母さんに「食べ物」って頼むのと「耳のないピーナッツバターサンドイッチ」って頼むのを想像して。どっちが欲しいものを正確にくれる?
@@ -9,11 +12,14 @@
<Panel character="promi" mood="thinking">
私も同じ!はっきり言ってくれると、どう助けるか正確にわかるよ。曖昧だと推測しなきゃいけない...間違えるかも!
</Panel>
</Section>
<Section>
## はっきり vs. 曖昧
違いを見つける練習をしよう!
<PromptVsMistake
question="どのプロンプトがもっとはっきりしてる?"
good="韻を踏む言葉を使って、庭の蝶についての4行の詩を書いて"
@@ -22,6 +28,7 @@
promiMessage="はっきりしたプロンプト=いい結果!魔法みたい!"
/>
<PromptVsMistake
question="どっちが正確に何が必要か教えてる?"
good="10歳が楽しめるイルカについての楽しい事実を3つ書くの手伝って"
@@ -29,11 +36,14 @@
explanation="最初のプロンプトはいくつの事実3つ、どんな種類楽しい、誰のため10歳を教えているよ。すごく助かる"
promiMessage="誰のためか教えてくれると、その人にぴったりに作れるよ!"
/>
</Section>
<Section>
## はっきりチャレンジ
今までで一番はっきりしたプロンプトを作ろう!
<DragDropPrompt
title="クリスタルみたいにはっきりさせよう!💎"
instruction="これらのピースを並べてスーパーはっきりしたプロンプトを作ろう"
@@ -47,7 +57,9 @@
correctOrder={[0, 1, 2, 3, 4]}
successMessage="今までで一番はっきりしたプロンプト!すごい!"
/>
</Section>
<Section>
## はっきりした詳細を追加
<MagicWords
@@ -61,7 +73,9 @@
]}
successMessage="大事な詳細を全部追加した!よくやったね!"
/>
</Section>
<Section>
## はっきりの黄金ルール
<Panel character="promi" mood="excited">
@@ -74,6 +88,7 @@
2. **どう**あるべき?(短い、おもしろい、シンプル)
3. **誰**のため?(私、友達、クラス)
<PromptVsMistake
question="最終チャレンジどれが3つのルール全部使ってる"
good="昼ごはんで友達に話せるピザについての短くておもしろいジョークを書いて"
@@ -81,7 +96,9 @@
explanation="いいプロンプトには何(ピザのジョーク)、どう(短くておもしろい)、誰(昼ごはんで友達に話す)がある!"
promiMessage="あなたははっきりチャンピオンだ!🏆"
/>
</Section>
<Section>
## ワールド1完了🎊
<Panel character="promi" mood="celebrating">
@@ -94,8 +111,11 @@
新しい冒険の準備ができたよ!
</Panel>
<LevelComplete
levelSlug="1-3-being-clear"
stars={3}
message="はっきり伝える技をマスターしたワールド1完了"
/>
</Section>

View File

@@ -1,3 +1,4 @@
<Section>
<Panel character="promi" mood="happy">
안녕! 나는 **Promi** 🤖, 네 로봇 친구야! 만나서 정말 반가워!
</Panel>
@@ -9,7 +10,9 @@
<Panel character="promi" mood="excited">
나는 AI야! 네 메시지를 읽고 도와주려고 해. 하지만 여기 비밀이 있어... 최고의 일을 하려면 **좋은 지시**가 필요해!
</Panel>
</Section>
<Section>
## 프롬프트가 뭐야?
**프롬프트**는 나 같은 AI에게 보내는 메시지를 말하는 멋진 단어야.
@@ -19,7 +22,9 @@
<Panel character="promi" mood="happy">
좋은 프롬프트를 쓰면 네가 뭘 원하는지 이해하고 더 잘 도와줄 수 있어! 연습해보자!
</Panel>
</Section>
<Section>
## 해보자!
<PromptVsMistake
@@ -29,7 +34,9 @@
explanation="첫 번째 메시지는 Promi에게 어떤 종류의 이야기를 쓸지 정확히 알려줘! 두 번째는 너무 짧아서 어떤 이야기를 원하는지 몰라."
promiMessage="봤지? 더 자세히 알려주면 뭘 원하는지 이해할 수 있어!"
/>
</Section>
<Section>
## 퀴즈!
<PromptVsMistake
@@ -39,15 +46,20 @@
explanation="프롬프트는 AI에게 뭐가 필요한지 말로 알려주는 거야. 이모지는 재미있지만 충분한 정보를 주지 않아!"
promiMessage="말은 내 초능력이야! 더 많이 알려줄수록 더 잘 도와줄 수 있어!"
/>
</Section>
<Section>
## 해냈어! 🎉
<Panel character="promi" mood="celebrating">
멋져! AI가 뭔지, 프롬프트가 뭔지 배웠어. 벌써 프롬프트 전문가가 되어가고 있어!
</Panel>
<LevelComplete
levelSlug="1-1-meet-promi"
stars={3}
message="AI와 프롬프트가 뭔지 배웠어!"
/>
</Section>

View File

@@ -1,7 +1,10 @@
<Section>
<Panel character="promi" mood="happy">
다시 왔구나, 친구! 첫 번째 진짜 프롬프트를 쓸 준비 됐어? 가보자! 🚀
</Panel>
</Section>
<Section>
## 단어의 마법
AI와 대화할 때 모든 단어가 중요해! 더 많은 단어를 추가하면 프롬프트가 어떻게 좋아지는지 봐보자.
@@ -9,7 +12,9 @@ AI와 대화할 때 모든 단어가 중요해! 더 많은 단어를 추가하
<Panel character="promi" mood="thinking">
이것 봐! 누군가 나에게 "고양이"라고만 하면 뭘 원하는지 몰라. 그림? 이야기? 고양이에 대한 사실? 혼란스러워! 😵‍💫
</Panel>
</Section>
<Section>
## 더 좋은 프롬프트 만들기
좋은 프롬프트는 **세 가지 부분**이 있어:
@@ -21,7 +26,9 @@ AI와 대화할 때 모든 단어가 중요해! 더 많은 단어를 추가하
<Panel character="promi" mood="excited">
같이 프롬프트를 만들어보자!
</Panel>
</Section>
<Section>
## 조각을 드래그해!
<DragDropPrompt
@@ -36,11 +43,14 @@ AI와 대화할 때 모든 단어가 중요해! 더 많은 단어를 추가하
correctOrder={[0, 1, 2, 3]}
successMessage="완벽해! 멋진 프롬프트야!"
/>
</Section>
<Section>
## 빈칸을 채워!
이제 마법의 단어를 드래그해서 네 프롬프트를 만들어봐:
<MagicWords
title="네 프롬프트를 만들어! ✨"
sentence="{{type}}을 써줘, {{character}}에 대해, {{action}}"
@@ -51,7 +61,9 @@ AI와 대화할 때 모든 단어가 중요해! 더 많은 단어를 추가하
]}
successMessage="와! 멋진 프롬프트를 만들었어!"
/>
</Section>
<Section>
## 네 차례야!
<PromptVsMistake
@@ -61,15 +73,20 @@ AI와 대화할 때 모든 단어가 중요해! 더 많은 단어를 추가하
explanation="첫 번째 프롬프트는 재미있어야 하고, 펭귄에 대한 거고, 펭귄이 뭘 하고 싶은지 알려줘!"
promiMessage="세부사항은 모든 걸 더 좋게 해! 정확히 뭘 원하는지 아는 게 좋아!"
/>
</Section>
<Section>
## 잘했어! 🌟
<Panel character="promi" mood="celebrating">
첫 번째 프롬프트를 썼어! 좋은 프롬프트에는 원하는 것, 주제, 세부사항이 필요하다는 걸 배웠어. 정말 잘하고 있어!
</Panel>
<LevelComplete
levelSlug="1-2-first-words"
stars={3}
message="첫 번째 프롬프트 쓰는 법을 배웠어!"
/>
</Section>

View File

@@ -1,7 +1,10 @@
<Section>
<Panel character="promi" mood="happy">
안녕 슈퍼스타! 🌟 오늘은 가장 중요한 기술을 배울 거야: **명확하게** 말하기!
</Panel>
</Section>
<Section>
## 왜 명확하게 말하는 게 중요할까
엄마에게 "음식"을 달라고 하는 것과 "가장자리 없는 땅콩버터 샌드위치"를 달라고 하는 것을 상상해봐. 어떤 게 원하는 걸 정확히 주지?
@@ -9,11 +12,14 @@
<Panel character="promi" mood="thinking">
나도 마찬가지야! 명확하게 말하면 어떻게 도와야 할지 정확히 알아. 모호하면 추측해야 하고... 틀릴 수도 있어!
</Panel>
</Section>
<Section>
## 명확 vs. 불명확
차이를 찾는 연습을 해보자!
<PromptVsMistake
question="어떤 프롬프트가 더 명확해?"
good="정원의 나비에 대해 운율이 맞는 단어로 4줄 시를 써줘"
@@ -22,6 +28,7 @@
promiMessage="명확한 프롬프트 = 더 좋은 결과! 마법 같아!"
/>
<PromptVsMistake
question="어떤 게 정확히 뭐가 필요한지 알려줘?"
good="10살이 좋아할 돌고래에 대한 재미있는 사실 3개를 쓰는 걸 도와줘"
@@ -29,11 +36,14 @@
explanation="첫 번째 프롬프트는 말해줘: 몇 개의 사실 (3개), 어떤 종류 (재미있는), 누구를 위한 (10살). 많이 도움돼!"
promiMessage="누구를 위한지 알려주면 그들에게 완벽하게 만들 수 있어!"
/>
</Section>
<Section>
## 명확성 도전
지금까지 가장 명확한 프롬프트를 만들어보자!
<DragDropPrompt
title="수정처럼 명확하게 만들어! 💎"
instruction="이 조각들을 배열해서 엄청 명확한 프롬프트를 만들어"
@@ -47,7 +57,9 @@
correctOrder={[0, 1, 2, 3, 4]}
successMessage="지금까지 가장 명확한 프롬프트야! 대단해!"
/>
</Section>
<Section>
## 명확한 세부사항 추가
<MagicWords
@@ -61,7 +73,9 @@
]}
successMessage="모든 중요한 세부사항을 추가했어! 잘했어!"
/>
</Section>
<Section>
## 명확성의 황금 규칙
<Panel character="promi" mood="excited">
@@ -74,6 +88,7 @@
2. **어떻게** 되어야 해? (짧게, 재미있게, 간단하게)
3. **누구를** 위한 거야? (나, 친구, 우리 반)
<PromptVsMistake
question="마지막 도전! 세 가지 규칙을 모두 사용한 건?"
good="점심에 친구들에게 말할 수 있는 피자에 대한 짧고 재미있는 농담을 써줘"
@@ -81,7 +96,9 @@
explanation="좋은 프롬프트에는 뭘 (피자 농담), 어떻게 (짧고 재미있게), 누구를 위해 (점심에 친구들에게 말할)가 있어!"
promiMessage="넌 명확성 챔피언이야! 🏆"
/>
</Section>
<Section>
## 월드 1 완료! 🎊
<Panel character="promi" mood="celebrating">
@@ -94,8 +111,11 @@
새로운 모험을 위한 준비가 됐어!
</Panel>
<LevelComplete
levelSlug="1-3-being-clear"
stars={3}
message="명확하게 말하기를 마스터했어! 월드 1 완료!"
/>
</Section>

View File

@@ -1,3 +1,4 @@
<Section>
<Panel character="promi" mood="happy">
Olá! Eu sou o **Promi** 🤖, seu amigo robô! Estou muito feliz em conhecer você!
</Panel>
@@ -9,7 +10,9 @@ Você sabe o que **IA** significa? IA significa **Inteligência Artificial**. É
<Panel character="promi" mood="excited">
Eu sou uma IA! Posso ler suas mensagens e tentar ajudar. Mas aqui está o segredo... Preciso de **boas instruções** para fazer meu melhor trabalho!
</Panel>
</Section>
<Section>
## O que é um Prompt?
Um **prompt** é apenas uma palavra chique para a mensagem que você envia para uma IA como eu.
@@ -19,7 +22,9 @@ Pense nisso como dar direções para um amigo. Se você diz "Vá lá!" seu amigo
<Panel character="promi" mood="happy">
Quando você escreve um bom prompt, eu posso entender o que você quer e te ajudar melhor! Vamos praticar!
</Panel>
</Section>
<Section>
## Vamos Tentar!
<PromptVsMistake
@@ -29,7 +34,9 @@ Quando você escreve um bom prompt, eu posso entender o que você quer e te ajud
explanation="A primeira mensagem diz ao Promi exatamente que tipo de história escrever! A segunda é muito curta - Promi não sabe que tipo de história você quer."
promiMessage="Viu? Mais detalhes me ajudam a entender o que você quer!"
/>
</Section>
<Section>
## Quiz Rápido!
<PromptVsMistake
@@ -39,15 +46,20 @@ Quando você escreve um bom prompt, eu posso entender o que você quer e te ajud
explanation="Um prompt usa palavras para dizer à IA o que você precisa. Emojis são divertidos mas não dão informação suficiente!"
promiMessage="Palavras são meu super poder! Quanto mais você me contar, melhor posso ajudar!"
/>
</Section>
<Section>
## Você Conseguiu! 🎉
<Panel character="promi" mood="celebrating">
Incrível trabalho! Você aprendeu o que é IA e o que é um prompt. Você já está se tornando um expert em prompts!
</Panel>
<LevelComplete
levelSlug="1-1-meet-promi"
stars={3}
message="Você aprendeu o que são IA e prompts!"
/>
</Section>

View File

@@ -1,7 +1,10 @@
<Section>
<Panel character="promi" mood="happy">
Bem-vindo de volta, amigo! Pronto para escrever seus primeiros prompts de verdade? Vamos lá! 🚀
</Panel>
</Section>
<Section>
## A Magia das Palavras
Quando você fala com IA, cada palavra importa! Vamos ver como adicionar mais palavras torna os prompts melhores.
@@ -9,7 +12,9 @@ Quando você fala com IA, cada palavra importa! Vamos ver como adicionar mais pa
<Panel character="promi" mood="thinking">
Olha isso! Se alguém só me diz "gato", não sei o que querem. Querem uma imagem? Uma história? Fatos sobre gatos? Estou confuso! 😵‍💫
</Panel>
</Section>
<Section>
## Construindo Prompts Melhores
Um bom prompt tem **três partes**:
@@ -21,7 +26,9 @@ Um bom prompt tem **três partes**:
<Panel character="promi" mood="excited">
Vamos construir um prompt juntos!
</Panel>
</Section>
<Section>
## Arraste as Peças!
<DragDropPrompt
@@ -36,11 +43,14 @@ Vamos construir um prompt juntos!
correctOrder={[0, 1, 2, 3]}
successMessage="Perfeito! Esse é um ótimo prompt!"
/>
</Section>
<Section>
## Preencha os Espaços!
Agora tente fazer seu próprio prompt arrastando as palavras mágicas:
<MagicWords
title="Crie seu próprio prompt! ✨"
sentence="Por favor escreva um {{type}} sobre um {{character}} que {{action}}"
@@ -51,7 +61,9 @@ Agora tente fazer seu próprio prompt arrastando as palavras mágicas:
]}
successMessage="Uau! Você criou um prompt incrível!"
/>
</Section>
<Section>
## Sua Vez de Escolher!
<PromptVsMistake
@@ -61,15 +73,20 @@ Agora tente fazer seu próprio prompt arrastando as palavras mágicas:
explanation="O primeiro prompt me diz que deve ser engraçado, é sobre um pinguim, E o que o pinguim quer fazer!"
promiMessage="Detalhes tornam tudo melhor! Adoro saber exatamente o que você quer!"
/>
</Section>
<Section>
## Ótimo Trabalho! 🌟
<Panel character="promi" mood="celebrating">
Você escreveu seus primeiros prompts! Aprendeu que bons prompts precisam de: o que você quer, um assunto e detalhes. Você está ficando muito bom nisso!
</Panel>
<LevelComplete
levelSlug="1-2-first-words"
stars={3}
message="Você aprendeu a escrever seus primeiros prompts!"
/>
</Section>

Some files were not shown because too many files have changed in this diff Show More