From 5e1807f30afe7dcfc66a6962d165f5012328519b Mon Sep 17 00:00:00 2001 From: k0sa <74941972+kosausrk@users.noreply.github.com> Date: Sat, 8 Nov 2025 03:53:13 -0500 Subject: [PATCH 1/2] Created minecraft code.org style --- frontend/src/EducationalScratchApp.jsx | 425 ++++++++--------- frontend/src/components/ChallengeSelector.jsx | 125 +++++ frontend/src/components/LessonCard.jsx | 13 +- frontend/src/components/LessonContent.jsx | 192 ++++++++ frontend/src/components/SimulationSandbox.jsx | 434 ++++++++++++++++++ frontend/src/data/challenges.js | 197 ++++++++ 6 files changed, 1132 insertions(+), 254 deletions(-) create mode 100644 frontend/src/components/ChallengeSelector.jsx create mode 100644 frontend/src/components/LessonContent.jsx create mode 100644 frontend/src/components/SimulationSandbox.jsx create mode 100644 frontend/src/data/challenges.js diff --git a/frontend/src/EducationalScratchApp.jsx b/frontend/src/EducationalScratchApp.jsx index ea3123d..de9b088 100644 --- a/frontend/src/EducationalScratchApp.jsx +++ b/frontend/src/EducationalScratchApp.jsx @@ -1,10 +1,14 @@ -import { useState, useCallback, useRef } from 'react'; +import { useState, useCallback, useRef, useEffect } from 'react'; import Sidebar from './components/Sidebar'; import Canvas from './components/Canvas'; import Popup from './components/Popup'; import HintSystem from './components/HintSystem'; import InfoCard from './components/InfoCard'; import ChallengePanel from './components/ChallengePanel'; +import LessonContent from './components/LessonContent'; +import SimulationSandbox from './components/SimulationSandbox'; +import ChallengeSelector from './components/ChallengeSelector'; +import { challenges } from './data/challenges'; /** * EducationalScratchApp - Full Educational Experience @@ -20,18 +24,6 @@ function EducationalScratchApp() { // State for editing popup const [editingNode, setEditingNode] = useState(null); - // State for agent position in sandbox - const [agentPos, setAgentPos] = useState({ x: 20, y: 50 }); - - // State for obstacles in sandbox - const [obstacles, setObstacles] = useState([ - { x: 40, y: 50, width: 10, height: 20 }, - { x: 65, y: 30, width: 10, height: 30 } - ]); - - // State for goal position - const [goalPos] = useState({ x: 85, y: 50 }); - // State for debug trail with enhanced educational messages const [debugTrail, setDebugTrail] = useState([ { @@ -46,11 +38,24 @@ function EducationalScratchApp() { const [lastAction, setLastAction] = useState(null); // State for current challenge - const [currentChallenge, setCurrentChallenge] = useState(null); + const [currentChallenge, setCurrentChallenge] = useState(challenges[0]); // Start with first challenge // State for current lesson focus const [lessonFocus, setLessonFocus] = useState('perception'); + // State for challenge selector + const [showChallengeSelector, setShowChallengeSelector] = useState(false); + + // State for right panel view toggle ('debug' or 'lesson') + const [rightPanelView, setRightPanelView] = useState('debug'); + + // State for right panel collapsed/expanded + const [isPanelCollapsed, setIsPanelCollapsed] = useState(false); + + // State for right panel width (resizable) + const [panelWidth, setPanelWidth] = useState(384); // 384px = w-96 + const [isResizing, setIsResizing] = useState(false); + // Counter for generating unique IDs const nextIdRef = useRef(1); const getNextId = () => nextIdRef.current++; @@ -153,154 +158,6 @@ function EducationalScratchApp() { ); }, []); - /** - * Check if agent collides with obstacles - */ - const checkCollision = useCallback((x, y) => { - return obstacles.some(obs => - x >= obs.x && x <= obs.x + obs.width && - y >= obs.y && y <= obs.y + obs.height - ); - }, [obstacles]); - - /** - * Check if agent reached goal - */ - const checkGoalReached = useCallback((x, y) => { - const distance = Math.sqrt( - Math.pow(x - goalPos.x, 2) + Math.pow(y - goalPos.y, 2) - ); - return distance < 5; - }, [goalPos]); - - /** - * Run agent simulation with educational feedback - */ - const runAgent = useCallback(() => { - if (nodes.length === 0) { - addDebugMessage('error', 'No blocks to execute. Start by dragging blocks from the left!', 'error'); - setLastAction({ type: 'error', message: 'no_blocks' }); - return; - } - - addDebugMessage('info', '🚀 Starting agent execution...', 'start'); - setAgentPos({ x: 20, y: 50 }); // Reset position - - let currentX = 20; - let currentY = 50; - let hasReflected = false; - - // Execute blocks sequentially with educational commentary - nodes.forEach((node, index) => { - setTimeout(() => { - const action = node.label; - - switch(action) { - case 'SENSE': - const obstacleAhead = checkCollision(currentX + 15, currentY); - addDebugMessage( - 'sense', - `👁️ SENSING: ${obstacleAhead ? 'Obstacle detected ahead!' : 'Path looks clear'}`, - 'sense' - ); - addDebugMessage( - 'sense', - `📊 Input: Environment data → Agent can see ${obstacleAhead ? 'blocked' : 'open'} path`, - 'sense' - ); - break; - - case 'PLAN': - const hasSense = nodes.some(n => n.label === 'SENSE'); - if (hasSense) { - addDebugMessage( - 'plan', - '🧠 PLANNING: Analyzing sensor data... Calculating best path around obstacles', - 'plan' - ); - addDebugMessage( - 'plan', - '💡 Decision Logic: Move forward cautiously, adjust if obstacle detected', - 'plan' - ); - } else { - addDebugMessage( - 'warning', - '⚠️ PLANNING: No sensor data available. Planning without information!', - 'plan' - ); - } - break; - - case 'ACT': - const willCollide = checkCollision(currentX + 20, currentY); - - if (willCollide) { - addDebugMessage('error', '❌ ACTION: Collision! Agent hit an obstacle', 'act'); - addDebugMessage('error', '💥 No SENSE block = blind movement', 'act'); - setLastAction({ type: 'collision' }); - } else { - currentX += 20; - currentY += Math.random() * 10 - 5; // Slight variation - setAgentPos({ x: currentX, y: currentY }); - - addDebugMessage('act', '⚡ ACTION: Moving forward...', 'act'); - addDebugMessage('act', `📍 New position: (${currentX.toFixed(0)}, ${currentY.toFixed(0)})`, 'act'); - - if (checkGoalReached(currentX, currentY)) { - addDebugMessage('success', '🎯 Goal reached! Mission accomplished!', 'success'); - setLastAction({ type: 'success' }); - } - } - break; - - case 'REFLECT': - hasReflected = true; - const success = checkGoalReached(currentX, currentY); - if (success) { - addDebugMessage( - 'reflect', - '💭 REFLECTING: Success! Storing this strategy in memory for future use', - 'reflect' - ); - addDebugMessage( - 'reflect', - '🎓 Learning: "Sense → Plan → Act" pattern works well', - 'reflect' - ); - } else { - addDebugMessage( - 'reflect', - '💭 REFLECTING: Analyzing what went wrong... Learning from mistakes', - 'reflect' - ); - addDebugMessage( - 'reflect', - '📝 Memory Update: Need better obstacle detection next time', - 'reflect' - ); - } - break; - - default: - addDebugMessage('info', `Executing: ${action}`, 'execute'); - } - - // Final summary - if (index === nodes.length - 1) { - setTimeout(() => { - if (!hasReflected) { - addDebugMessage( - 'info', - '💡 Tip: Add a REFLECT block to help your agent learn and improve!', - 'tip' - ); - } - }, 500); - } - }, index * 1500); // Slower for educational clarity - }); - }, [nodes, addDebugMessage, checkCollision, checkGoalReached]); /** * Export agent logic as JSON @@ -340,6 +197,45 @@ function EducationalScratchApp() { setAgentPos({ x: 20, y: 50 }); }, [addDebugMessage]); + /** + * Handle panel resize start + */ + const handleResizeStart = useCallback((e) => { + e.preventDefault(); + setIsResizing(true); + }, []); + + /** + * Handle panel resize move + */ + const handleResizeMove = useCallback((e) => { + if (isResizing) { + const newWidth = window.innerWidth - e.clientX; + if (newWidth >= 300 && newWidth <= 600) { + setPanelWidth(newWidth); + } + } + }, [isResizing]); + + /** + * Handle panel resize end + */ + const handleResizeEnd = useCallback(() => { + setIsResizing(false); + }, []); + + // Set up resize event listeners + useEffect(() => { + if (isResizing) { + window.addEventListener('mousemove', handleResizeMove); + window.addEventListener('mouseup', handleResizeEnd); + return () => { + window.removeEventListener('mousemove', handleResizeMove); + window.removeEventListener('mouseup', handleResizeEnd); + }; + } + }, [isResizing, handleResizeMove, handleResizeEnd]); + return (
{/* Left Sidebar - Scratch-style */} @@ -362,15 +258,14 @@ function EducationalScratchApp() { {/* Control Buttons */}
- {/* Obstacles */} - {obstacles.map((obs, idx) => ( -
- ))} - - {/* Goal marker */} -
+ { + addDebugMessage('success', '🎉 Challenge completed!', 'success'); + setLastAction({ type: 'success' }); }} - title="Goal" - > - 🎯 -
+ onFailure={() => { + addDebugMessage('error', '❌ Challenge failed. Try again!', 'error'); + setLastAction({ type: 'failure' }); + }} + />
+ + {/* Toggle Tabs */} +
+ +
- {/* Enhanced Debug Trail */} -
-

Debug Trail

-

Watch how your agent thinks and acts

-
- {debugTrail.map((entry, idx) => ( -
-
- {entry.type} - - {new Date(entry.timestamp).toLocaleTimeString()} - + {/* Conditional Content Based on Toggle */} + {rightPanelView === 'debug' ? ( + /* Debug Trail View */ +
+

Debug Trail

+

Watch how your agent thinks and acts

+
+ {debugTrail.map((entry, idx) => ( +
+
+ {entry.type} + + {new Date(entry.timestamp).toLocaleTimeString()} + +
+
{entry.message}
-
{entry.message}
-
- ))} + ))} +
-
+ ) : ( + /* Lesson Content View */ + + )}
+ ) : ( + /* Collapsed Panel - Expand Button */ + + )} {/* Edit Popup Modal */} {editingNode && ( @@ -494,6 +415,18 @@ function EducationalScratchApp() { onChallengeSelect={handleChallengeSelect} currentChallenge={currentChallenge} /> + + {/* Challenge Selector Modal */} + {showChallengeSelector && ( + { + setCurrentChallenge(challenge); + addDebugMessage('info', `🎮 Challenge selected: ${challenge.title}`, 'challenge'); + }} + onClose={() => setShowChallengeSelector(false)} + /> + )}
); } 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 ( +
+
+ {/* Header */} +
+
+
+

Select a Challenge

+

Choose your mission and build your agent

+
+ +
+
+ + {/* Challenges Grid */} +
+
+ {challenges.map((challenge) => ( +
{ + onSelectChallenge(challenge); + onClose(); + }} + > + {/* Challenge Header */} +
+

{challenge.title}

+ + {challenge.difficulty} + +
+ + {/* Description */} +

{challenge.description}

+ + {/* Stats */} +
+
+ + + + {Object.values(challenge.successCriteria).filter(Boolean).length} criteria +
+ {challenge.items && challenge.items.length > 0 && ( +
+ + {challenge.items.length} items +
+ )} + {challenge.obstacles && challenge.obstacles.length > 0 && ( +
+ 🚧 + {challenge.obstacles.length} obstacles +
+ )} +
+ + {/* Recommended Blocks */} +
+ {challenge.recommendedBlocks.map((block, idx) => ( + + {block} + + ))} +
+ + {/* Current Challenge Badge */} + {currentChallenge?.id === challenge.id && ( +
+ ✓ Currently Active +
+ )} +
+ ))} +
+
+ + {/* Footer */} +
+

+ 💡 Tip: Start with easier challenges to learn the basics, then progress to advanced ones +

+
+
+
+ ); +} + +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 && ( +
+

{guidance.message}

+
+ )} + + {/* 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' && ( +
+
+

🎉 Success!

+
+
+ )} + {simulationState === 'failure' && ( +
+
+

❌ Try Again

+
+
+ )} +
+
+ + {/* 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 */} -
+
-
{lesson.title}
+
{lesson.title}
{lesson.focus}
-
+ + + + {/* 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; +}