@@ -142,9 +173,9 @@ export function DemoMode({ initialScreenId, onBack, onNavigateToStory }: DemoMod
style={{
display: 'block',
padding: '8px 16px',
- borderBottom: '1px solid var(--sketch-light-gray)',
+ borderBottom: '1px solid var(--tool-border-light)',
textDecoration: 'none',
- color: 'inherit',
+ color: 'var(--tool-text)',
cursor: 'pointer',
}}
>
@@ -193,7 +224,7 @@ export function DemoMode({ initialScreenId, onBack, onNavigateToStory }: DemoMod
Tous les écrans
@@ -207,8 +238,9 @@ export function DemoMode({ initialScreenId, onBack, onNavigateToStory }: DemoMod
fontFamily: 'var(--font-sketch)',
fontSize: 14,
cursor: 'pointer',
- background: s.id === currentScreenId ? 'var(--sketch-light-gray)' : 'transparent',
- borderLeft: s.id === currentScreenId ? '3px solid var(--sketch-black)' : '3px solid transparent',
+ background: s.id === currentScreenId ? 'var(--tool-border-light)' : 'transparent',
+ borderLeft: s.id === currentScreenId ? '3px solid var(--tool-text)' : '3px solid transparent',
+ color: 'var(--tool-text)',
}}
>
{s.name}
diff --git a/src/components/Gallery.tsx b/src/components/Gallery.tsx
index 22a6a7a..1ab21e2 100644
--- a/src/components/Gallery.tsx
+++ b/src/components/Gallery.tsx
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import { PhoneFrame } from './sketchy';
import { screenGroups, type Screen } from '../screens';
+import { ThemeToggle } from './ThemeToggle';
interface GalleryProps {
onSelectScreen: (screenId: string) => void;
@@ -19,8 +20,9 @@ export function Gallery({ onSelectScreen, onShowStories, onShowSpecs }: GalleryP
@@ -28,13 +30,14 @@ export function Gallery({ onSelectScreen, onShowStories, onShowSpecs }: GalleryP
fontFamily: 'var(--font-sketch)',
fontSize: 28,
margin: 0,
+ color: 'var(--tool-text)',
}}>
Festipod
Cliquez sur un écran pour le prévisualiser
@@ -51,12 +54,13 @@ export function Gallery({ onSelectScreen, onShowStories, onShowSpecs }: GalleryP
onClick={onShowStories}
style={{
background: 'none',
- border: '2px solid var(--sketch-black)',
+ border: '2px solid var(--tool-border)',
borderRadius: '255px 15px 225px 15px/15px 225px 15px 255px',
padding: '8px 16px',
fontFamily: 'var(--font-sketch)',
fontSize: 14,
cursor: 'pointer',
+ color: 'var(--tool-text)',
}}
>
User Stories
@@ -67,9 +71,9 @@ export function Gallery({ onSelectScreen, onShowStories, onShowSpecs }: GalleryP
+
+ {/* Theme toggle */}
+
@@ -114,7 +121,7 @@ export function Gallery({ onSelectScreen, onShowStories, onShowSpecs }: GalleryP
fontFamily: 'var(--font-sketch)',
fontSize: 18,
margin: '0 0 16px 32px',
- color: 'var(--sketch-black)',
+ color: 'var(--tool-text)',
}}>
{group.name}
@@ -179,7 +186,7 @@ function GalleryItem({ screen, scale, onClick }: GalleryItemProps) {
fontSize: 14,
textAlign: 'center',
marginTop: 8,
- color: 'var(--sketch-black)',
+ color: 'var(--tool-text)',
}}>
{screen.name}
diff --git a/src/components/ThemeToggle.tsx b/src/components/ThemeToggle.tsx
new file mode 100644
index 0000000..d53cdc8
--- /dev/null
+++ b/src/components/ThemeToggle.tsx
@@ -0,0 +1,59 @@
+import React from 'react';
+import { useTheme } from '../context/ThemeContext';
+import { Sun, Moon, Monitor } from 'lucide-react';
+
+export function ThemeToggle() {
+ const { theme, setTheme } = useTheme();
+
+ const cycleTheme = () => {
+ if (theme === 'system') setTheme('light');
+ else if (theme === 'light') setTheme('dark');
+ else setTheme('system');
+ };
+
+ const getIcon = () => {
+ switch (theme) {
+ case 'light':
+ return
;
+ case 'dark':
+ return
;
+ case 'system':
+ return
;
+ }
+ };
+
+ const getLabel = () => {
+ switch (theme) {
+ case 'light':
+ return 'Clair';
+ case 'dark':
+ return 'Sombre';
+ case 'system':
+ return 'Auto';
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/src/components/UserStoriesPage.tsx b/src/components/UserStoriesPage.tsx
index 7ca5a00..64a1430 100644
--- a/src/components/UserStoriesPage.tsx
+++ b/src/components/UserStoriesPage.tsx
@@ -10,6 +10,7 @@ import {
type StoryCategory,
} from '../data';
import { getScreen, screens } from '../screens';
+import { ThemeToggle } from './ThemeToggle';
interface UserStoriesPageProps {
selectedStoryId?: string;
@@ -101,64 +102,71 @@ export function UserStoriesPage({ selectedStoryId, onBack, onSelectScreen }: Use
const hasFilters = selectedCategories.size > 0 || selectedPriorities.size > 0 || selectedScreens.size > 0;
return (
-
+
{/* Header */}
-
-
-
- User Stories
-
-
- {filteredStories.length} / {userStories.length} stories · Cliquez sur un écran pour voir le mockup
-
+
+
+
+
+ User Stories
+
+
+ {filteredStories.length} / {userStories.length} stories · Cliquez sur un écran pour voir le mockup
+
+
+
{/* Filter bar */}
{/* Category filters */}
Catégorie
@@ -179,7 +187,7 @@ export function UserStoriesPage({ selectedStoryId, onBack, onSelectScreen }: Use
Priorité
@@ -200,7 +208,7 @@ export function UserStoriesPage({ selectedStoryId, onBack, onSelectScreen }: Use
Écran
@@ -209,7 +217,7 @@ export function UserStoriesPage({ selectedStoryId, onBack, onSelectScreen }: Use
toggleScreen(screen.id)}
/>
@@ -243,7 +251,7 @@ export function UserStoriesPage({ selectedStoryId, onBack, onSelectScreen }: Use
@@ -259,6 +267,7 @@ export function UserStoriesPage({ selectedStoryId, onBack, onSelectScreen }: Use
display: 'flex',
alignItems: 'center',
gap: 12,
+ color: 'var(--tool-text)',
}}>
({stories.length} stories)
@@ -345,10 +354,10 @@ const StoryCard = React.forwardRef(
@@ -370,6 +379,7 @@ const StoryCard = React.forwardRef
(
fontFamily: 'var(--font-sketch)',
fontSize: 16,
margin: 0,
+ color: 'var(--tool-text)',
}}>
{story.title}
@@ -378,7 +388,7 @@ const StoryCard = React.forwardRef(
@@ -392,13 +402,14 @@ const StoryCard = React.forwardRef(
key={id}
onClick={() => onSelectScreen(id)}
style={{
- background: 'var(--sketch-light-gray)',
- border: '1px solid var(--sketch-black)',
+ background: 'var(--tool-border-light)',
+ border: '1px solid var(--tool-border)',
borderRadius: '255px 15px 225px 15px/15px 225px 15px 255px',
padding: '6px 12px',
fontFamily: 'var(--font-sketch)',
fontSize: 13,
cursor: 'pointer',
+ color: 'var(--tool-text)',
}}
>
→ {screen!.name}
@@ -409,7 +420,7 @@ const StoryCard = React.forwardRef(
diff --git a/src/components/sketchy/PhoneFrame.tsx b/src/components/sketchy/PhoneFrame.tsx
index f02baef..fc19275 100644
--- a/src/components/sketchy/PhoneFrame.tsx
+++ b/src/components/sketchy/PhoneFrame.tsx
@@ -13,7 +13,7 @@ export function PhoneFrame({ children, scale = 1, className = '' }: PhoneFramePr
return (
9:41
diff --git a/src/components/specs/SpecsPage.tsx b/src/components/specs/SpecsPage.tsx
index 8463cfc..ac81d11 100644
--- a/src/components/specs/SpecsPage.tsx
+++ b/src/components/specs/SpecsPage.tsx
@@ -8,6 +8,7 @@ import { Card, CardHeader, CardTitle, CardContent, CardDescription } from '../ui
import { Button } from '../ui/button';
import { ArrowLeft, FileText, Monitor, CheckCircle2, XCircle, AlertCircle, ExternalLink } from 'lucide-react';
import type { ParsedFeature } from '../../types/gherkin';
+import { ThemeToggle } from '../ThemeToggle';
interface SpecsPageProps {
selectedFeatureId?: string;
@@ -109,8 +110,10 @@ export function SpecsPage({ selectedFeatureId, onBack, onSelectScreen, onSelectS
Rapport HTML
+
)}
+ {testSummary.totalScenarios === 0 && }
diff --git a/src/context/ThemeContext.tsx b/src/context/ThemeContext.tsx
new file mode 100644
index 0000000..c9d9ffb
--- /dev/null
+++ b/src/context/ThemeContext.tsx
@@ -0,0 +1,83 @@
+import React, { createContext, useContext, useEffect, useState, type ReactNode } from 'react';
+
+type Theme = 'light' | 'dark' | 'system';
+
+interface ThemeContextValue {
+ theme: Theme;
+ resolvedTheme: 'light' | 'dark';
+ setTheme: (theme: Theme) => void;
+}
+
+const ThemeContext = createContext
(null);
+
+const STORAGE_KEY = 'festipod-theme';
+
+function getSystemTheme(): 'light' | 'dark' {
+ if (typeof window === 'undefined') return 'light';
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
+}
+
+function getStoredTheme(): Theme {
+ if (typeof window === 'undefined') return 'system';
+ const stored = localStorage.getItem(STORAGE_KEY);
+ if (stored === 'light' || stored === 'dark' || stored === 'system') {
+ return stored;
+ }
+ return 'system';
+}
+
+export function ThemeProvider({ children }: { children: ReactNode }) {
+ const [theme, setThemeState] = useState(getStoredTheme);
+ const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>(
+ theme === 'system' ? getSystemTheme() : theme
+ );
+
+ const setTheme = (newTheme: Theme) => {
+ setThemeState(newTheme);
+ localStorage.setItem(STORAGE_KEY, newTheme);
+ };
+
+ useEffect(() => {
+ const resolved = theme === 'system' ? getSystemTheme() : theme;
+ setResolvedTheme(resolved);
+
+ const root = document.documentElement;
+ if (resolved === 'dark') {
+ root.classList.add('dark');
+ } else {
+ root.classList.remove('dark');
+ }
+ }, [theme]);
+
+ useEffect(() => {
+ if (theme !== 'system') return;
+
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
+ const handler = (e: MediaQueryListEvent) => {
+ setResolvedTheme(e.matches ? 'dark' : 'light');
+ const root = document.documentElement;
+ if (e.matches) {
+ root.classList.add('dark');
+ } else {
+ root.classList.remove('dark');
+ }
+ };
+
+ mediaQuery.addEventListener('change', handler);
+ return () => mediaQuery.removeEventListener('change', handler);
+ }, [theme]);
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useTheme() {
+ const context = useContext(ThemeContext);
+ if (!context) {
+ throw new Error('useTheme must be used within a ThemeProvider');
+ }
+ return context;
+}
diff --git a/src/index.css b/src/index.css
index 6fa97f3..9e22f0a 100644
--- a/src/index.css
+++ b/src/index.css
@@ -10,6 +10,24 @@
--sketch-accent: #4a90d9;
--sketch-line-width: 2px;
--font-sketch: 'Architects Daughter', cursive;
+
+ /* Prototyping tool theme (outer app) */
+ --tool-bg: #fafafa;
+ --tool-surface: #ffffff;
+ --tool-text: #2d2d2d;
+ --tool-text-muted: #666;
+ --tool-border: #2d2d2d;
+ --tool-border-light: #e5e5e5;
+}
+
+/* Dark mode for prototyping tool only */
+.dark {
+ --tool-bg: #1a1a1a;
+ --tool-surface: #2d2d2d;
+ --tool-text: #f5f5f5;
+ --tool-text-muted: #a0a0a0;
+ --tool-border: #4a4a4a;
+ --tool-border-light: #3a3a3a;
}
* {
@@ -18,10 +36,11 @@
body {
font-family: var(--font-sketch);
- background-color: var(--sketch-bg);
- color: var(--sketch-black);
+ background-color: var(--tool-bg);
+ color: var(--tool-text);
margin: 0;
padding: 0;
+ transition: background-color 0.2s ease, color 0.2s ease;
}
/* Sketchy border effect using border-radius variations */
@@ -312,6 +331,18 @@ body {
- Blue = user-provided content only (names, event titles, descriptions, usernames)
=========================================== */
+/* Force phone screen to always use light mode colors */
+.phone-screen {
+ color: var(--sketch-black);
+ background: var(--sketch-white);
+}
+
+/* Dark mode: add outer glow to phone frame for visibility */
+.dark .phone-frame-wrapper {
+ box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.15);
+ border-radius: 44px;
+}
+
/* User content - displayed in blue */
.phone-screen .user-content {
color: var(--sketch-accent);