);
}
diff --git a/frontend/src/components/ChallengeSelector.jsx b/frontend/src/components/ChallengeSelector.jsx
new file mode 100644
index 0000000..29d6f81
--- /dev/null
+++ b/frontend/src/components/ChallengeSelector.jsx
@@ -0,0 +1,125 @@
+import { challenges } from '../data/challenges';
+
+/**
+ * ChallengeSelector Component
+ * Let users select different challenges to solve
+ */
+function ChallengeSelector({ currentChallenge, onSelectChallenge, onClose }) {
+ const getDifficultyColor = (difficulty) => {
+ switch (difficulty) {
+ case 'Beginner':
+ return 'bg-green-100 text-green-700 border-green-300';
+ case 'Intermediate':
+ return 'bg-yellow-100 text-yellow-700 border-yellow-300';
+ case 'Advanced':
+ return 'bg-red-100 text-red-700 border-red-300';
+ default:
+ return 'bg-blue-100 text-blue-700 border-blue-300';
+ }
+ };
+
+ return (
+
+ );
+}
+
+export default ChallengeSelector;
diff --git a/frontend/src/components/LessonCard.jsx b/frontend/src/components/LessonCard.jsx
index 75dcd39..1c7a9f4 100644
--- a/frontend/src/components/LessonCard.jsx
+++ b/frontend/src/components/LessonCard.jsx
@@ -1,7 +1,6 @@
'use client'
import { useState } from 'react';
-import { useRouter } from 'next/navigation';
/**
* LessonCard Component
@@ -9,12 +8,10 @@ import { useRouter } from 'next/navigation';
*/
export default function LessonCard({ lesson, progress, onProgressUpdate, allLessons }) {
const [isExpanded, setIsExpanded] = useState(false);
- const router = useRouter();
// Check if prerequisites are met
- const prerequisitesMet = lesson.prerequisites.every(prereqId => {
- const prereqProgress = allLessons.find(l => l.id === prereqId)?.id;
- return prereqProgress && (progress[prereqId] === 'completed' || progress[prereqId] === 'in-progress');
+ const prerequisitesMet = lesson.prerequisites.length === 0 || lesson.prerequisites.every(prereqId => {
+ return progress[prereqId] === 'completed' || progress[prereqId] === 'in-progress';
});
// Get progress badge color
@@ -153,12 +150,12 @@ export default function LessonCard({ lesson, progress, onProgressUpdate, allLess
{prerequisitesMet && (
diff --git a/frontend/src/components/LessonContent.jsx b/frontend/src/components/LessonContent.jsx
new file mode 100644
index 0000000..d5c853d
--- /dev/null
+++ b/frontend/src/components/LessonContent.jsx
@@ -0,0 +1,192 @@
+/**
+ * LessonContent Component
+ * Displays educational content explaining Agentic AI concepts
+ */
+function LessonContent({ nodes }) {
+ const lessons = {
+ sense: {
+ title: '👁️ Sense',
+ tagline: 'Perceive the environment',
+ description: 'Agents gather information from their surroundings through sensors, APIs, and data inputs.',
+ keyPoints: [
+ 'Collects data from environment',
+ 'Detects obstacles and targets',
+ 'Real-time monitoring'
+ ],
+ example: 'Self-driving cars use cameras to detect obstacles',
+ tryThis: 'Drag a SENSE block to help your agent see!'
+ },
+ plan: {
+ title: '🧠 Plan',
+ tagline: 'Think and decide',
+ description: 'Agents analyze data, evaluate options, and choose the best action. This is where intelligence happens.',
+ keyPoints: [
+ 'Breaks goals into steps',
+ 'Weighs different options',
+ 'Predicts outcomes'
+ ],
+ example: 'Chess AI evaluates millions of moves to find the best one',
+ tryThis: 'Connect SENSE → PLAN for smart decisions!'
+ },
+ act: {
+ title: '⚡ Act',
+ tagline: 'Execute the plan',
+ description: 'Agents execute their decisions and interact with the environment to achieve goals.',
+ keyPoints: [
+ 'Converts plans to actions',
+ 'Manipulates the environment',
+ 'Creates feedback loops'
+ ],
+ example: 'Robot arm picks and places objects based on its plan',
+ tryThis: 'Add ACT to see your agent move in the sandbox!'
+ },
+ reflect: {
+ title: '💭 Reflect',
+ tagline: 'Learn and improve',
+ description: 'Agents analyze outcomes, update memory, and improve future performance through learning.',
+ keyPoints: [
+ 'Evaluates what worked',
+ 'Stores successful strategies',
+ 'Adapts based on experience'
+ ],
+ example: 'AlphaGo learns from game outcomes to play better',
+ tryThis: 'Complete the cycle with REFLECT for learning!'
+ }
+ };
+
+ // Determine primary lesson to display
+ const getCurrentLesson = () => {
+ if (nodes.length === 0) {
+ return {
+ title: '🎓 Get Started',
+ tagline: 'Build your first agent',
+ description: 'Agents perceive, think, act, and learn in a continuous cycle.',
+ keyPoints: [
+ 'Drag blocks from the sidebar',
+ 'Connect them in order',
+ 'Click Run to see it work'
+ ],
+ example: 'Try building: SENSE → PLAN → ACT → REFLECT',
+ tryThis: 'Drag your first block to begin!'
+ };
+ }
+
+ // Show lesson for most recently added block
+ const lastBlock = nodes[nodes.length - 1];
+ const blockType = lastBlock.label.toLowerCase();
+
+ if (lessons[blockType]) {
+ return lessons[blockType];
+ }
+
+ // Default to sense if block type doesn't match
+ return lessons.sense;
+ };
+
+ const currentLesson = getCurrentLesson();
+
+ // Additional guidance based on current setup
+ const getGuidance = () => {
+ const blockTypes = nodes.map(n => n.label);
+ const hasSense = blockTypes.includes('SENSE');
+ const hasPlan = blockTypes.includes('PLAN');
+ const hasAct = blockTypes.includes('ACT');
+ const hasReflect = blockTypes.includes('REFLECT');
+
+ if (hasSense && hasPlan && hasAct && hasReflect) {
+ return { type: 'success', message: 'Complete! Click "Run Agent" to test' };
+ }
+ if (hasAct && !hasSense) {
+ return { type: 'warning', message: 'Add SENSE before ACT' };
+ }
+ if (hasSense && hasAct && !hasPlan) {
+ return { type: 'info', message: 'Add PLAN for smarter decisions' };
+ }
+ if (hasAct && !hasReflect) {
+ return { type: 'info', message: 'Add REFLECT to enable learning' };
+ }
+
+ return null;
+ };
+
+ const guidance = getGuidance();
+ const guidanceStyles = {
+ success: 'bg-green-50 border-l-4 border-green-500 text-green-800',
+ warning: 'bg-orange-50 border-l-4 border-orange-500 text-orange-800',
+ info: 'bg-blue-50 border-l-4 border-blue-500 text-blue-800'
+ };
+
+ return (
+
+ {/* Lesson Header */}
+
+
{currentLesson.title}
+
{currentLesson.tagline}
+
+
+ {/* Description */}
+
{currentLesson.description}
+
+ {/* Key Points */}
+
+
What it does:
+
+ {currentLesson.keyPoints.map((point, idx) => (
+ -
+ •
+ {point}
+
+ ))}
+
+
+
+ {/* Example */}
+
+
Example:
+
{currentLesson.example}
+
+
+ {/* Try This */}
+
+
💡 Try This:
+
{currentLesson.tryThis}
+
+
+ {/* Current Progress Guidance */}
+ {guidance && (
+
+ )}
+
+ {/* SPAR Cycle Diagram - Compact */}
+
+
Agent Cycle
+
+
n.label === 'SENSE') ? 'bg-blue-500 text-white shadow-md' : 'bg-gray-100 text-gray-500'}`}>
+ 👁️
+ SENSE
+
+
↓
+
n.label === 'PLAN') ? 'bg-yellow-500 text-white shadow-md' : 'bg-gray-100 text-gray-500'}`}>
+ 🧠
+ PLAN
+
+
↓
+
n.label === 'ACT') ? 'bg-green-500 text-white shadow-md' : 'bg-gray-100 text-gray-500'}`}>
+ ⚡
+ ACT
+
+
↓
+
n.label === 'REFLECT') ? 'bg-purple-500 text-white shadow-md' : 'bg-gray-100 text-gray-500'}`}>
+ 💭
+ REFLECT
+
+
↻ Repeat
+
+
+
+ );
+}
+
+export default LessonContent;
diff --git a/frontend/src/components/SimulationSandbox.jsx b/frontend/src/components/SimulationSandbox.jsx
new file mode 100644
index 0000000..f63c5ea
--- /dev/null
+++ b/frontend/src/components/SimulationSandbox.jsx
@@ -0,0 +1,434 @@
+import { useState, useEffect, useCallback } from 'react';
+
+/**
+ * SimulationSandbox Component
+ * Interactive sandbox for testing agent behavior with goals and obstacles
+ */
+function SimulationSandbox({ nodes, challenge, onSuccess, onFailure }) {
+ const [agentPos, setAgentPos] = useState({ x: 10, y: 50 });
+ const [agentDirection, setAgentDirection] = useState('right'); // right, left, up, down
+ const [collectedItems, setCollectedItems] = useState([]);
+ const [isRunning, setIsRunning] = useState(false);
+ const [currentBlockIndex, setCurrentBlockIndex] = useState(-1);
+ const [executionLog, setExecutionLog] = useState([]);
+ const [simulationState, setSimulationState] = useState('idle'); // idle, running, success, failure
+
+ // Reset simulation
+ const resetSimulation = useCallback(() => {
+ setAgentPos({ x: 10, y: 50 });
+ setAgentDirection('right');
+ setCollectedItems([]);
+ setIsRunning(false);
+ setCurrentBlockIndex(-1);
+ setExecutionLog([]);
+ setSimulationState('idle');
+ }, []);
+
+ // Check if position collides with obstacle
+ const checkCollision = useCallback((x, y) => {
+ if (!challenge?.obstacles) return false;
+ return challenge.obstacles.some(obs =>
+ x >= obs.x && x <= obs.x + obs.width &&
+ y >= obs.y && y <= obs.y + obs.height
+ );
+ }, [challenge]);
+
+ // Check if position has collectible item
+ const checkItem = useCallback((x, y) => {
+ if (!challenge?.items) return null;
+ return challenge.items.find(item =>
+ Math.abs(x - item.x) < 5 && Math.abs(y - item.y) < 5 &&
+ !collectedItems.includes(item.id)
+ );
+ }, [challenge, collectedItems]);
+
+ // Check if goal is reached
+ const checkGoal = useCallback((x, y) => {
+ if (!challenge?.goal) return false;
+ const distance = Math.sqrt(
+ Math.pow(x - challenge.goal.x, 2) + Math.pow(y - challenge.goal.y, 2)
+ );
+ return distance < 8;
+ }, [challenge]);
+
+ // Check if all success criteria are met
+ const checkSuccess = useCallback(() => {
+ const criteria = challenge?.successCriteria;
+ if (!criteria) return false;
+
+ let allMet = true;
+
+ // Check if agent reached goal
+ if (criteria.reachGoal) {
+ allMet = allMet && checkGoal(agentPos.x, agentPos.y);
+ }
+
+ // Check if all items collected
+ if (criteria.collectAllItems && challenge.items) {
+ allMet = allMet && collectedItems.length === challenge.items.length;
+ }
+
+ // Check if specific blocks were used
+ if (criteria.useBlocks) {
+ const usedBlocks = nodes.map(n => n.label);
+ allMet = allMet && criteria.useBlocks.every(block => usedBlocks.includes(block));
+ }
+
+ // Check if no collisions occurred
+ if (criteria.noCollisions) {
+ const hasCollisionLog = executionLog.some(log => log.type === 'collision');
+ allMet = allMet && !hasCollisionLog;
+ }
+
+ return allMet;
+ }, [challenge, agentPos, collectedItems, nodes, executionLog, checkGoal]);
+
+ // Execute a single block
+ const executeBlock = useCallback((block, index) => {
+ return new Promise((resolve) => {
+ setCurrentBlockIndex(index);
+
+ setTimeout(() => {
+ switch (block.label) {
+ case 'SENSE':
+ // Check surroundings
+ const obstacleAhead = checkCollision(agentPos.x + 10, agentPos.y);
+ const itemNearby = checkItem(agentPos.x, agentPos.y);
+
+ setExecutionLog(prev => [...prev, {
+ type: 'sense',
+ message: obstacleAhead ? '⚠️ Obstacle detected ahead!' : '✓ Path is clear',
+ details: itemNearby ? `Item detected: ${itemNearby.type}` : null
+ }]);
+ break;
+
+ case 'PLAN':
+ // Analyze and decide
+ const goalDirection = challenge?.goal ?
+ (challenge.goal.x > agentPos.x ? 'right' : 'left') : 'right';
+
+ setExecutionLog(prev => [...prev, {
+ type: 'plan',
+ message: `🧠 Planning: Move ${goalDirection} toward goal`,
+ details: `Current: (${Math.round(agentPos.x)}, ${Math.round(agentPos.y)})`
+ }]);
+ break;
+
+ case 'ACT':
+ // Move agent
+ let newX = agentPos.x;
+ let newY = agentPos.y;
+
+ switch (agentDirection) {
+ case 'right':
+ newX += 15;
+ break;
+ case 'left':
+ newX -= 15;
+ break;
+ case 'up':
+ newY -= 15;
+ break;
+ case 'down':
+ newY += 15;
+ break;
+ }
+
+ // Check for collision
+ if (checkCollision(newX, newY)) {
+ setExecutionLog(prev => [...prev, {
+ type: 'collision',
+ message: '💥 Collision! Agent hit an obstacle',
+ details: 'Failed to move'
+ }]);
+ } else {
+ setAgentPos({ x: newX, y: newY });
+
+ // Check for items
+ const item = checkItem(newX, newY);
+ if (item) {
+ setCollectedItems(prev => [...prev, item.id]);
+ setExecutionLog(prev => [...prev, {
+ type: 'collect',
+ message: `✨ Collected: ${item.type}`,
+ details: `Items: ${collectedItems.length + 1}/${challenge.items?.length || 0}`
+ }]);
+ }
+
+ setExecutionLog(prev => [...prev, {
+ type: 'act',
+ message: `⚡ Moved ${agentDirection}`,
+ details: `Position: (${Math.round(newX)}, ${Math.round(newY)})`
+ }]);
+ }
+ break;
+
+ case 'REFLECT':
+ // Learn from experience
+ const goalReached = checkGoal(agentPos.x, agentPos.y);
+ const itemsCollected = collectedItems.length;
+
+ setExecutionLog(prev => [...prev, {
+ type: 'reflect',
+ message: goalReached ? '🎯 Goal reached!' : '🤔 Analyzing performance...',
+ details: `Items collected: ${itemsCollected}, Goal: ${goalReached ? 'Yes' : 'No'}`
+ }]);
+ break;
+
+ default:
+ setExecutionLog(prev => [...prev, {
+ type: 'info',
+ message: `Executing: ${block.label}`,
+ details: null
+ }]);
+ }
+
+ resolve();
+ }, 1000); // 1 second per block execution
+ });
+ }, [agentPos, agentDirection, collectedItems, challenge, checkCollision, checkItem, checkGoal]);
+
+ // Run simulation
+ const runSimulation = useCallback(async () => {
+ if (nodes.length === 0) {
+ setExecutionLog([{ type: 'error', message: '❌ No blocks to execute!', details: 'Add blocks to your agent' }]);
+ return;
+ }
+
+ setIsRunning(true);
+ setSimulationState('running');
+ setExecutionLog([{ type: 'info', message: '🚀 Starting simulation...', details: null }]);
+
+ // Execute each block sequentially
+ for (let i = 0; i < nodes.length; i++) {
+ await executeBlock(nodes[i], i);
+ }
+
+ setCurrentBlockIndex(-1);
+ setIsRunning(false);
+
+ // Check success criteria
+ setTimeout(() => {
+ if (checkSuccess()) {
+ setSimulationState('success');
+ setExecutionLog(prev => [...prev, {
+ type: 'success',
+ message: '🎉 Challenge Complete!',
+ details: 'All criteria met'
+ }]);
+ if (onSuccess) onSuccess();
+ } else {
+ setSimulationState('failure');
+ setExecutionLog(prev => [...prev, {
+ type: 'failure',
+ message: '❌ Challenge Failed',
+ details: 'Review the success criteria'
+ }]);
+ if (onFailure) onFailure();
+ }
+ }, 500);
+ }, [nodes, executeBlock, checkSuccess, onSuccess, onFailure]);
+
+ return (
+
+ {/* Challenge Info */}
+ {challenge && (
+
+
{challenge.title}
+
{challenge.description}
+
+ {/* Success Criteria */}
+
+
Success Criteria:
+
+ {challenge.successCriteria?.reachGoal && (
+ -
+
+ {checkGoal(agentPos.x, agentPos.y) ? '✓' : '○'}
+
+ Reach the goal
+
+ )}
+ {challenge.successCriteria?.collectAllItems && challenge.items && (
+ -
+
+ {collectedItems.length === challenge.items.length ? '✓' : '○'}
+
+ Collect all items ({collectedItems.length}/{challenge.items.length})
+
+ )}
+ {challenge.successCriteria?.useBlocks && (
+ -
+ ○
+ Use: {challenge.successCriteria.useBlocks.join(', ')}
+
+ )}
+ {challenge.successCriteria?.noCollisions && (
+ -
+ log.type === 'collision') ? 'text-green-600' : 'text-red-600'}>
+ {!executionLog.some(log => log.type === 'collision') ? '✓' : '✗'}
+
+ No collisions
+
+ )}
+
+
+
+ {/* Hint */}
+
+
+ 💡 Hint: {challenge.hint}
+
+
+
+ )}
+
+ {/* Sandbox Viewport */}
+
+
+ {/* Agent */}
+
+ 🤖
+
+
+ {/* Obstacles */}
+ {challenge?.obstacles?.map((obs, idx) => (
+
+ ))}
+
+ {/* Collectible Items */}
+ {challenge?.items?.map((item, idx) => (
+ !collectedItems.includes(item.id) && (
+
+ {item.icon || '⭐'}
+
+ )
+ ))}
+
+ {/* Goal */}
+ {challenge?.goal && (
+
+ 🎯
+
+ )}
+
+ {/* Success/Failure Overlay */}
+ {simulationState === 'success' && (
+
+ )}
+ {simulationState === 'failure' && (
+
+ )}
+
+
+
+ {/* Control Buttons */}
+
+
+
+
+
+ {/* Execution Log */}
+
+
+ {executionLog.map((log, idx) => (
+
+
{log.message}
+ {log.details &&
{log.details}
}
+
+ ))}
+
+
+
+ );
+}
+
+export default SimulationSandbox;
diff --git a/frontend/src/data/challenges.js b/frontend/src/data/challenges.js
new file mode 100644
index 0000000..523c335
--- /dev/null
+++ b/frontend/src/data/challenges.js
@@ -0,0 +1,197 @@
+/**
+ * Predefined Challenges for Agent Builder
+ * Code.org / Minecraft Education style challenges
+ */
+
+export const challenges = [
+ {
+ id: 'challenge-1',
+ title: '🎯 First Steps',
+ difficulty: 'Beginner',
+ description: 'Help your agent reach the goal. Start simple!',
+ hint: 'Use SENSE to see, PLAN to think, and ACT to move',
+
+ // Initial setup
+ startPosition: { x: 10, y: 50 },
+ goal: { x: 85, y: 50 },
+ obstacles: [],
+ items: [],
+
+ // Success criteria
+ successCriteria: {
+ reachGoal: true,
+ useBlocks: ['SENSE', 'PLAN', 'ACT'],
+ collectAllItems: false,
+ noCollisions: false
+ },
+
+ // Recommended blocks
+ recommendedBlocks: ['SENSE', 'PLAN', 'ACT'],
+ maxBlocks: 10
+ },
+
+ {
+ id: 'challenge-2',
+ title: '🚧 Navigate Obstacles',
+ difficulty: 'Beginner',
+ description: 'Your agent must avoid obstacles to reach the goal',
+ hint: 'SENSE will help detect obstacles before moving',
+
+ startPosition: { x: 10, y: 50 },
+ goal: { x: 85, y: 50 },
+ obstacles: [
+ { x: 35, y: 40, width: 8, height: 25 },
+ { x: 60, y: 35, width: 8, height: 30 }
+ ],
+ items: [],
+
+ successCriteria: {
+ reachGoal: true,
+ useBlocks: ['SENSE', 'ACT'],
+ collectAllItems: false,
+ noCollisions: true
+ },
+
+ recommendedBlocks: ['SENSE', 'PLAN', 'ACT'],
+ maxBlocks: 15
+ },
+
+ {
+ id: 'challenge-3',
+ title: '⭐ Collect & Reach',
+ difficulty: 'Intermediate',
+ description: 'Collect all stars before reaching the goal',
+ hint: 'Plan your route to collect items efficiently',
+
+ startPosition: { x: 10, y: 50 },
+ goal: { x: 85, y: 50 },
+ obstacles: [
+ { x: 40, y: 35, width: 8, height: 35 }
+ ],
+ items: [
+ { id: 'star-1', type: 'Star', icon: '⭐', x: 30, y: 30 },
+ { id: 'star-2', type: 'Star', icon: '⭐', x: 55, y: 70 },
+ { id: 'star-3', type: 'Star', icon: '⭐', x: 70, y: 50 }
+ ],
+
+ successCriteria: {
+ reachGoal: true,
+ useBlocks: ['SENSE', 'PLAN', 'ACT'],
+ collectAllItems: true,
+ noCollisions: true
+ },
+
+ recommendedBlocks: ['SENSE', 'PLAN', 'ACT', 'REFLECT'],
+ maxBlocks: 20
+ },
+
+ {
+ id: 'challenge-4',
+ title: '💎 Diamond Hunt',
+ difficulty: 'Intermediate',
+ description: 'Collect all diamonds in the maze',
+ hint: 'Use REFLECT to remember which paths work best',
+
+ startPosition: { x: 10, y: 20 },
+ goal: { x: 85, y: 80 },
+ obstacles: [
+ { x: 25, y: 10, width: 6, height: 40 },
+ { x: 45, y: 30, width: 6, height: 50 },
+ { x: 65, y: 10, width: 6, height: 45 }
+ ],
+ items: [
+ { id: 'diamond-1', type: 'Diamond', icon: '💎', x: 35, y: 25 },
+ { id: 'diamond-2', type: 'Diamond', icon: '💎', x: 55, y: 60 },
+ { id: 'diamond-3', type: 'Diamond', icon: '💎', x: 75, y: 30 }
+ ],
+
+ successCriteria: {
+ reachGoal: true,
+ useBlocks: ['SENSE', 'PLAN', 'ACT', 'REFLECT'],
+ collectAllItems: true,
+ noCollisions: true
+ },
+
+ recommendedBlocks: ['SENSE', 'PLAN', 'ACT', 'REFLECT'],
+ maxBlocks: 25
+ },
+
+ {
+ id: 'challenge-5',
+ title: '🏆 Master Challenge',
+ difficulty: 'Advanced',
+ description: 'Navigate complex obstacles, collect all items, reach the goal perfectly',
+ hint: 'Use all blocks strategically. Plan carefully before acting!',
+
+ startPosition: { x: 10, y: 10 },
+ goal: { x: 85, y: 85 },
+ obstacles: [
+ { x: 20, y: 25, width: 8, height: 30 },
+ { x: 35, y: 10, width: 8, height: 25 },
+ { x: 35, y: 55, width: 8, height: 30 },
+ { x: 50, y: 30, width: 8, height: 35 },
+ { x: 65, y: 15, width: 8, height: 25 },
+ { x: 65, y: 60, width: 8, height: 25 }
+ ],
+ items: [
+ { id: 'gem-1', type: 'Gem', icon: '💎', x: 28, y: 15 },
+ { id: 'gem-2', type: 'Gem', icon: '💎', x: 43, y: 45 },
+ { id: 'gem-3', type: 'Gem', icon: '💎', x: 58, y: 25 },
+ { id: 'gem-4', type: 'Gem', icon: '💎', x: 73, y: 70 },
+ { id: 'star-1', type: 'Star', icon: '⭐', x: 28, y: 70 },
+ { id: 'star-2', type: 'Star', icon: '⭐', x: 73, y: 45 }
+ ],
+
+ successCriteria: {
+ reachGoal: true,
+ useBlocks: ['SENSE', 'PLAN', 'ACT', 'REFLECT'],
+ collectAllItems: true,
+ noCollisions: true
+ },
+
+ recommendedBlocks: ['SENSE', 'PLAN', 'ACT', 'REFLECT'],
+ maxBlocks: 30
+ },
+
+ {
+ id: 'challenge-6',
+ title: '🎮 Sandbox Mode',
+ difficulty: 'Custom',
+ description: 'Free play! Experiment with your agent in an open environment',
+ hint: 'Try different block combinations and see what happens',
+
+ startPosition: { x: 20, y: 50 },
+ goal: { x: 80, y: 50 },
+ obstacles: [
+ { x: 45, y: 35, width: 10, height: 30 }
+ ],
+ items: [
+ { id: 'item-1', type: 'Coin', icon: '🪙', x: 35, y: 50 },
+ { id: 'item-2', type: 'Coin', icon: '🪙', x: 65, y: 50 }
+ ],
+
+ successCriteria: {
+ reachGoal: false, // Optional in sandbox
+ useBlocks: [],
+ collectAllItems: false,
+ noCollisions: false
+ },
+
+ recommendedBlocks: ['SENSE', 'PLAN', 'ACT', 'REFLECT'],
+ maxBlocks: 50
+ }
+];
+
+/**
+ * Get challenge by ID
+ */
+export function getChallengeById(id) {
+ return challenges.find(c => c.id === id);
+}
+
+/**
+ * Get challenges by difficulty
+ */
+export function getChallengesByDifficulty(difficulty) {
+ return challenges.filter(c => c.difficulty === difficulty);
+}
From 0c2cbd057b06d7a581c77060a4d1dfcebb7ad461 Mon Sep 17 00:00:00 2001
From: k0sa <74941972+kosausrk@users.noreply.github.com>
Date: Sat, 8 Nov 2025 04:11:14 -0500
Subject: [PATCH 2/2] UI / UX tweaks
---
.../src/components/ComponentDetailSidebar.jsx | 14 +-
frontend/src/components/NeetCodeRoadmap.jsx | 14 +-
frontend/src/minecraft-theme.css | 439 ++++++++++++++++++
3 files changed, 457 insertions(+), 10 deletions(-)
create mode 100644 frontend/src/minecraft-theme.css
diff --git a/frontend/src/components/ComponentDetailSidebar.jsx b/frontend/src/components/ComponentDetailSidebar.jsx
index 5b94ce4..b7d0cdc 100644
--- a/frontend/src/components/ComponentDetailSidebar.jsx
+++ b/frontend/src/components/ComponentDetailSidebar.jsx
@@ -208,12 +208,20 @@ export default function ComponentDetailSidebar({ component, lessons, progress, o
{/* Lesson Name */}
-
+
+
|
{/* Difficulty */}
diff --git a/frontend/src/components/NeetCodeRoadmap.jsx b/frontend/src/components/NeetCodeRoadmap.jsx
index b5bce46..4c9ee44 100644
--- a/frontend/src/components/NeetCodeRoadmap.jsx
+++ b/frontend/src/components/NeetCodeRoadmap.jsx
@@ -131,16 +131,16 @@ export default function NeetCodeRoadmap({ components, lessons, progress, onCompo
type: 'bezier',
animated: isCompleted && canProceed,
style: {
- stroke: isCompleted ? '#10b981' : canProceed ? '#ffffff' : '#6b7280',
- strokeWidth: isCompleted ? 3 : 2.5,
- strokeDasharray: isCompleted ? '0' : '8,4',
- opacity: canProceed ? (isCompleted ? 1 : 0.7) : 0.25,
+ stroke: isCompleted ? '#10b981' : canProceed ? '#ffffff' : '#9ca3af',
+ strokeWidth: isCompleted ? 5 : 4,
+ strokeDasharray: isCompleted ? '0' : '0',
+ opacity: canProceed ? 1 : 0.4,
},
markerEnd: {
type: 'arrowclosed',
- color: isCompleted ? '#10b981' : canProceed ? '#ffffff' : '#6b7280',
- width: isCompleted ? 20 : 15,
- height: isCompleted ? 20 : 15,
+ color: isCompleted ? '#10b981' : canProceed ? '#ffffff' : '#9ca3af',
+ width: isCompleted ? 24 : 20,
+ height: isCompleted ? 24 : 20,
},
});
}
diff --git a/frontend/src/minecraft-theme.css b/frontend/src/minecraft-theme.css
new file mode 100644
index 0000000..1d4132c
--- /dev/null
+++ b/frontend/src/minecraft-theme.css
@@ -0,0 +1,439 @@
+/**
+ * Minecraft/Code.org Style Theme
+ * Blocky, colorful interface with jigsaw-puzzle block shapes
+ */
+
+/* ============================================
+ 1. MAIN COLOR PALETTE & LAYOUT
+ ============================================ */
+
+/* Header Bar - Dark teal/gray like Minecraft */
+.header-bar,
+header,
+nav {
+ background: linear-gradient(180deg, #4a7c8c 0%, #3d6b7a 100%) !important;
+ border-bottom: 3px solid #2a5563 !important;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3) !important;
+}
+
+/* Override any white backgrounds in header */
+header *,
+nav * {
+ color: white !important;
+}
+
+/* Workspace Area - Light gray solid background */
+.workspace-area,
+.canvas,
+[class*="canvas"] {
+ background: #e8e8e8 !important;
+ background-image: none !important;
+}
+
+/* Block Palette (Left Sidebar) - Medium gray */
+.block-palette,
+aside,
+[class*="sidebar"] {
+ background: #4d4d4d !important;
+ border-right: 3px solid #3a3a3a !important;
+ box-shadow: 2px 0 8px rgba(0, 0, 0, 0.2) !important;
+}
+
+/* Block Palette Text */
+.block-palette *,
+aside *,
+[class*="sidebar"] * {
+ color: #f0f0f0 !important;
+}
+
+/* Instructions Panel - Dark gray bar at top */
+.instructions-panel,
+[class*="challenge"],
+[class*="hint"] {
+ background: #3e3e3e !important;
+ border: 2px solid #2a2a2a !important;
+ border-radius: 4px !important;
+ padding: 12px 16px !important;
+ color: white !important;
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3) !important;
+}
+
+.instructions-panel h3,
+.instructions-panel p {
+ color: white !important;
+ margin: 4px 0 !important;
+}
+
+/* ============================================
+ 2. BUTTONS
+ ============================================ */
+
+/* Main "Run" Button - Large bright yellow */
+.run-button,
+button[class*="run"],
+button[class*="Run"],
+button:has(svg):contains("Run") {
+ background: linear-gradient(180deg, #ffd54f 0%, #ffc107 100%) !important;
+ color: #333 !important;
+ border: 3px solid #ffb300 !important;
+ border-radius: 8px !important;
+ padding: 14px 28px !important;
+ font-size: 18px !important;
+ font-weight: bold !important;
+ text-transform: uppercase !important;
+ box-shadow: 0 4px 0 #e69500, 0 6px 12px rgba(0, 0, 0, 0.3) !important;
+ cursor: pointer !important;
+ transition: all 0.1s ease !important;
+}
+
+.run-button:hover,
+button[class*="run"]:hover {
+ background: linear-gradient(180deg, #ffe082 0%, #ffd54f 100%) !important;
+ transform: translateY(2px) !important;
+ box-shadow: 0 2px 0 #e69500, 0 4px 8px rgba(0, 0, 0, 0.3) !important;
+}
+
+.run-button:active {
+ transform: translateY(4px) !important;
+ box-shadow: 0 0 0 #e69500, 0 2px 4px rgba(0, 0, 0, 0.3) !important;
+}
+
+/* Secondary Buttons */
+.secondary-button,
+button[class*="export"],
+button[class*="Export"],
+button[class*="reset"],
+button[class*="Reset"] {
+ background: linear-gradient(180deg, #7cb342 0%, #689f38 100%) !important;
+ color: white !important;
+ border: 2px solid #558b2f !important;
+ border-radius: 6px !important;
+ padding: 10px 20px !important;
+ font-weight: 600 !important;
+ box-shadow: 0 3px 0 #4e7a2f, 0 4px 8px rgba(0, 0, 0, 0.2) !important;
+}
+
+.secondary-button:hover {
+ background: linear-gradient(180deg, #8bc34a 0%, #7cb342 100%) !important;
+ transform: translateY(1px) !important;
+ box-shadow: 0 2px 0 #4e7a2f, 0 3px 6px rgba(0, 0, 0, 0.2) !important;
+}
+
+/* Challenge Button */
+button[class*="challenge"],
+button[class*="Challenge"] {
+ background: linear-gradient(180deg, #9c27b0 0%, #7b1fa2 100%) !important;
+ color: white !important;
+ border: 2px solid #6a1b9a !important;
+ border-radius: 6px !important;
+ box-shadow: 0 3px 0 #4a148c, 0 4px 8px rgba(0, 0, 0, 0.2) !important;
+}
+
+/* ============================================
+ 3. BLOCK DESIGN (JIGSAW/PUZZLE PIECES)
+ ============================================ */
+
+/* Base Block Style */
+.agent-block,
+.block,
+[class*="block"][class*="category"],
+div[draggable="true"] {
+ position: relative !important;
+ padding: 12px 20px !important;
+ margin: 8px 0 !important;
+ border-radius: 4px !important;
+ border: none !important;
+ box-shadow: 0 3px 0 rgba(0, 0, 0, 0.3), 0 4px 8px rgba(0, 0, 0, 0.2) !important;
+ cursor: grab !important;
+ font-weight: bold !important;
+ color: white !important;
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3) !important;
+ transition: all 0.1s ease !important;
+ min-height: 48px !important;
+ display: flex !important;
+ align-items: center !important;
+ justify-content: center !important;
+}
+
+.agent-block:hover,
+.block:hover {
+ transform: translateY(-2px) !important;
+ box-shadow: 0 5px 0 rgba(0, 0, 0, 0.3), 0 6px 12px rgba(0, 0, 0, 0.3) !important;
+}
+
+.agent-block:active,
+.block:active {
+ cursor: grabbing !important;
+ transform: scale(1.05) !important;
+}
+
+/* Top Notch (Cutout) - Using pseudo-element */
+.agent-block::before,
+.block::before,
+[class*="block"][class*="category"]::before {
+ content: '' !important;
+ position: absolute !important;
+ top: -6px !important;
+ left: 50% !important;
+ transform: translateX(-50%) !important;
+ width: 20px !important;
+ height: 6px !important;
+ background: #e8e8e8 !important; /* Match workspace background */
+ border-radius: 0 0 4px 4px !important;
+ z-index: 1 !important;
+}
+
+/* Bottom Bump (Outdent) - Using pseudo-element */
+.agent-block::after,
+.block::after,
+[class*="block"][class*="category"]::after {
+ content: '' !important;
+ position: absolute !important;
+ bottom: -6px !important;
+ left: 50% !important;
+ transform: translateX(-50%) !important;
+ width: 20px !important;
+ height: 6px !important;
+ background: inherit !important;
+ border-radius: 4px 4px 0 0 !important;
+ z-index: 1 !important;
+ box-shadow: 0 2px 0 rgba(0, 0, 0, 0.2) !important;
+}
+
+/* ============================================
+ 4. BLOCK COLORS (Solid, Vibrant)
+ ============================================ */
+
+/* SENSE Block - Bright Blue */
+.block-sense,
+[class*="SENSE"],
+div:contains("SENSE") {
+ background: linear-gradient(180deg, #42a5f5 0%, #1e88e5 100%) !important;
+ border-bottom: 3px solid #1565c0 !important;
+}
+
+.block-sense::after {
+ background: linear-gradient(180deg, #42a5f5 0%, #1e88e5 100%) !important;
+}
+
+/* PLAN Block - Bright Green */
+.block-plan,
+[class*="PLAN"],
+div:contains("PLAN") {
+ background: linear-gradient(180deg, #66bb6a 0%, #43a047 100%) !important;
+ border-bottom: 3px solid #2e7d32 !important;
+}
+
+.block-plan::after {
+ background: linear-gradient(180deg, #66bb6a 0%, #43a047 100%) !important;
+}
+
+/* ACT Block - Bright Orange */
+.block-act,
+[class*="ACT"],
+div:contains("ACT") {
+ background: linear-gradient(180deg, #ffa726 0%, #fb8c00 100%) !important;
+ border-bottom: 3px solid #e65100 !important;
+}
+
+.block-act::after {
+ background: linear-gradient(180deg, #ffa726 0%, #fb8c00 100%) !important;
+}
+
+/* REFLECT Block - Purple */
+.block-reflect,
+[class*="REFLECT"],
+div:contains("REFLECT") {
+ background: linear-gradient(180deg, #ab47bc 0%, #8e24aa 100%) !important;
+ border-bottom: 3px solid #6a1b9a !important;
+}
+
+.block-reflect::after {
+ background: linear-gradient(180deg, #ab47bc 0%, #8e24aa 100%) !important;
+}
+
+/* Start/When Run Block - Bright Yellow */
+.start-block,
+.block-start,
+[class*="when"],
+[class*="start"] {
+ background: linear-gradient(180deg, #ffeb3b 0%, #fdd835 100%) !important;
+ color: #333 !important;
+ border-bottom: 3px solid #f9a825 !important;
+ text-shadow: none !important;
+}
+
+.start-block::after {
+ background: linear-gradient(180deg, #ffeb3b 0%, #fdd835 100%) !important;
+}
+
+/* ============================================
+ 5. SIDEBAR BLOCKS (In Palette)
+ ============================================ */
+
+/* Blocks in the left sidebar should be slightly smaller */
+.block-palette .agent-block,
+.block-palette .block,
+aside .agent-block,
+aside .block {
+ padding: 10px 16px !important;
+ min-height: 42px !important;
+ font-size: 14px !important;
+ margin: 6px 8px !important;
+}
+
+/* ============================================
+ 6. ADDITIONAL UI ELEMENTS
+ ============================================ */
+
+/* Sandbox/Viewport */
+[class*="sandbox"],
+[class*="viewport"],
+[class*="simulation"] {
+ background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%) !important;
+ border: 4px solid #64b5f6 !important;
+ border-radius: 8px !important;
+ box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.1) !important;
+}
+
+/* Debug Trail / Console */
+[class*="debug"],
+[class*="trail"],
+[class*="console"],
+[class*="log"] {
+ background: #1e1e1e !important;
+ border: 2px solid #333 !important;
+ border-radius: 4px !important;
+ font-family: 'Courier New', monospace !important;
+ padding: 12px !important;
+}
+
+/* Success Messages */
+[class*="success"],
+.bg-green-50,
+.bg-emerald-50 {
+ background: #4caf50 !important;
+ color: white !important;
+ border-left: 4px solid #2e7d32 !important;
+ padding: 8px 12px !important;
+ border-radius: 4px !important;
+}
+
+/* Error Messages */
+[class*="error"],
+[class*="failure"],
+.bg-red-50 {
+ background: #f44336 !important;
+ color: white !important;
+ border-left: 4px solid #c62828 !important;
+ padding: 8px 12px !important;
+ border-radius: 4px !important;
+}
+
+/* Info/Warning Messages */
+[class*="info"],
+.bg-blue-50 {
+ background: #2196f3 !important;
+ color: white !important;
+ border-left: 4px solid #1565c0 !important;
+ padding: 8px 12px !important;
+ border-radius: 4px !important;
+}
+
+[class*="warning"],
+.bg-orange-50,
+.bg-yellow-50 {
+ background: #ff9800 !important;
+ color: white !important;
+ border-left: 4px solid #e65100 !important;
+ padding: 8px 12px !important;
+ border-radius: 4px !important;
+}
+
+/* ============================================
+ 7. TYPOGRAPHY
+ ============================================ */
+
+/* Make text more bold and readable like Minecraft */
+.agent-block,
+.block,
+button,
+.instructions-panel {
+ font-family: 'Arial', 'Helvetica', sans-serif !important;
+ letter-spacing: 0.3px !important;
+}
+
+/* Block text should be centered and bold */
+.agent-block *,
+.block * {
+ text-align: center !important;
+ font-weight: bold !important;
+}
+
+/* ============================================
+ 8. ANIMATIONS & INTERACTIONS
+ ============================================ */
+
+/* Dragging state */
+.agent-block.dragging,
+.block.dragging {
+ opacity: 0.7 !important;
+ transform: rotate(5deg) scale(1.1) !important;
+ z-index: 1000 !important;
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.4) !important;
+}
+
+/* Connection indicators */
+.connection-point {
+ background: #fff !important;
+ border: 3px solid #333 !important;
+ width: 16px !important;
+ height: 16px !important;
+}
+
+.connection-point:hover {
+ background: #ffd54f !important;
+ transform: scale(1.3) !important;
+}
+
+/* ============================================
+ 9. RESPONSIVE ADJUSTMENTS
+ ============================================ */
+
+@media (max-width: 768px) {
+ .agent-block,
+ .block {
+ padding: 10px 16px !important;
+ font-size: 13px !important;
+ }
+
+ .run-button {
+ padding: 12px 24px !important;
+ font-size: 16px !important;
+ }
+}
+
+/* ============================================
+ 10. UTILITY OVERRIDES
+ ============================================ */
+
+/* Remove any conflicting Tailwind classes */
+.bg-white {
+ background: transparent !important;
+}
+
+.bg-gray-50,
+.bg-gray-100 {
+ background: #e8e8e8 !important;
+}
+
+.rounded-lg,
+.rounded-xl {
+ border-radius: 8px !important;
+}
+
+/* Override shadow utilities */
+.shadow-lg,
+.shadow-xl {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3) !important;
+}