diff --git a/app/components/challenge/ChallengeCard.tsx b/app/components/challenge/ChallengeCard.tsx
new file mode 100644
index 0000000000..36df83241b
--- /dev/null
+++ b/app/components/challenge/ChallengeCard.tsx
@@ -0,0 +1,49 @@
+import React from 'react';
+
+export type ChallengeCardProps = {
+ id: string;
+ title: string;
+ image: string;
+ difficulty: 'Easy' | 'Medium' | 'Hard';
+ averageAccuracy?: number; // percentage, optional for backward compatibility
+ onClick?: () => void;
+};
+
+export function ChallengeCard({ id, title, image, difficulty, averageAccuracy, onClick }: ChallengeCardProps) {
+ const difficultyColor =
+ difficulty === 'Easy' ? 'text-green-500' : difficulty === 'Medium' ? 'text-yellow-500' : 'text-red-500';
+ return (
+
+
+

+
+
+
+
+ {title}
+
+
+ {typeof averageAccuracy === 'number' && (
+
+ {averageAccuracy}%
+
+
+ )}
+ {difficulty}
+
+
+
+
+ );
+}
diff --git a/app/components/chat/ChallengeChat.client.tsx b/app/components/chat/ChallengeChat.client.tsx
new file mode 100644
index 0000000000..54311ec0af
--- /dev/null
+++ b/app/components/chat/ChallengeChat.client.tsx
@@ -0,0 +1,246 @@
+import { useStore } from '@nanostores/react';
+import type { Message } from 'ai';
+import { useChat } from 'ai/react';
+import { useAnimate } from 'framer-motion';
+import { memo, useEffect, useRef, useState } from 'react';
+import { cssTransition, toast, ToastContainer } from 'react-toastify';
+import { useMessageParser, usePromptEnhancer, useShortcuts, useSnapScroll } from '~/lib/hooks';
+import { useChatHistory } from '~/lib/persistence';
+import { chatStore } from '~/lib/stores/chat';
+import { workbenchStore } from '~/lib/stores/workbench';
+import { type Challenge } from '~/lib/challenges';
+import { fileModificationsToHTML } from '~/utils/diff';
+import { cubicEasingFn } from '~/utils/easings';
+import { createScopedLogger, renderLogger } from '~/utils/logger';
+import { ChallengeChat as ChallengeChatBase } from './ChallengeChat';
+
+const toastAnimation = cssTransition({
+ enter: 'animated fadeInRight',
+ exit: 'animated fadeOutRight',
+});
+
+const logger = createScopedLogger('ChallengeChat');
+
+interface ChallengeChatClientProps {
+ challenge: Challenge;
+}
+
+export function ChallengeChatClient({ challenge }: ChallengeChatClientProps) {
+ renderLogger.trace('ChallengeChat');
+
+ const { ready, initialMessages, storeMessageHistory } = useChatHistory();
+
+ return (
+ <>
+ {ready && (
+
+ )}
+ {
+ return (
+
+ );
+ }}
+ icon={({ type }) => {
+ /**
+ * @todo Handle more types if we need them. This may require extra color palettes.
+ */
+ switch (type) {
+ case 'success': {
+ return ;
+ }
+ case 'error': {
+ return ;
+ }
+ }
+
+ return undefined;
+ }}
+ position="bottom-right"
+ pauseOnFocusLoss
+ transition={toastAnimation}
+ />
+ >
+ );
+}
+
+interface ChallengeChatProps {
+ challenge: Challenge;
+ initialMessages: Message[];
+ storeMessageHistory: (messages: Message[]) => Promise;
+}
+
+export const ChallengeChatImpl = memo(({ challenge, initialMessages, storeMessageHistory }: ChallengeChatProps) => {
+ useShortcuts();
+
+ const textareaRef = useRef(null);
+
+ const [chatStarted, setChatStarted] = useState(initialMessages.length > 0);
+
+ const { showChat } = useStore(chatStore);
+
+ const [animationScope, animate] = useAnimate();
+
+ const { messages, isLoading, input, handleInputChange, setInput, stop, append } = useChat({
+ api: '/api/chat',
+ onError: (error) => {
+ logger.error('Request failed\n\n', error);
+ toast.error('There was an error processing your request');
+ },
+ onFinish: () => {
+ logger.debug('Finished streaming');
+ },
+ initialMessages,
+ });
+
+ const { enhancingPrompt, promptEnhanced, enhancePrompt, resetEnhancer } = usePromptEnhancer();
+ const { parsedMessages, parseMessages } = useMessageParser();
+
+ const TEXTAREA_MAX_HEIGHT = chatStarted ? 400 : 200;
+
+ useEffect(() => {
+ chatStore.setKey('started', initialMessages.length > 0);
+ }, []);
+
+ useEffect(() => {
+ parseMessages(messages, isLoading);
+
+ if (messages.length > initialMessages.length) {
+ storeMessageHistory(messages).catch((error) => toast.error(error.message));
+ }
+ }, [messages, isLoading, parseMessages]);
+
+ const scrollTextArea = () => {
+ const textarea = textareaRef.current;
+
+ if (textarea) {
+ textarea.scrollTop = textarea.scrollHeight;
+ }
+ };
+
+ const abort = () => {
+ stop();
+ chatStore.setKey('aborted', true);
+ workbenchStore.abortAllActions();
+ };
+
+ useEffect(() => {
+ const textarea = textareaRef.current;
+
+ if (textarea) {
+ textarea.style.height = 'auto';
+
+ const scrollHeight = textarea.scrollHeight;
+
+ textarea.style.height = `${Math.min(scrollHeight, TEXTAREA_MAX_HEIGHT)}px`;
+ textarea.style.overflowY = scrollHeight > TEXTAREA_MAX_HEIGHT ? 'auto' : 'hidden';
+ }
+ }, [input, textareaRef]);
+
+ const runAnimation = async () => {
+ if (chatStarted) {
+ return;
+ }
+
+ await Promise.all([
+ animate('#challenge-intro', { opacity: 0, flex: 1 }, { duration: 0.2, ease: cubicEasingFn }),
+ ]);
+
+ chatStore.setKey('started', true);
+
+ setChatStarted(true);
+ };
+
+ const sendMessage = async (_event: React.UIEvent, messageInput?: string) => {
+ const _input = messageInput || input;
+
+ if (_input.length === 0 || isLoading) {
+ return;
+ }
+
+ /**
+ * @note (delm) Usually saving files shouldn't take long but it may take longer if there
+ * many unsaved files. In that case we need to block user input and show an indicator
+ * of some kind so the user is aware that something is happening. But I consider the
+ * happy case to be no unsaved files and I would expect users to save their changes
+ * before they send another message.
+ */
+ await workbenchStore.saveAllFiles();
+
+ const fileModifications = workbenchStore.getFileModifcations();
+
+ chatStore.setKey('aborted', false);
+
+ runAnimation();
+
+ if (fileModifications !== undefined) {
+ const diff = fileModificationsToHTML(fileModifications);
+
+ /**
+ * If we have file modifications we append a new user message manually since we have to prefix
+ * the user input with the file modifications and we don't want the new user input to appear
+ * in the prompt. Using `append` is almost the same as `handleSubmit` except that we have to
+ * manually reset the input and we'd have to manually pass in file attachments. However, those
+ * aren't relevant here.
+ */
+ append({ role: 'user', content: `${diff}\n\n${_input}` });
+
+ /**
+ * After sending a new message we reset all modifications since the model
+ * should now be aware of all the changes.
+ */
+ workbenchStore.resetAllFileModifications();
+ } else {
+ append({ role: 'user', content: _input });
+ }
+
+ setInput('');
+
+ resetEnhancer();
+
+ textareaRef.current?.blur();
+ };
+
+ const [messageRef, scrollRef] = useSnapScroll();
+
+ return (
+ {
+ if (message.role === 'user') {
+ return message;
+ }
+
+ return {
+ ...message,
+ content: parsedMessages[i] || '',
+ };
+ })}
+ enhancePrompt={() => {
+ enhancePrompt(input, (input) => {
+ setInput(input);
+ scrollTextArea();
+ });
+ }}
+ />
+ );
+});
\ No newline at end of file
diff --git a/app/components/chat/ChallengeChat.tsx b/app/components/chat/ChallengeChat.tsx
new file mode 100644
index 0000000000..1972d17799
--- /dev/null
+++ b/app/components/chat/ChallengeChat.tsx
@@ -0,0 +1,193 @@
+import type { Message } from 'ai';
+import React, { type RefCallback } from 'react';
+import { ClientOnly } from 'remix-utils/client-only';
+import { Menu } from '~/components/sidebar/Menu.client';
+import { IconButton } from '~/components/ui/IconButton';
+import { Workbench } from '~/components/workbench/Workbench.client';
+import { classNames } from '~/utils/classNames';
+import { type Challenge } from '~/lib/challenges';
+import { Messages } from './Messages.client';
+import { SendButton } from './SendButton.client';
+
+import styles from './BaseChat.module.scss';
+
+interface ChallengeChatProps {
+ challenge: Challenge;
+ textareaRef?: React.RefObject | undefined;
+ messageRef?: RefCallback | undefined;
+ scrollRef?: RefCallback | undefined;
+ showChat?: boolean;
+ chatStarted?: boolean;
+ isStreaming?: boolean;
+ messages?: Message[];
+ enhancingPrompt?: boolean;
+ promptEnhanced?: boolean;
+ input?: string;
+ handleStop?: () => void;
+ sendMessage?: (event: React.UIEvent, messageInput?: string) => void;
+ handleInputChange?: (event: React.ChangeEvent) => void;
+ enhancePrompt?: () => void;
+}
+
+const TEXTAREA_MIN_HEIGHT = 76;
+
+export const ChallengeChat = React.forwardRef(
+ (
+ {
+ challenge,
+ textareaRef,
+ messageRef,
+ scrollRef,
+ showChat = true,
+ chatStarted = false,
+ isStreaming = false,
+ enhancingPrompt = false,
+ promptEnhanced = false,
+ messages,
+ input = '',
+ sendMessage,
+ handleInputChange,
+ enhancePrompt,
+ handleStop,
+ },
+ ref,
+ ) => {
+ const TEXTAREA_MAX_HEIGHT = chatStarted ? 400 : 200;
+
+ return (
+
+
{() => }
+
+
+ {!chatStarted && (
+
+
+ {challenge.title}
+
+
+
+ {challenge.question}
+
+
+ Your challenge timer will start right after the first prompt
+
+
+
+ )}
+
+
+ {() => {
+ return chatStarted ? (
+
+ ) : null;
+ }}
+
+
+
+
{/* Ghost Element */}
+
+
+
+
{() => }
+
+
+ );
+ },
+);
\ No newline at end of file
diff --git a/app/components/header/Header.tsx b/app/components/header/Header.tsx
index 15cf4bfbd0..9d0f70c2a2 100644
--- a/app/components/header/Header.tsx
+++ b/app/components/header/Header.tsx
@@ -4,26 +4,43 @@ import { chatStore } from '~/lib/stores/chat';
import { classNames } from '~/utils/classNames';
import { HeaderActionButtons } from './HeaderActionButtons.client';
import { ChatDescription } from '~/lib/persistence/ChatDescription.client';
+import { Link, useLocation } from '@remix-run/react';
export function Header() {
const chat = useStore(chatStore);
+ const location = useLocation();
return (
-
+ {/* Navbar */}
+
{() => }
diff --git a/app/lib/challenges.ts b/app/lib/challenges.ts
new file mode 100644
index 0000000000..efeb2ee21c
--- /dev/null
+++ b/app/lib/challenges.ts
@@ -0,0 +1,32 @@
+export interface Challenge {
+ id: string;
+ title: string;
+ question: string;
+}
+
+let challengesCache: Challenge[] | null = null;
+
+async function loadChallenges(): Promise {
+ if (challengesCache) {
+ return challengesCache;
+ }
+
+ try {
+ // In production, we'll need to handle file loading differently for Cloudflare Workers
+ const challengesModule = await import('../../data/challenges.json');
+ challengesCache = challengesModule.default as Challenge[];
+ return challengesCache;
+ } catch (error) {
+ console.error('Failed to load challenges:', error);
+ return [];
+ }
+}
+
+export async function getChallengeById(id: string): Promise {
+ const challenges = await loadChallenges();
+ return challenges.find(challenge => challenge.id === id) || null;
+}
+
+export async function getAllChallenges(): Promise {
+ return loadChallenges();
+}
\ No newline at end of file
diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx
index 86d73409c9..9ab8bc5076 100644
--- a/app/routes/_index.tsx
+++ b/app/routes/_index.tsx
@@ -3,6 +3,72 @@ import { ClientOnly } from 'remix-utils/client-only';
import { BaseChat } from '~/components/chat/BaseChat';
import { Chat } from '~/components/chat/Chat.client';
import { Header } from '~/components/header/Header';
+import React, { useState } from 'react';
+import { ChallengeCard } from '~/components/challenge/ChallengeCard';
+import { useNavigate } from '@remix-run/react';
+
+type Challenge = {
+ id: string;
+ title: string;
+ image: string;
+ difficulty: 'Easy' | 'Medium' | 'Hard';
+ averageAccuracy: number;
+ description?: string;
+};
+
+const challenges: Challenge[] = [
+ {
+ id: '1',
+ title: 'Sales Dashboard',
+ image: '/sales-dashboard.png',
+ difficulty: 'Hard',
+ averageAccuracy: 62,
+ },
+ {
+ id: '2',
+ title: 'Login Box',
+ image: '/login.png',
+ difficulty: 'Easy',
+ averageAccuracy: 91,
+ },
+ {
+ id: '3',
+ title: 'Google Drive',
+ image: '/Folders.png',
+ difficulty: 'Easy',
+ averageAccuracy: 87,
+ },
+ {
+ id: '4',
+ title: 'Profile Page',
+ image: '/profile.jpg',
+ difficulty: 'Medium',
+ averageAccuracy: 74,
+ description: 'Determine whether an integer is a palindrome.',
+ },
+ {
+ id: '5',
+ title: 'Merge Intervals',
+ image: '/project-visibility.jpg',
+ difficulty: 'Medium',
+ averageAccuracy: 68,
+ description: 'Merge all overlapping intervals in a list of intervals.',
+ },
+ {
+ id: '6',
+ title: 'N-Queens',
+ image: '/social_preview_index.jpg',
+ difficulty: 'Hard',
+ averageAccuracy: 41,
+ description: 'Place N queens on an N×N chessboard so that no two queens threaten each other.',
+ },
+] as const;
+
+const difficultyOptions = ['All', 'Easy', 'Medium', 'Hard'] as const;
+const sortOptions = [
+ { value: 'title', label: 'Title' },
+ { value: 'difficulty', label: 'Difficulty' },
+];
export const meta: MetaFunction = () => {
return [{ title: 'Bolt' }, { name: 'description', content: 'Talk with Bolt, an AI assistant from StackBlitz' }];
@@ -11,10 +77,109 @@ export const meta: MetaFunction = () => {
export const loader = () => json({});
export default function Index() {
+ const navigate = useNavigate();
+ const [difficulty, setDifficulty] = useState<'All' | 'Easy' | 'Medium' | 'Hard'>('All');
+ const [sort, setSort] = useState<'title' | 'difficulty'>('title');
+ const [search, setSearch] = useState('');
+
+ const filtered = challenges.filter(
+ (c) =>
+ (difficulty === 'All' || c.difficulty === difficulty) &&
+ (c.title.toLowerCase().includes(search.toLowerCase()) ||
+ (c.description && c.description.toLowerCase().includes(search.toLowerCase()))),
+ );
+ const sorted = [...filtered].sort((a, b) => {
+ if (sort === 'title') {
+ return a.title.localeCompare(b.title);
+ }
+
+ if (sort === 'difficulty') {
+ return a.difficulty.localeCompare(b.difficulty);
+ }
+
+ return 0;
+ });
+
return (
-
}>{() =>
}
+
+
+
+
+
+
+ Solve Challenges
+
+
+ Browse and solve interactive UI challenges to sharpen your frontend skills.
+
+
+
+
+
+
setSearch(e.target.value)}
+ className="flex-1 rounded-lg px-4 py-2 border-0 bg-bolt-elements-background-depth-1 text-bolt-elements-textPrimary focus:outline-none focus:ring-2 focus:ring-bolt-elements-accent/60 transition shadow-md text-base font-medium placeholder:text-bolt-elements-textSecondary"
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+ {sorted.map((challenge) => (
+ navigate(`/challenge/${challenge.id}`)} />
+ ))}
+
+
+
);
}
diff --git a/app/routes/challenge.$id.tsx b/app/routes/challenge.$id.tsx
new file mode 100644
index 0000000000..f54070a016
--- /dev/null
+++ b/app/routes/challenge.$id.tsx
@@ -0,0 +1,41 @@
+import { json, type LoaderFunctionArgs, type MetaFunction } from '@remix-run/cloudflare';
+import { useLoaderData } from '@remix-run/react';
+import { ClientOnly } from 'remix-utils/client-only';
+import { ChallengeChat as ChallengeChatFallback } from '~/components/chat/ChallengeChat';
+import { ChallengeChatClient } from '~/components/chat/ChallengeChat.client';
+import { Header } from '~/components/header/Header';
+import { getChallengeById, type Challenge } from '~/lib/challenges';
+
+export const meta: MetaFunction = ({ data }) => {
+ const title = data?.challenge ? `${data.challenge.title} - Challenge` : 'Challenge Not Found';
+ return [{ title }, { name: 'description', content: 'Code challenges powered by AI' }];
+};
+
+export async function loader({ params }: LoaderFunctionArgs) {
+ const { id } = params;
+
+ if (!id) {
+ throw new Response('Challenge ID is required', { status: 400 });
+ }
+
+ const challenge = await getChallengeById(id);
+
+ if (!challenge) {
+ throw new Response('Challenge not found', { status: 404 });
+ }
+
+ return json({ challenge });
+}
+
+export default function Challenge() {
+ const { challenge } = useLoaderData();
+
+ return (
+
+
+ }>
+ {() => }
+
+
+ );
+}
\ No newline at end of file
diff --git a/app/routes/profile.tsx b/app/routes/profile.tsx
new file mode 100644
index 0000000000..3d3dda4db9
--- /dev/null
+++ b/app/routes/profile.tsx
@@ -0,0 +1,154 @@
+import { Header } from '~/components/header/Header';
+import { useState } from 'react';
+
+// dummy data for demonstration
+const userStats = {
+ name: 'Azam Jawad Butt',
+ solved: 42,
+ easy: 20,
+ medium: 15,
+ hard: 7,
+ easyTotal: 50,
+ mediumTotal: 40,
+ hardTotal: 20,
+};
+
+function getRank(solved: number) {
+ if (solved >= 100) {
+ return 'Coding Master';
+ }
+
+ if (solved >= 50) {
+ return 'Vibe Coder';
+ }
+
+ if (solved >= 20) {
+ return 'Getting Good';
+ }
+
+ return 'Beginner';
+}
+
+type ProgressBarProps = { value: number; max: number; color: string };
+
+function ProgressBar({ value, max, color }: ProgressBarProps) {
+ const percent = Math.min(100, Math.round((value / max) * 100));
+ return (
+
+ );
+}
+
+export default function ProfilePage() {
+ const [showStats, setShowStats] = useState(false);
+
+ return (
+
+
+
+
+ {/* Profile header removed */}
+
+
+
+ JB
+
+
+ {userStats.name}
+
+
+ {getRank(userStats.solved)}
+
+
+ Active
+ Member since 2024
+
+
Last solved: 2 days ago
+
+
+
+
+ {userStats.easy}
+ Easy
+
+
+ {userStats.medium}
+ Medium
+
+
+ {userStats.hard}
+ Hard
+
+
+
+ Total Solved:
+ {userStats.solved}
+
+
+
+ Streak: 5 days
+
+
+ Accuracy: 92%
+
+
+
+
+ {/* Progress section */}
+
+ {/* Add more detail: stats table */}
+
+
+ {showStats && (
+
+
+
+
+ | Category |
+ Value |
+
+
+
+
+ | Average Acceptance Rate |
+ 92% |
+
+
+ | Longest Streak |
+ 12 days |
+
+
+ | Last Submission |
+ 2 days ago |
+
+
+ | Total Submissions |
+ 128 |
+
+
+
+
+ )}
+
+
+
+
+
+ );
+}
diff --git a/app/styles/variables.scss b/app/styles/variables.scss
index 38967b1171..ce85ee9384 100644
--- a/app/styles/variables.scss
+++ b/app/styles/variables.scss
@@ -1,183 +1,183 @@
/* Color Tokens Light Theme */
:root,
:root[data-theme='light'] {
- --bolt-elements-borderColor: theme('colors.alpha.gray.10');
- --bolt-elements-borderColorActive: theme('colors.accent.600');
+ --bolt-elements-borderColor: #E9D5FF;
+ --bolt-elements-borderColorActive: #A78BFA;
- --bolt-elements-bg-depth-1: theme('colors.white');
- --bolt-elements-bg-depth-2: theme('colors.gray.50');
- --bolt-elements-bg-depth-3: theme('colors.gray.200');
- --bolt-elements-bg-depth-4: theme('colors.alpha.gray.5');
+ --bolt-elements-background-depth-1: #F5F3FF;
+ --bolt-elements-background-depth-2: #EDE9FE;
+ --bolt-elements-background-depth-3: #DDD6FE;
+ --bolt-elements-background-depth-4: #F3E8FF;
- --bolt-elements-textPrimary: theme('colors.gray.950');
- --bolt-elements-textSecondary: theme('colors.gray.600');
- --bolt-elements-textTertiary: theme('colors.gray.500');
+ --bolt-elements-textPrimary: #3B0764;
+ --bolt-elements-textSecondary: #7C3AED;
+ --bolt-elements-textTertiary: #C4B5FD;
- --bolt-elements-code-background: theme('colors.gray.100');
- --bolt-elements-code-text: theme('colors.gray.950');
+ --bolt-elements-code-background: #EDE9FE;
+ --bolt-elements-code-text: #3B0764;
- --bolt-elements-button-primary-background: theme('colors.alpha.accent.10');
- --bolt-elements-button-primary-backgroundHover: theme('colors.alpha.accent.20');
- --bolt-elements-button-primary-text: theme('colors.accent.500');
+ --bolt-elements-button-primary-background: #A78BFA;
+ --bolt-elements-button-primary-backgroundHover: #C4B5FD;
+ --bolt-elements-button-primary-text: #6D28D9;
- --bolt-elements-button-secondary-background: theme('colors.alpha.gray.5');
- --bolt-elements-button-secondary-backgroundHover: theme('colors.alpha.gray.10');
- --bolt-elements-button-secondary-text: theme('colors.gray.950');
+ --bolt-elements-button-secondary-background: #F3E8FF;
+ --bolt-elements-button-secondary-backgroundHover: #E9D5FF;
+ --bolt-elements-button-secondary-text: #6D28D9;
- --bolt-elements-button-danger-background: theme('colors.alpha.red.10');
- --bolt-elements-button-danger-backgroundHover: theme('colors.alpha.red.20');
- --bolt-elements-button-danger-text: theme('colors.red.500');
+ --bolt-elements-button-danger-background: #F3E8FF;
+ --bolt-elements-button-danger-backgroundHover: #E9D5FF;
+ --bolt-elements-button-danger-text: #A21CAF;
- --bolt-elements-item-contentDefault: theme('colors.alpha.gray.50');
- --bolt-elements-item-contentActive: theme('colors.gray.950');
- --bolt-elements-item-contentAccent: theme('colors.accent.700');
- --bolt-elements-item-contentDanger: theme('colors.red.500');
+ --bolt-elements-item-contentDefault: #C4B5FD;
+ --bolt-elements-item-contentActive: #3B0764;
+ --bolt-elements-item-contentAccent: #A78BFA;
+ --bolt-elements-item-contentDanger: #A21CAF;
--bolt-elements-item-backgroundDefault: rgba(0, 0, 0, 0);
- --bolt-elements-item-backgroundActive: theme('colors.alpha.gray.5');
- --bolt-elements-item-backgroundAccent: theme('colors.alpha.accent.10');
- --bolt-elements-item-backgroundDanger: theme('colors.alpha.red.10');
+ --bolt-elements-item-backgroundActive: #F3E8FF;
+ --bolt-elements-item-backgroundAccent: #EDE9FE;
+ --bolt-elements-item-backgroundDanger: #F3E8FF;
- --bolt-elements-loader-background: theme('colors.alpha.gray.10');
- --bolt-elements-loader-progress: theme('colors.accent.500');
+ --bolt-elements-loader-background: #E9D5FF;
+ --bolt-elements-loader-progress: #A78BFA;
- --bolt-elements-artifacts-background: theme('colors.white');
- --bolt-elements-artifacts-backgroundHover: theme('colors.alpha.gray.2');
+ --bolt-elements-artifacts-background: #F5F3FF;
+ --bolt-elements-artifacts-backgroundHover: #F3E8FF;
--bolt-elements-artifacts-borderColor: var(--bolt-elements-borderColor);
- --bolt-elements-artifacts-inlineCode-background: theme('colors.gray.100');
+ --bolt-elements-artifacts-inlineCode-background: #EDE9FE;
--bolt-elements-artifacts-inlineCode-text: var(--bolt-elements-textPrimary);
- --bolt-elements-actions-background: theme('colors.white');
- --bolt-elements-actions-code-background: theme('colors.gray.800');
+ --bolt-elements-actions-background: #F5F3FF;
+ --bolt-elements-actions-code-background: #7C3AED;
- --bolt-elements-messages-background: theme('colors.gray.100');
- --bolt-elements-messages-linkColor: theme('colors.accent.500');
- --bolt-elements-messages-code-background: theme('colors.gray.800');
- --bolt-elements-messages-inlineCode-background: theme('colors.gray.200');
- --bolt-elements-messages-inlineCode-text: theme('colors.gray.800');
+ --bolt-elements-messages-background: #EDE9FE;
+ --bolt-elements-messages-linkColor: #A78BFA;
+ --bolt-elements-messages-code-background: #7C3AED;
+ --bolt-elements-messages-inlineCode-background: #DDD6FE;
+ --bolt-elements-messages-inlineCode-text: #7C3AED;
- --bolt-elements-icon-success: theme('colors.green.500');
- --bolt-elements-icon-error: theme('colors.red.500');
- --bolt-elements-icon-primary: theme('colors.gray.950');
- --bolt-elements-icon-secondary: theme('colors.gray.600');
- --bolt-elements-icon-tertiary: theme('colors.gray.500');
+ --bolt-elements-icon-success: #8B5CF6;
+ --bolt-elements-icon-error: #A21CAF;
+ --bolt-elements-icon-primary: #3B0764;
+ --bolt-elements-icon-secondary: #7C3AED;
+ --bolt-elements-icon-tertiary: #C4B5FD;
- --bolt-elements-dividerColor: theme('colors.gray.100');
+ --bolt-elements-dividerColor: #E9D5FF;
- --bolt-elements-prompt-background: theme('colors.alpha.white.80');
+ --bolt-elements-prompt-background: rgba(245, 243, 255, 0.8);
- --bolt-elements-sidebar-dropdownShadow: theme('colors.alpha.gray.10');
- --bolt-elements-sidebar-buttonBackgroundDefault: theme('colors.alpha.accent.10');
- --bolt-elements-sidebar-buttonBackgroundHover: theme('colors.alpha.accent.20');
- --bolt-elements-sidebar-buttonText: theme('colors.accent.700');
+ --bolt-elements-sidebar-dropdownShadow: #E9D5FF;
+ --bolt-elements-sidebar-buttonBackgroundDefault: #EDE9FE;
+ --bolt-elements-sidebar-buttonBackgroundHover: #DDD6FE;
+ --bolt-elements-sidebar-buttonText: #A78BFA;
- --bolt-elements-preview-addressBar-background: theme('colors.gray.100');
- --bolt-elements-preview-addressBar-backgroundHover: theme('colors.alpha.gray.5');
- --bolt-elements-preview-addressBar-backgroundActive: theme('colors.white');
+ --bolt-elements-preview-addressBar-background: #EDE9FE;
+ --bolt-elements-preview-addressBar-backgroundHover: #F3E8FF;
+ --bolt-elements-preview-addressBar-backgroundActive: #F5F3FF;
--bolt-elements-preview-addressBar-text: var(--bolt-elements-textSecondary);
--bolt-elements-preview-addressBar-textActive: var(--bolt-elements-textPrimary);
- --bolt-elements-terminals-background: theme('colors.white');
- --bolt-elements-terminals-buttonBackground: var(--bolt-elements-bg-depth-4);
+ --bolt-elements-terminals-background: #F5F3FF;
+ --bolt-elements-terminals-buttonBackground: var(--bolt-elements-background-depth-4);
- --bolt-elements-cta-background: theme('colors.gray.100');
- --bolt-elements-cta-text: theme('colors.gray.950');
+ --bolt-elements-cta-background: #EDE9FE;
+ --bolt-elements-cta-text: #3B0764;
/* Terminal Colors */
--bolt-terminal-background: var(--bolt-elements-terminals-background);
- --bolt-terminal-foreground: #333333;
- --bolt-terminal-selection-background: #00000040;
- --bolt-terminal-black: #000000;
- --bolt-terminal-red: #cd3131;
- --bolt-terminal-green: #00bc00;
- --bolt-terminal-yellow: #949800;
- --bolt-terminal-blue: #0451a5;
- --bolt-terminal-magenta: #bc05bc;
- --bolt-terminal-cyan: #0598bc;
- --bolt-terminal-white: #555555;
- --bolt-terminal-brightBlack: #686868;
- --bolt-terminal-brightRed: #cd3131;
- --bolt-terminal-brightGreen: #00bc00;
- --bolt-terminal-brightYellow: #949800;
- --bolt-terminal-brightBlue: #0451a5;
- --bolt-terminal-brightMagenta: #bc05bc;
- --bolt-terminal-brightCyan: #0598bc;
- --bolt-terminal-brightWhite: #a5a5a5;
+ --bolt-terminal-foreground: #3B0764;
+ --bolt-terminal-selection-background: #A78BFA40;
+ --bolt-terminal-black: #3B0764;
+ --bolt-terminal-red: #A21CAF;
+ --bolt-terminal-green: #8B5CF6;
+ --bolt-terminal-yellow: #C4B5FD;
+ --bolt-terminal-blue: #7C3AED;
+ --bolt-terminal-magenta: #A78BFA;
+ --bolt-terminal-cyan: #C4B5FD;
+ --bolt-terminal-white: #F5F3FF;
+ --bolt-terminal-brightBlack: #7C3AED;
+ --bolt-terminal-brightRed: #A21CAF;
+ --bolt-terminal-brightGreen: #A78BFA;
+ --bolt-terminal-brightYellow: #DDD6FE;
+ --bolt-terminal-brightBlue: #C4B5FD;
+ --bolt-terminal-brightMagenta: #E9D5FF;
+ --bolt-terminal-brightCyan: #F3E8FF;
+ --bolt-terminal-brightWhite: #FFFFFF;
}
/* Color Tokens Dark Theme */
:root,
:root[data-theme='dark'] {
- --bolt-elements-borderColor: theme('colors.alpha.white.10');
- --bolt-elements-borderColorActive: theme('colors.accent.500');
+ --bolt-elements-borderColor: rgba(139, 92, 246, 0.2);
+ --bolt-elements-borderColorActive: #A78BFA;
- --bolt-elements-bg-depth-1: theme('colors.gray.950');
- --bolt-elements-bg-depth-2: theme('colors.gray.900');
- --bolt-elements-bg-depth-3: theme('colors.gray.800');
- --bolt-elements-bg-depth-4: theme('colors.alpha.white.5');
+ --bolt-elements-bg-depth-1: #0F0F23;
+ --bolt-elements-bg-depth-2: #1E1B2F;
+ --bolt-elements-bg-depth-3: #2D2A3E;
+ --bolt-elements-bg-depth-4: rgba(139, 92, 246, 0.05);
- --bolt-elements-textPrimary: theme('colors.white');
- --bolt-elements-textSecondary: theme('colors.gray.400');
- --bolt-elements-textTertiary: theme('colors.gray.500');
+ --bolt-elements-textPrimary: #F8FAFC;
+ --bolt-elements-textSecondary: #CBD5E1;
+ --bolt-elements-textTertiary: #94A3B8;
- --bolt-elements-code-background: theme('colors.gray.800');
- --bolt-elements-code-text: theme('colors.white');
+ --bolt-elements-code-background: #2D2A3E;
+ --bolt-elements-code-text: #F8FAFC;
- --bolt-elements-button-primary-background: theme('colors.alpha.accent.10');
- --bolt-elements-button-primary-backgroundHover: theme('colors.alpha.accent.20');
- --bolt-elements-button-primary-text: theme('colors.accent.500');
+ --bolt-elements-button-primary-background: rgba(139, 92, 246, 0.15);
+ --bolt-elements-button-primary-backgroundHover: rgba(139, 92, 246, 0.25);
+ --bolt-elements-button-primary-text: #A78BFA;
- --bolt-elements-button-secondary-background: theme('colors.alpha.white.5');
- --bolt-elements-button-secondary-backgroundHover: theme('colors.alpha.white.10');
- --bolt-elements-button-secondary-text: theme('colors.white');
+ --bolt-elements-button-secondary-background: rgba(248, 250, 252, 0.05);
+ --bolt-elements-button-secondary-backgroundHover: rgba(248, 250, 252, 0.1);
+ --bolt-elements-button-secondary-text: #F8FAFC;
- --bolt-elements-button-danger-background: theme('colors.alpha.red.10');
- --bolt-elements-button-danger-backgroundHover: theme('colors.alpha.red.20');
- --bolt-elements-button-danger-text: theme('colors.red.500');
+ --bolt-elements-button-danger-background: rgba(239, 68, 68, 0.15);
+ --bolt-elements-button-danger-backgroundHover: rgba(239, 68, 68, 0.25);
+ --bolt-elements-button-danger-text: #FCA5A5;
- --bolt-elements-item-contentDefault: theme('colors.alpha.white.50');
- --bolt-elements-item-contentActive: theme('colors.white');
- --bolt-elements-item-contentAccent: theme('colors.accent.500');
- --bolt-elements-item-contentDanger: theme('colors.red.500');
+ --bolt-elements-item-contentDefault: rgba(248, 250, 252, 0.5);
+ --bolt-elements-item-contentActive: #F8FAFC;
+ --bolt-elements-item-contentAccent: #A78BFA;
+ --bolt-elements-item-contentDanger: #FCA5A5;
--bolt-elements-item-backgroundDefault: rgba(255, 255, 255, 0);
- --bolt-elements-item-backgroundActive: theme('colors.alpha.white.10');
- --bolt-elements-item-backgroundAccent: theme('colors.alpha.accent.10');
- --bolt-elements-item-backgroundDanger: theme('colors.alpha.red.10');
+ --bolt-elements-item-backgroundActive: rgba(248, 250, 252, 0.1);
+ --bolt-elements-item-backgroundAccent: rgba(139, 92, 246, 0.15);
+ --bolt-elements-item-backgroundDanger: rgba(239, 68, 68, 0.15);
- --bolt-elements-loader-background: theme('colors.alpha.gray.10');
- --bolt-elements-loader-progress: theme('colors.accent.500');
+ --bolt-elements-loader-background: rgba(139, 92, 246, 0.2);
+ --bolt-elements-loader-progress: #A78BFA;
- --bolt-elements-artifacts-background: theme('colors.gray.900');
- --bolt-elements-artifacts-backgroundHover: theme('colors.alpha.white.5');
+ --bolt-elements-artifacts-background: #1E1B2F;
+ --bolt-elements-artifacts-backgroundHover: rgba(248, 250, 252, 0.05);
--bolt-elements-artifacts-borderColor: var(--bolt-elements-borderColor);
- --bolt-elements-artifacts-inlineCode-background: theme('colors.gray.800');
- --bolt-elements-artifacts-inlineCode-text: theme('colors.white');
+ --bolt-elements-artifacts-inlineCode-background: #2D2A3E;
+ --bolt-elements-artifacts-inlineCode-text: #F8FAFC;
- --bolt-elements-actions-background: theme('colors.gray.900');
- --bolt-elements-actions-code-background: theme('colors.gray.800');
+ --bolt-elements-actions-background: #1E1B2F;
+ --bolt-elements-actions-code-background: #2D2A3E;
- --bolt-elements-messages-background: theme('colors.gray.800');
- --bolt-elements-messages-linkColor: theme('colors.accent.500');
- --bolt-elements-messages-code-background: theme('colors.gray.900');
- --bolt-elements-messages-inlineCode-background: theme('colors.gray.700');
+ --bolt-elements-messages-background: #2D2A3E;
+ --bolt-elements-messages-linkColor: #A78BFA;
+ --bolt-elements-messages-code-background: #1E1B2F;
+ --bolt-elements-messages-inlineCode-background: #374151;
--bolt-elements-messages-inlineCode-text: var(--bolt-elements-textPrimary);
- --bolt-elements-icon-success: theme('colors.green.400');
- --bolt-elements-icon-error: theme('colors.red.400');
- --bolt-elements-icon-primary: theme('colors.gray.950');
- --bolt-elements-icon-secondary: theme('colors.gray.600');
- --bolt-elements-icon-tertiary: theme('colors.gray.500');
+ --bolt-elements-icon-success: #34D399;
+ --bolt-elements-icon-error: #FCA5A5;
+ --bolt-elements-icon-primary: #F8FAFC;
+ --bolt-elements-icon-secondary: #CBD5E1;
+ --bolt-elements-icon-tertiary: #94A3B8;
- --bolt-elements-dividerColor: theme('colors.gray.100');
+ --bolt-elements-dividerColor: rgba(139, 92, 246, 0.15);
- --bolt-elements-prompt-background: theme('colors.alpha.gray.80');
+ --bolt-elements-prompt-background: rgba(15, 15, 35, 0.8);
- --bolt-elements-sidebar-dropdownShadow: theme('colors.alpha.gray.30');
- --bolt-elements-sidebar-buttonBackgroundDefault: theme('colors.alpha.accent.10');
- --bolt-elements-sidebar-buttonBackgroundHover: theme('colors.alpha.accent.20');
- --bolt-elements-sidebar-buttonText: theme('colors.accent.500');
+ --bolt-elements-sidebar-dropdownShadow: rgba(0, 0, 0, 0.3);
+ --bolt-elements-sidebar-buttonBackgroundDefault: rgba(139, 92, 246, 0.15);
+ --bolt-elements-sidebar-buttonBackgroundHover: rgba(139, 92, 246, 0.25);
+ --bolt-elements-sidebar-buttonText: #A78BFA;
--bolt-elements-preview-addressBar-background: var(--bolt-elements-bg-depth-1);
- --bolt-elements-preview-addressBar-backgroundHover: theme('colors.alpha.white.5');
+ --bolt-elements-preview-addressBar-backgroundHover: rgba(248, 250, 252, 0.05);
--bolt-elements-preview-addressBar-backgroundActive: var(--bolt-elements-bg-depth-1);
--bolt-elements-preview-addressBar-text: var(--bolt-elements-textSecondary);
--bolt-elements-preview-addressBar-textActive: var(--bolt-elements-textPrimary);
@@ -185,29 +185,29 @@
--bolt-elements-terminals-background: var(--bolt-elements-bg-depth-1);
--bolt-elements-terminals-buttonBackground: var(--bolt-elements-bg-depth-3);
- --bolt-elements-cta-background: theme('colors.alpha.white.10');
- --bolt-elements-cta-text: theme('colors.white');
+ --bolt-elements-cta-background: rgba(248, 250, 252, 0.1);
+ --bolt-elements-cta-text: #F8FAFC;
/* Terminal Colors */
--bolt-terminal-background: var(--bolt-elements-terminals-background);
- --bolt-terminal-foreground: #eff0eb;
- --bolt-terminal-selection-background: #97979b33;
- --bolt-terminal-black: #000000;
- --bolt-terminal-red: #ff5c57;
- --bolt-terminal-green: #5af78e;
- --bolt-terminal-yellow: #f3f99d;
- --bolt-terminal-blue: #57c7ff;
- --bolt-terminal-magenta: #ff6ac1;
- --bolt-terminal-cyan: #9aedfe;
- --bolt-terminal-white: #f1f1f0;
- --bolt-terminal-brightBlack: #686868;
- --bolt-terminal-brightRed: #ff5c57;
- --bolt-terminal-brightGreen: #5af78e;
- --bolt-terminal-brightYellow: #f3f99d;
- --bolt-terminal-brightBlue: #57c7ff;
- --bolt-terminal-brightMagenta: #ff6ac1;
- --bolt-terminal-brightCyan: #9aedfe;
- --bolt-terminal-brightWhite: #f1f1f0;
+ --bolt-terminal-foreground: #F8FAFC;
+ --bolt-terminal-selection-background: rgba(139, 92, 246, 0.3);
+ --bolt-terminal-black: #0F0F23;
+ --bolt-terminal-red: #F87171;
+ --bolt-terminal-green: #34D399;
+ --bolt-terminal-yellow: #FBBF24;
+ --bolt-terminal-blue: #60A5FA;
+ --bolt-terminal-magenta: #A78BFA;
+ --bolt-terminal-cyan: #22D3EE;
+ --bolt-terminal-white: #F8FAFC;
+ --bolt-terminal-brightBlack: #6B7280;
+ --bolt-terminal-brightRed: #FCA5A5;
+ --bolt-terminal-brightGreen: #6EE7B7;
+ --bolt-terminal-brightYellow: #FCD34D;
+ --bolt-terminal-brightBlue: #93C5FD;
+ --bolt-terminal-brightMagenta: #C4B5FD;
+ --bolt-terminal-brightCyan: #67E8F9;
+ --bolt-terminal-brightWhite: #FFFFFF;
}
/*
diff --git a/data/challenges.json b/data/challenges.json
new file mode 100644
index 0000000000..99d82fc03d
--- /dev/null
+++ b/data/challenges.json
@@ -0,0 +1,12 @@
+[
+ {
+ "id": "counter",
+ "title": "Build a Counter",
+ "question": "Make a click counter with +, -, and reset buttons. The value must never go below 0."
+ },
+ {
+ "id": "todo",
+ "title": "Todo List",
+ "question": "Build a todo list with add and delete functionality."
+ }
+]
\ No newline at end of file
diff --git a/public/Folders.png b/public/Folders.png
new file mode 100644
index 0000000000..3f4f6769eb
Binary files /dev/null and b/public/Folders.png differ
diff --git a/public/counter.gif b/public/counter.gif
new file mode 100644
index 0000000000..c939de9e6c
Binary files /dev/null and b/public/counter.gif differ
diff --git a/public/login.png b/public/login.png
new file mode 100644
index 0000000000..212fae8708
Binary files /dev/null and b/public/login.png differ
diff --git a/public/profile.jpg b/public/profile.jpg
new file mode 100644
index 0000000000..07c61fb3a6
Binary files /dev/null and b/public/profile.jpg differ
diff --git a/public/sales-dashboard.png b/public/sales-dashboard.png
new file mode 100644
index 0000000000..14381ab121
Binary files /dev/null and b/public/sales-dashboard.png differ