mirror of
https://github.com/f/awesome-chatgpt-prompts.git
synced 2026-03-02 23:07:02 +00:00
feat(kids): Add new interactive elements for kids levels
This commit is contained in:
201
.windsurf/plans/kids-interactive-elements.md
Normal file
201
.windsurf/plans/kids-interactive-elements.md
Normal 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?
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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": "جبل الإتقان" } },
|
||||
|
||||
@@ -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ğı" } },
|
||||
|
||||
@@ -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" } },
|
||||
|
||||
@@ -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": "Βουνό Μαεστρίας" } },
|
||||
|
||||
172
messages/en.json
172
messages/en.json
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" } },
|
||||
|
||||
@@ -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": "کوه استادی" } },
|
||||
|
||||
@@ -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" } },
|
||||
|
||||
@@ -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": "הר המומחיות" } },
|
||||
|
||||
@@ -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" } },
|
||||
|
||||
@@ -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": "マスター山" } },
|
||||
|
||||
@@ -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": "마스터 산" } },
|
||||
|
||||
@@ -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" } },
|
||||
|
||||
@@ -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": "Гора мастерства" } },
|
||||
|
||||
126
messages/tr.json
126
messages/tr.json
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
16
public/sounds/README.md
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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 />;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
196
src/components/kids/elements/example-matcher.tsx
Normal file
196
src/components/kids/elements/example-matcher.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
134
src/components/kids/elements/level-slides.tsx
Normal file
134
src/components/kids/elements/level-slides.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
287
src/components/kids/elements/pixel-art.tsx
Normal file
287
src/components/kids/elements/pixel-art.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
208
src/components/kids/elements/prompt-doctor.tsx
Normal file
208
src/components/kids/elements/prompt-doctor.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
203
src/components/kids/elements/prompt-lab.tsx
Normal file
203
src/components/kids/elements/prompt-lab.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
204
src/components/kids/elements/prompt-parts.tsx
Normal file
204
src/components/kids/elements/prompt-parts.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
212
src/components/kids/elements/step-by-step.tsx
Normal file
212
src/components/kids/elements/step-by-step.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
204
src/components/kids/elements/word-predictor.tsx
Normal file
204
src/components/kids/elements/word-predictor.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
347
src/components/kids/layout/background-music.tsx
Normal file
347
src/components/kids/layout/background-music.tsx
Normal 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;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
252
src/components/kids/layout/kids-home-content.tsx
Normal file
252
src/components/kids/layout/kids-home-content.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
217
src/components/kids/layout/level-content-wrapper.tsx
Normal file
217
src/components/kids/layout/level-content-wrapper.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
210
src/components/kids/layout/settings-modal.tsx
Normal file
210
src/components/kids/layout/settings-modal.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
46
src/components/kids/providers/level-context.tsx
Normal file
46
src/components/kids/providers/level-context.tsx
Normal 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;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
81
src/content/kids/en/2-1-missing-details.mdx
Normal file
81
src/content/kids/en/2-1-missing-details.mdx
Normal 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>
|
||||
97
src/content/kids/en/2-2-who-and-what.mdx
Normal file
97
src/content/kids/en/2-2-who-and-what.mdx
Normal 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>
|
||||
96
src/content/kids/en/2-3-when-and-where.mdx
Normal file
96
src/content/kids/en/2-3-when-and-where.mdx
Normal 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>
|
||||
104
src/content/kids/en/2-4-detail-detective.mdx
Normal file
104
src/content/kids/en/2-4-detail-detective.mdx
Normal 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>
|
||||
71
src/content/kids/en/3-1-setting-the-scene.mdx
Normal file
71
src/content/kids/en/3-1-setting-the-scene.mdx
Normal 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>
|
||||
104
src/content/kids/en/3-2-show-dont-tell.mdx
Normal file
104
src/content/kids/en/3-2-show-dont-tell.mdx
Normal 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>
|
||||
87
src/content/kids/en/3-3-format-finder.mdx
Normal file
87
src/content/kids/en/3-3-format-finder.mdx
Normal 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>
|
||||
87
src/content/kids/en/3-4-context-champion.mdx
Normal file
87
src/content/kids/en/3-4-context-champion.mdx
Normal 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>
|
||||
86
src/content/kids/en/4-1-pretend-time.mdx
Normal file
86
src/content/kids/en/4-1-pretend-time.mdx
Normal 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>
|
||||
87
src/content/kids/en/4-2-story-starters.mdx
Normal file
87
src/content/kids/en/4-2-story-starters.mdx
Normal 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>
|
||||
88
src/content/kids/en/4-3-character-creator.mdx
Normal file
88
src/content/kids/en/4-3-character-creator.mdx
Normal 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>
|
||||
85
src/content/kids/en/4-4-world-builder.mdx
Normal file
85
src/content/kids/en/4-4-world-builder.mdx
Normal 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>
|
||||
107
src/content/kids/en/5-1-perfect-prompt.mdx
Normal file
107
src/content/kids/en/5-1-perfect-prompt.mdx
Normal 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>
|
||||
108
src/content/kids/en/5-2-fix-it-up.mdx
Normal file
108
src/content/kids/en/5-2-fix-it-up.mdx
Normal 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>
|
||||
106
src/content/kids/en/5-3-prompt-remix.mdx
Normal file
106
src/content/kids/en/5-3-prompt-remix.mdx
Normal 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>
|
||||
102
src/content/kids/en/5-4-graduation-day.mdx
Normal file
102
src/content/kids/en/5-4-graduation-day.mdx
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user