Upload files to "/"
This commit is contained in:
@@ -0,0 +1,500 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import confetti from 'canvas-confetti';
|
||||
import {
|
||||
Brush,
|
||||
Trash2,
|
||||
Download,
|
||||
Sparkles,
|
||||
Heart,
|
||||
Wind,
|
||||
HelpCircle,
|
||||
Shuffle,
|
||||
Check
|
||||
} from 'lucide-react';
|
||||
import { KINDNESS_CHALLENGES, DISCUSSION_PROMPTS, COLOR_PALETTE } from '../data/storyData';
|
||||
import { soundManager } from '../utils/audio';
|
||||
|
||||
interface ActivityCornerProps {
|
||||
isBedtimeMode: boolean;
|
||||
}
|
||||
|
||||
export const ActivityCorner: React.FC<ActivityCornerProps> = ({ isBedtimeMode }) => {
|
||||
// Canvas State
|
||||
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
||||
const [isDrawing, setIsDrawing] = useState<boolean>(false);
|
||||
const [brushColor, setBrushColor] = useState<string>('#60a5fa');
|
||||
const [brushSize, setBrushSize] = useState<number>(18);
|
||||
const [activeTab, setActiveTab] = useState<'canvas' | 'kindness' | 'breathing' | 'discussion'>('canvas');
|
||||
|
||||
// Kindness State
|
||||
const [challengeIndex, setChallengeIndex] = useState<number>(0);
|
||||
const [completedChallenges, setCompletedChallenges] = useState<number[]>([]);
|
||||
|
||||
// Breathing State
|
||||
const [breathPhase, setBreathPhase] = useState<'Inhale (Gather Soft Mist)' | 'Hold (Glow with Warmth)' | 'Exhale (Rain Gentle Kindness)'>('Inhale (Gather Soft Mist)');
|
||||
const [isBreathingActive, setIsBreathingActive] = useState<boolean>(false);
|
||||
|
||||
// Initialize Canvas background
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
if (canvas) {
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (ctx) {
|
||||
ctx.fillStyle = isBedtimeMode ? '#1e1b4b' : '#f0f9ff';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
}
|
||||
}, [isBedtimeMode, activeTab]);
|
||||
|
||||
// Breathing Cycle loop
|
||||
useEffect(() => {
|
||||
let timer: ReturnType<typeof setInterval>;
|
||||
if (isBreathingActive) {
|
||||
const phases: ('Inhale (Gather Soft Mist)' | 'Hold (Glow with Warmth)' | 'Exhale (Rain Gentle Kindness)')[] = [
|
||||
'Inhale (Gather Soft Mist)',
|
||||
'Hold (Glow with Warmth)',
|
||||
'Exhale (Rain Gentle Kindness)'
|
||||
];
|
||||
let idx = 0;
|
||||
setBreathPhase(phases[0]);
|
||||
|
||||
timer = setInterval(() => {
|
||||
idx = (idx + 1) % phases.length;
|
||||
setBreathPhase(phases[idx]);
|
||||
if (idx === 0) soundManager.playChime();
|
||||
if (idx === 2) soundManager.playRain();
|
||||
}, 4000);
|
||||
}
|
||||
return () => clearInterval(timer);
|
||||
}, [isBreathingActive]);
|
||||
|
||||
// Canvas drawing handlers
|
||||
const startDrawing = (e: React.MouseEvent<HTMLCanvasElement> | React.TouchEvent<HTMLCanvasElement>) => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
setIsDrawing(true);
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
|
||||
const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
|
||||
|
||||
const x = (clientX - rect.left) * (canvas.width / rect.width);
|
||||
const y = (clientY - rect.top) * (canvas.height / rect.height);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, y);
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
ctx.strokeStyle = brushColor;
|
||||
ctx.lineWidth = brushSize;
|
||||
};
|
||||
|
||||
const draw = (e: React.MouseEvent<HTMLCanvasElement> | React.TouchEvent<HTMLCanvasElement>) => {
|
||||
if (!isDrawing) return;
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
|
||||
const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
|
||||
|
||||
const x = (clientX - rect.left) * (canvas.width / rect.width);
|
||||
const y = (clientY - rect.top) * (canvas.height / rect.height);
|
||||
|
||||
ctx.lineTo(x, y);
|
||||
ctx.stroke();
|
||||
};
|
||||
|
||||
const stopDrawing = () => {
|
||||
setIsDrawing(false);
|
||||
};
|
||||
|
||||
const clearCanvas = () => {
|
||||
soundManager.playFlutter();
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
ctx.fillStyle = isBedtimeMode ? '#1e1b4b' : '#f0f9ff';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
};
|
||||
|
||||
const downloadCanvas = () => {
|
||||
soundManager.playPop();
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
const link = document.createElement('a');
|
||||
link.download = 'pip-cloud-creation.png';
|
||||
link.href = canvas.toDataURL();
|
||||
link.click();
|
||||
};
|
||||
|
||||
// Kindness handlers
|
||||
const handleShuffleKindness = () => {
|
||||
soundManager.playPop();
|
||||
setChallengeIndex((prev) => (prev + 1) % KINDNESS_CHALLENGES.length);
|
||||
};
|
||||
|
||||
const handleCompleteChallenge = (id: number) => {
|
||||
soundManager.playStarCelebration();
|
||||
setCompletedChallenges((prev) => [...prev, id]);
|
||||
confetti({
|
||||
particleCount: 50,
|
||||
spread: 60,
|
||||
origin: { y: 0.6 }
|
||||
});
|
||||
};
|
||||
|
||||
const currentChallenge = KINDNESS_CHALLENGES[challengeIndex];
|
||||
const isChallengeDone = completedChallenges.includes(currentChallenge.id);
|
||||
|
||||
return (
|
||||
<div className={`min-h-[calc(100vh-7rem)] py-8 px-4 sm:px-6 lg:px-8 transition-colors duration-300 ${
|
||||
isBedtimeMode
|
||||
? 'bg-gradient-to-b from-indigo-950 via-slate-900 to-indigo-950 text-indigo-100'
|
||||
: 'bg-[#faf6ee] text-stone-900'
|
||||
}`}>
|
||||
<div className="max-w-7xl mx-auto space-y-8">
|
||||
{/* Header */}
|
||||
<div className={`p-6 sm:p-8 rounded-3xl border shadow-sm ${
|
||||
isBedtimeMode
|
||||
? 'bg-slate-900/90 border-indigo-800'
|
||||
: 'bg-white border-amber-200/90'
|
||||
}`}>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="px-3 py-1 rounded-full text-xs font-bold uppercase tracking-wider bg-amber-500/10 text-amber-700 dark:text-amber-400 border border-amber-300/40">
|
||||
Interactive Activity Studio
|
||||
</span>
|
||||
<span className="text-xs text-stone-500 dark:text-indigo-300">
|
||||
Creativity • Kindness • Mindfulness • Family Discussion
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="text-2xl sm:text-3xl font-heading font-bold text-amber-950 dark:text-white">
|
||||
Pip & Caelum's Creative Meadow
|
||||
</h2>
|
||||
<p className="mt-1 text-sm sm:text-base text-stone-600 dark:text-indigo-200 max-w-3xl leading-relaxed">
|
||||
Hands-on interactive experiences designed to extend the storybook's lessons of kindness, self-acceptance, and imaginative problem-solving into real-world family life.
|
||||
</p>
|
||||
|
||||
{/* Activity Category Tabs */}
|
||||
<div className="mt-6 flex flex-wrap gap-2">
|
||||
<button
|
||||
onClick={() => { soundManager.playPop(); setActiveTab('canvas'); }}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-2xl text-xs sm:text-sm font-bold transition-all ${
|
||||
activeTab === 'canvas'
|
||||
? 'bg-amber-500 text-white shadow-md'
|
||||
: 'bg-stone-100 dark:bg-indigo-900 text-stone-700 dark:text-indigo-200 hover:bg-stone-200'
|
||||
}`}
|
||||
>
|
||||
<Brush className="w-4 h-4" />
|
||||
<span>Paint Your Cloud</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { soundManager.playPop(); setActiveTab('kindness'); }}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-2xl text-xs sm:text-sm font-bold transition-all ${
|
||||
activeTab === 'kindness'
|
||||
? 'bg-rose-500 text-white shadow-md'
|
||||
: 'bg-stone-100 dark:bg-indigo-900 text-stone-700 dark:text-indigo-200 hover:bg-stone-200'
|
||||
}`}
|
||||
>
|
||||
<Heart className="w-4 h-4" />
|
||||
<span>Kindness Challenge</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { soundManager.playPop(); setActiveTab('breathing'); }}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-2xl text-xs sm:text-sm font-bold transition-all ${
|
||||
activeTab === 'breathing'
|
||||
? 'bg-sky-500 text-white shadow-md'
|
||||
: 'bg-stone-100 dark:bg-indigo-900 text-stone-700 dark:text-indigo-200 hover:bg-stone-200'
|
||||
}`}
|
||||
>
|
||||
<Wind className="w-4 h-4" />
|
||||
<span>Bedtime Cloud Breathing</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { soundManager.playPop(); setActiveTab('discussion'); }}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-2xl text-xs sm:text-sm font-bold transition-all ${
|
||||
activeTab === 'discussion'
|
||||
? 'bg-purple-500 text-white shadow-md'
|
||||
: 'bg-stone-100 dark:bg-indigo-900 text-stone-700 dark:text-indigo-200 hover:bg-stone-200'
|
||||
}`}
|
||||
>
|
||||
<HelpCircle className="w-4 h-4" />
|
||||
<span>Family Discussion Guide</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* TAB 1: Paint Your Cloud Canvas */}
|
||||
{activeTab === 'canvas' && (
|
||||
<div className={`p-6 sm:p-8 rounded-3xl border shadow-xl ${
|
||||
isBedtimeMode ? 'bg-slate-900 border-indigo-800' : 'bg-white border-amber-200/90'
|
||||
}`}>
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-4">
|
||||
<div>
|
||||
<h3 className="text-xl font-heading font-bold text-stone-900 dark:text-white flex items-center gap-2">
|
||||
<span>🎨</span> Paint Your Whispering Cloud Creature
|
||||
</h3>
|
||||
<p className="text-xs text-stone-500 dark:text-indigo-300">
|
||||
Use Pip's berry pigments to swirl fluffy clouds, raindrops, and flowers!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Canvas Controls */}
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
{/* Palette picker */}
|
||||
<div className="flex items-center gap-1.5 bg-stone-100 dark:bg-indigo-950 p-1.5 rounded-2xl border border-stone-200 dark:border-indigo-800">
|
||||
{COLOR_PALETTE.map((c) => (
|
||||
<button
|
||||
key={c.hex}
|
||||
onClick={() => { soundManager.playPop(); setBrushColor(c.hex); }}
|
||||
style={{ backgroundColor: c.hex }}
|
||||
className={`w-7 h-7 rounded-xl transition-transform ${
|
||||
brushColor === c.hex ? 'scale-125 ring-2 ring-amber-500 shadow-md' : 'hover:scale-110'
|
||||
}`}
|
||||
title={c.name}
|
||||
/>
|
||||
))}
|
||||
{/* White for fluffy cloud */}
|
||||
<button
|
||||
onClick={() => { soundManager.playPop(); setBrushColor('#ffffff'); }}
|
||||
className={`w-7 h-7 rounded-xl bg-white border border-stone-300 transition-transform ${
|
||||
brushColor === '#ffffff' ? 'scale-125 ring-2 ring-amber-500 shadow-md' : 'hover:scale-110'
|
||||
}`}
|
||||
title="Fluffy White Cloud"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Brush Size */}
|
||||
<div className="flex items-center gap-1 bg-stone-100 dark:bg-indigo-950 px-3 py-1.5 rounded-2xl border border-stone-200 dark:border-indigo-800 text-xs font-semibold">
|
||||
<span>Size:</span>
|
||||
<input
|
||||
type="range"
|
||||
min="6"
|
||||
max="45"
|
||||
value={brushSize}
|
||||
onChange={(e) => setBrushSize(parseInt(e.target.value))}
|
||||
className="w-20 accent-amber-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Clear */}
|
||||
<button
|
||||
onClick={clearCanvas}
|
||||
className="p-2 rounded-xl border border-stone-200 dark:border-indigo-800 hover:bg-rose-50 text-stone-600 dark:text-indigo-300 hover:text-rose-600 transition-colors"
|
||||
title="Clear Canvas"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
|
||||
{/* Download */}
|
||||
<button
|
||||
onClick={downloadCanvas}
|
||||
className="flex items-center gap-1 px-3 py-1.5 rounded-xl bg-amber-500 hover:bg-amber-600 text-white font-bold text-xs shadow-sm transition-all"
|
||||
title="Save Picture"
|
||||
>
|
||||
<Download className="w-3.5 h-3.5" />
|
||||
<span>Save</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Drawing Canvas Board */}
|
||||
<div className="relative rounded-2xl overflow-hidden border-2 border-stone-300 dark:border-indigo-800 shadow-inner bg-sky-50 dark:bg-indigo-950 touch-none">
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
width={800}
|
||||
height={480}
|
||||
onMouseDown={startDrawing}
|
||||
onMouseMove={draw}
|
||||
onMouseUp={stopDrawing}
|
||||
onMouseLeave={stopDrawing}
|
||||
onTouchStart={startDrawing}
|
||||
onTouchMove={draw}
|
||||
onTouchEnd={stopDrawing}
|
||||
className="w-full h-[360px] sm:h-[480px] cursor-crosshair block"
|
||||
/>
|
||||
</div>
|
||||
<p className="mt-2 text-center text-xs text-stone-400 dark:text-indigo-400 italic">
|
||||
Tip: Draw a cloud body, then paint little blushing pink cheeks and golden rainbow drops!
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* TAB 2: Kindness Challenge */}
|
||||
{activeTab === 'kindness' && (
|
||||
<div className={`p-6 sm:p-8 rounded-3xl border shadow-xl ${
|
||||
isBedtimeMode ? 'bg-slate-900 border-indigo-800' : 'bg-white border-amber-200/90'
|
||||
}`}>
|
||||
<div className="max-w-2xl mx-auto text-center space-y-6">
|
||||
<div className="w-20 h-20 mx-auto rounded-3xl bg-rose-100 dark:bg-rose-950/60 border border-rose-300 dark:border-rose-700 flex items-center justify-center text-4xl shadow-md">
|
||||
{currentChallenge.icon}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span className="text-xs font-bold uppercase tracking-wider text-rose-600 dark:text-rose-400 px-3 py-1 rounded-full bg-rose-50 dark:bg-rose-950 border border-rose-200 dark:border-rose-800">
|
||||
Daily Mission • {currentChallenge.difficulty}
|
||||
</span>
|
||||
<h3 className="text-2xl sm:text-3xl font-heading font-bold text-stone-900 dark:text-white mt-3">
|
||||
{currentChallenge.title}
|
||||
</h3>
|
||||
<p className="mt-3 text-base sm:text-lg font-reading leading-relaxed text-stone-700 dark:text-stone-200">
|
||||
"{currentChallenge.description}"
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center gap-3 pt-2">
|
||||
<button
|
||||
onClick={handleShuffleKindness}
|
||||
className="flex items-center gap-2 px-4 py-2.5 rounded-2xl bg-stone-100 hover:bg-stone-200 dark:bg-indigo-900 dark:hover:bg-indigo-800 text-stone-800 dark:text-indigo-100 font-heading font-bold text-sm transition-all"
|
||||
>
|
||||
<Shuffle className="w-4 h-4" />
|
||||
<span>Try Another Challenge</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => handleCompleteChallenge(currentChallenge.id)}
|
||||
disabled={isChallengeDone}
|
||||
className={`flex items-center gap-2 px-6 py-2.5 rounded-2xl font-heading font-bold text-sm shadow-md transition-all ${
|
||||
isChallengeDone
|
||||
? 'bg-emerald-600 text-white cursor-default'
|
||||
: 'bg-rose-500 hover:bg-rose-600 text-white'
|
||||
}`}
|
||||
>
|
||||
{isChallengeDone ? (
|
||||
<>
|
||||
<Check className="w-4 h-4" />
|
||||
<span>Completed! ⭐</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Sparkles className="w-4 h-4" />
|
||||
<span>I Did This Today!</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-stone-500 dark:text-indigo-300">
|
||||
You have completed {completedChallenges.length} of {KINDNESS_CHALLENGES.length} kindness challenges!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* TAB 3: Bedtime Cloud Breathing */}
|
||||
{activeTab === 'breathing' && (
|
||||
<div className={`p-6 sm:p-8 rounded-3xl border shadow-xl ${
|
||||
isBedtimeMode ? 'bg-slate-900 border-indigo-800' : 'bg-white border-amber-200/90'
|
||||
}`}>
|
||||
<div className="max-w-xl mx-auto text-center space-y-6">
|
||||
<div>
|
||||
<span className="text-xs font-bold uppercase tracking-wider text-sky-600 dark:text-sky-400 px-3 py-1 rounded-full bg-sky-50 dark:bg-sky-950 border border-sky-200 dark:border-sky-800">
|
||||
Mindfulness & Bedtime Regulation
|
||||
</span>
|
||||
<h3 className="text-2xl sm:text-3xl font-heading font-bold text-stone-900 dark:text-white mt-2">
|
||||
Caelum's Gentle Cloud Breath
|
||||
</h3>
|
||||
<p className="text-sm text-stone-600 dark:text-indigo-200 mt-1">
|
||||
Help little bodies settle for sleep or calm down after high-energy emotions.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Rhythmic Expanding/Contracting Breathing Ring */}
|
||||
<div className="relative py-12 flex items-center justify-center">
|
||||
<div className={`w-56 h-56 sm:w-64 sm:h-64 rounded-full flex items-center justify-center transition-all duration-1000 ${
|
||||
isBreathingActive
|
||||
? breathPhase.startsWith('Inhale')
|
||||
? 'scale-125 bg-gradient-to-tr from-sky-400 via-rose-300 to-amber-200 shadow-2xl shadow-sky-400/40'
|
||||
: breathPhase.startsWith('Hold')
|
||||
? 'scale-125 bg-amber-200/90 ring-8 ring-amber-300/50 shadow-2xl shadow-amber-300/40'
|
||||
: 'scale-90 bg-gradient-to-br from-indigo-400 via-purple-300 to-sky-300 shadow-lg'
|
||||
: 'scale-100 bg-sky-100 dark:bg-indigo-900'
|
||||
}`}>
|
||||
<div className="w-40 h-40 rounded-full bg-white/90 dark:bg-slate-900/90 shadow-inner flex flex-col items-center justify-center p-4 text-center">
|
||||
<span className="text-4xl mb-1">☁️</span>
|
||||
<span className="font-heading font-bold text-xs sm:text-sm text-stone-800 dark:text-white leading-tight">
|
||||
{isBreathingActive ? breathPhase : 'Ready to Breathe'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
onClick={() => {
|
||||
soundManager.playPop();
|
||||
setIsBreathingActive(!isBreathingActive);
|
||||
}}
|
||||
className={`px-6 py-3 rounded-2xl font-heading font-bold text-sm shadow-md transition-all ${
|
||||
isBreathingActive
|
||||
? 'bg-stone-700 text-white hover:bg-stone-800'
|
||||
: 'bg-sky-500 hover:bg-sky-600 text-white'
|
||||
}`}
|
||||
>
|
||||
{isBreathingActive ? 'Pause Exercise' : 'Start 4-4-4 Cloud Breathing'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-stone-500 dark:text-indigo-300">
|
||||
Follow the cloud: Breathe in peace, hold warm love, breathe out gentle rain.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* TAB 4: Family & Educator Discussion Prompts */}
|
||||
{activeTab === 'discussion' && (
|
||||
<div className={`p-6 sm:p-8 rounded-3xl border shadow-xl ${
|
||||
isBedtimeMode ? 'bg-slate-900 border-indigo-800' : 'bg-white border-amber-200/90'
|
||||
}`}>
|
||||
<div className="flex items-center gap-2 mb-6">
|
||||
<HelpCircle className="w-5 h-5 text-purple-600 dark:text-purple-400" />
|
||||
<div>
|
||||
<h3 className="text-xl font-heading font-bold text-stone-900 dark:text-white">
|
||||
Parent, Caregiver & Teacher Circle-Time Guide
|
||||
</h3>
|
||||
<p className="text-xs text-stone-500 dark:text-indigo-300">
|
||||
Open-ended questions to deepen comprehension and emotional connection
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{DISCUSSION_PROMPTS.map((prompt, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="p-5 rounded-2xl border border-stone-200 dark:border-indigo-800 bg-stone-50/70 dark:bg-indigo-950/40 space-y-3"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs font-bold uppercase tracking-wider text-purple-700 dark:text-purple-300 px-2.5 py-0.5 rounded-full bg-purple-100 dark:bg-purple-950 border border-purple-200 dark:border-purple-800">
|
||||
{prompt.category}
|
||||
</span>
|
||||
<span className="text-xs font-bold text-stone-400">
|
||||
Q{i + 1}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h4 className="font-heading font-bold text-base text-stone-900 dark:text-white">
|
||||
"{prompt.question}"
|
||||
</h4>
|
||||
|
||||
<div className="p-3 rounded-xl bg-white/80 dark:bg-slate-900/60 border border-stone-200/70 dark:border-indigo-800/80 text-xs text-stone-700 dark:text-stone-300">
|
||||
<strong className="text-purple-800 dark:text-purple-300 block mb-0.5">
|
||||
Caregiver Guidance:
|
||||
</strong>
|
||||
{prompt.guide}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,319 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Palette,
|
||||
Copy,
|
||||
Check,
|
||||
Sun,
|
||||
Smile,
|
||||
CheckCircle2,
|
||||
Brush
|
||||
} from 'lucide-react';
|
||||
import { COLOR_PALETTE, ART_DIRECTION_GUIDE, CHARACTERS } from '../data/storyData';
|
||||
import { soundManager } from '../utils/audio';
|
||||
|
||||
interface ArtDirectionViewProps {
|
||||
isBedtimeMode: boolean;
|
||||
}
|
||||
|
||||
export const ArtDirectionView: React.FC<ArtDirectionViewProps> = ({ isBedtimeMode }) => {
|
||||
const [copiedHex, setCopiedHex] = useState<string | null>(null);
|
||||
const [customStyle, setCustomStyle] = useState<string>('digital-gouache');
|
||||
const [customSubject, setCustomSubject] = useState<string>('Pip holding his willow brush looking at Caelum');
|
||||
const [customMood, setCustomMood] = useState<string>('Warm golden twilight');
|
||||
|
||||
const handleCopyHex = (hex: string) => {
|
||||
soundManager.playPop();
|
||||
navigator.clipboard.writeText(hex);
|
||||
setCopiedHex(hex);
|
||||
setTimeout(() => setCopiedHex(null), 2000);
|
||||
};
|
||||
|
||||
const generatedCustomPrompt = `Whimsical children's picture book illustration of ${customSubject}. ${customMood}, in ${customStyle === 'digital-gouache' ? 'soft digital gouache and textured watercolor' : customStyle === 'classic-pastel' ? 'classic soft oil pastel with hand-drawn colored pencil contours' : 'dreamy storybook gouache with visible paper tooth grain'}. Color palette featuring ${COLOR_PALETTE.map(c => c.name).slice(0, 3).join(', ')}. Friendly character expressions, gentle magical atmosphere, heartwarming children's literature style.`;
|
||||
|
||||
return (
|
||||
<div className={`min-h-[calc(100vh-7rem)] py-8 px-4 sm:px-6 lg:px-8 transition-colors duration-300 ${
|
||||
isBedtimeMode
|
||||
? 'bg-gradient-to-b from-indigo-950 via-slate-900 to-indigo-950 text-indigo-100'
|
||||
: 'bg-[#faf6ee] text-stone-900'
|
||||
}`}>
|
||||
<div className="max-w-7xl mx-auto space-y-8">
|
||||
{/* Header */}
|
||||
<div className={`p-6 sm:p-8 rounded-3xl border shadow-sm ${
|
||||
isBedtimeMode
|
||||
? 'bg-slate-900/90 border-indigo-800'
|
||||
: 'bg-white border-amber-200/90'
|
||||
}`}>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="px-3 py-1 rounded-full text-xs font-bold uppercase tracking-wider bg-rose-500/10 text-rose-700 dark:text-rose-400 border border-rose-300/40">
|
||||
Art & Visual Direction Bible
|
||||
</span>
|
||||
<span className="text-xs text-stone-500 dark:text-indigo-300">
|
||||
{ART_DIRECTION_GUIDE.styleName}
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="text-2xl sm:text-3xl font-heading font-bold text-amber-950 dark:text-white">
|
||||
Color Palette, Expressions & Artistic Philosophy
|
||||
</h2>
|
||||
<p className="mt-1 text-sm sm:text-base text-stone-600 dark:text-indigo-200 max-w-3xl leading-relaxed">
|
||||
Detailed art notes crafted so that any illustrator or AI visual model can render consistent, heartwarming, award-winning storybook spreads.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Master Color Palette Section */}
|
||||
<div className={`p-6 sm:p-8 rounded-3xl border ${
|
||||
isBedtimeMode ? 'bg-slate-900 border-indigo-800' : 'bg-white border-amber-200/90'
|
||||
}`}>
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<Palette className="w-5 h-5 text-amber-600 dark:text-amber-400" />
|
||||
<h3 className="text-xl font-heading font-bold text-stone-900 dark:text-white">
|
||||
Master Storybook Color Palette
|
||||
</h3>
|
||||
</div>
|
||||
<span className="text-xs font-semibold text-stone-500 dark:text-indigo-300">
|
||||
Click any swatch to copy Hex
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{COLOR_PALETTE.map((swatch) => (
|
||||
<div
|
||||
key={swatch.hex}
|
||||
onClick={() => handleCopyHex(swatch.hex)}
|
||||
className="p-4 rounded-2xl border border-stone-200 dark:border-indigo-800 bg-stone-50 dark:bg-indigo-950/40 hover:shadow-md cursor-pointer transition-all group"
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div
|
||||
style={{ backgroundColor: swatch.hex }}
|
||||
className="w-12 h-12 rounded-2xl shadow-inner border-2 border-white dark:border-slate-800 shrink-0 group-hover:scale-105 transition-transform"
|
||||
/>
|
||||
<div>
|
||||
<h4 className="font-heading font-bold text-sm text-stone-900 dark:text-white">
|
||||
{swatch.name}
|
||||
</h4>
|
||||
<div className="flex items-center gap-1.5 text-xs font-mono font-bold text-amber-700 dark:text-amber-400">
|
||||
<span>{swatch.hex}</span>
|
||||
{copiedHex === swatch.hex ? (
|
||||
<Check className="w-3.5 h-3.5 text-emerald-600" />
|
||||
) : (
|
||||
<Copy className="w-3.5 h-3.5 opacity-40 group-hover:opacity-100" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1 text-xs">
|
||||
<p className="text-stone-700 dark:text-stone-300">
|
||||
<strong className="text-stone-900 dark:text-white">Usage:</strong> {swatch.usage}
|
||||
</p>
|
||||
<p className="text-stone-500 dark:text-indigo-300 italic">
|
||||
<strong className="text-stone-700 dark:text-stone-200 not-italic">Mood:</strong> {swatch.mood}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Character Expression & Anatomy Guidelines */}
|
||||
<div className={`p-6 sm:p-8 rounded-3xl border ${
|
||||
isBedtimeMode ? 'bg-slate-900 border-indigo-800' : 'bg-white border-amber-200/90'
|
||||
}`}>
|
||||
<div className="flex items-center gap-2 mb-6">
|
||||
<Smile className="w-5 h-5 text-amber-600 dark:text-amber-400" />
|
||||
<h3 className="text-xl font-heading font-bold text-stone-900 dark:text-white">
|
||||
Character Expression & Visual Identity Rules
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{CHARACTERS.map((char) => (
|
||||
<div
|
||||
key={char.name}
|
||||
className="p-5 rounded-2xl border border-stone-200 dark:border-indigo-800 bg-stone-50/70 dark:bg-indigo-950/40 space-y-3"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-3xl">{char.avatarEmoji}</span>
|
||||
<div>
|
||||
<h4 className="font-heading font-bold text-base text-stone-900 dark:text-white">
|
||||
{char.name}
|
||||
</h4>
|
||||
<span className="text-xs font-semibold text-stone-500 dark:text-indigo-300">
|
||||
{char.role}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
style={{ backgroundColor: `${char.accentColor}20`, color: char.accentColor, borderColor: `${char.accentColor}40` }}
|
||||
className="text-xs font-bold px-2.5 py-1 rounded-full border"
|
||||
>
|
||||
{char.species}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 text-xs sm:text-sm text-stone-700 dark:text-stone-300">
|
||||
<p>
|
||||
<strong className="text-stone-900 dark:text-white">Key Visual Markers:</strong> {char.appearance}
|
||||
</p>
|
||||
<p>
|
||||
<strong className="text-stone-900 dark:text-white">Signature Gesture / Quirk:</strong> {char.quirk}
|
||||
</p>
|
||||
<p>
|
||||
<strong className="text-stone-900 dark:text-white">Emotional Color Transition:</strong> {char.name === 'Pip' ? 'Quills ripple like grass when inspired; scarf flaps cheerfully.' : char.name === 'Caelum' ? 'Shifts from misty slate-gray (timid) to soft lavender (shy) to radiant sunset gold and rosé (confident).' : char.name === 'Barnaby' ? 'Eyes crinkle warmly into crescents; spectacles catch amber rim-light.' : 'Wings flutter in excited loops; chest puffs with sky-blue pride.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Golden Rules Box */}
|
||||
<div className="mt-6 p-4 rounded-2xl bg-amber-50 dark:bg-indigo-950/60 border border-amber-200 dark:border-indigo-800">
|
||||
<h5 className="font-heading font-bold text-xs uppercase tracking-wider text-amber-800 dark:text-amber-300 mb-2">
|
||||
Core Principles for the Children's Illustrator
|
||||
</h5>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 text-xs text-stone-700 dark:text-stone-300">
|
||||
<div className="flex items-start gap-2">
|
||||
<CheckCircle2 className="w-4 h-4 text-emerald-600 shrink-0 mt-0.5" />
|
||||
<span><strong>No Harsh Black Outlines:</strong> Use deep sepia, indigo, or forest bark for line work.</span>
|
||||
</div>
|
||||
<div className="flex items-start gap-2">
|
||||
<CheckCircle2 className="w-4 h-4 text-emerald-600 shrink-0 mt-0.5" />
|
||||
<span><strong>Tactile Paper Texture:</strong> Retain visible rag-paper tooth and watercolor bloom edges.</span>
|
||||
</div>
|
||||
<div className="flex items-start gap-2">
|
||||
<CheckCircle2 className="w-4 h-4 text-emerald-600 shrink-0 mt-0.5" />
|
||||
<span><strong>Gentle Scale & Proximity:</strong> Caelum hovers close to the grass to invite cuddles.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Lighting Keys Across The Story */}
|
||||
<div className={`p-6 sm:p-8 rounded-3xl border ${
|
||||
isBedtimeMode ? 'bg-slate-900 border-indigo-800' : 'bg-white border-amber-200/90'
|
||||
}`}>
|
||||
<div className="flex items-center gap-2 mb-6">
|
||||
<Sun className="w-5 h-5 text-amber-600 dark:text-amber-400" />
|
||||
<h3 className="text-xl font-heading font-bold text-stone-900 dark:text-white">
|
||||
Lighting & Atmospheric Keys Across Acts
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
{ART_DIRECTION_GUIDE.lightingKeys.map((key, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="p-5 rounded-2xl border border-stone-200 dark:border-indigo-800 bg-stone-50 dark:bg-indigo-950/40 space-y-3"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs font-bold uppercase tracking-wider px-2.5 py-0.5 rounded-full bg-amber-100 dark:bg-indigo-900 text-amber-800 dark:text-amber-300 border border-amber-200 dark:border-indigo-700">
|
||||
{key.scene}
|
||||
</span>
|
||||
<span className="text-[11px] font-mono text-stone-500 dark:text-indigo-300">
|
||||
{key.colorTemp}
|
||||
</span>
|
||||
</div>
|
||||
<h4 className="font-heading font-bold text-base text-stone-900 dark:text-white">
|
||||
{key.mood}
|
||||
</h4>
|
||||
<p className="text-xs sm:text-sm text-stone-600 dark:text-indigo-200 font-reading leading-relaxed">
|
||||
{key.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Interactive Storybook Prompt Builder */}
|
||||
<div className={`p-6 sm:p-8 rounded-3xl border ${
|
||||
isBedtimeMode ? 'bg-slate-900 border-indigo-800' : 'bg-white border-amber-200/90'
|
||||
}`}>
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Brush className="w-5 h-5 text-rose-500" />
|
||||
<div>
|
||||
<h3 className="text-xl font-heading font-bold text-stone-900 dark:text-white">
|
||||
Interactive Illustration Prompt Builder
|
||||
</h3>
|
||||
<p className="text-xs text-stone-500 dark:text-indigo-300">
|
||||
Generate new children's book scene prompts matching this art bible
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
|
||||
<div>
|
||||
<label className="block text-xs font-bold uppercase text-stone-600 dark:text-indigo-300 mb-1">
|
||||
Subject & Action
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={customSubject}
|
||||
onChange={(e) => setCustomSubject(e.target.value)}
|
||||
className="w-full px-3 py-2 text-sm rounded-xl border border-stone-300 dark:border-indigo-700 bg-stone-50 dark:bg-indigo-950 text-stone-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs font-bold uppercase text-stone-600 dark:text-indigo-300 mb-1">
|
||||
Lighting & Time of Day
|
||||
</label>
|
||||
<select
|
||||
value={customMood}
|
||||
onChange={(e) => setCustomMood(e.target.value)}
|
||||
className="w-full px-3 py-2 text-sm rounded-xl border border-stone-300 dark:border-indigo-700 bg-stone-50 dark:bg-indigo-950 text-stone-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||
>
|
||||
<option value="Morning sunbeams streaming through dewy oak leaves">Morning Dewy Sunbeams</option>
|
||||
<option value="Warm golden twilight with pastel rainbow mist">Warm Golden Twilight</option>
|
||||
<option value="Velvet indigo bedtime sky with twinkling stars and lanterns">Velvet Bedtime Stars</option>
|
||||
<option value="Misty lavender afternoon breeze over purple clover">Misty Lavender Afternoon</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs font-bold uppercase text-stone-600 dark:text-indigo-300 mb-1">
|
||||
Illustration Medium
|
||||
</label>
|
||||
<select
|
||||
value={customStyle}
|
||||
onChange={(e) => setCustomStyle(e.target.value)}
|
||||
className="w-full px-3 py-2 text-sm rounded-xl border border-stone-300 dark:border-indigo-700 bg-stone-50 dark:bg-indigo-950 text-stone-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||
>
|
||||
<option value="digital-gouache">Digital Gouache & Soft Watercolor</option>
|
||||
<option value="classic-pastel">Classic Soft Pastel & Colored Pencil</option>
|
||||
<option value="storybook-ragpaper">Storybook Gouache with Rag-paper Grain</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 rounded-2xl bg-amber-50 dark:bg-indigo-950/70 border border-amber-200 dark:border-indigo-800 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs font-heading font-bold uppercase text-amber-800 dark:text-amber-300">
|
||||
Generated Artist / AI Prompt:
|
||||
</span>
|
||||
<button
|
||||
onClick={() => handleCopyHex(generatedCustomPrompt)}
|
||||
className="flex items-center gap-1 text-xs font-bold text-amber-700 dark:text-amber-300 hover:text-amber-900 bg-white dark:bg-indigo-900 px-2.5 py-1 rounded-lg border border-amber-200 dark:border-indigo-700"
|
||||
>
|
||||
{copiedHex === generatedCustomPrompt ? (
|
||||
<>
|
||||
<Check className="w-3.5 h-3.5 text-emerald-600" />
|
||||
<span>Copied!</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Copy className="w-3.5 h-3.5" />
|
||||
<span>Copy Generated Prompt</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs sm:text-sm font-reading italic text-stone-800 dark:text-stone-200 leading-relaxed">
|
||||
"{generatedCustomPrompt}"
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
BookOpen,
|
||||
Film,
|
||||
Palette,
|
||||
Users,
|
||||
Sparkles,
|
||||
Printer,
|
||||
Volume2,
|
||||
VolumeX,
|
||||
Moon,
|
||||
Sun,
|
||||
Layers,
|
||||
BookMarked
|
||||
} from 'lucide-react';
|
||||
import { soundManager } from '../utils/audio';
|
||||
|
||||
export type NavTab =
|
||||
| 'reader'
|
||||
| 'storyboard'
|
||||
| 'art'
|
||||
| 'characters'
|
||||
| 'adaptability'
|
||||
| 'publishing'
|
||||
| 'activities';
|
||||
|
||||
export type AgeGroup = 'toddler' | 'pictureBook' | 'earlyReader';
|
||||
|
||||
interface NavbarProps {
|
||||
activeTab: NavTab;
|
||||
setActiveTab: (tab: NavTab) => void;
|
||||
ageGroup: AgeGroup;
|
||||
setAgeGroup: (age: AgeGroup) => void;
|
||||
isBedtimeMode: boolean;
|
||||
setIsBedtimeMode: (val: boolean) => void;
|
||||
soundEnabled: boolean;
|
||||
setSoundEnabled: (val: boolean) => void;
|
||||
onOpenPitchDeck: () => void;
|
||||
}
|
||||
|
||||
export const Navbar: React.FC<NavbarProps> = ({
|
||||
activeTab,
|
||||
setActiveTab,
|
||||
ageGroup,
|
||||
setAgeGroup,
|
||||
isBedtimeMode,
|
||||
setIsBedtimeMode,
|
||||
soundEnabled,
|
||||
setSoundEnabled,
|
||||
onOpenPitchDeck,
|
||||
}) => {
|
||||
const tabs = [
|
||||
{ id: 'reader' as NavTab, label: 'Story Reader', icon: BookOpen, badge: 'Live Book' },
|
||||
{ id: 'storyboard' as NavTab, label: 'Storyboard', icon: Film, badge: '5 Scenes' },
|
||||
{ id: 'art' as NavTab, label: 'Art Direction', icon: Palette, badge: 'Palette & Style' },
|
||||
{ id: 'characters' as NavTab, label: 'Characters', icon: Users, badge: 'Cast' },
|
||||
{ id: 'adaptability' as NavTab, label: 'Age Editions', icon: Layers, badge: '3 Ages' },
|
||||
{ id: 'publishing' as NavTab, label: 'Format & Pitch', icon: BookMarked, badge: 'Specs' },
|
||||
{ id: 'activities' as NavTab, label: 'Activity Corner', icon: Sparkles, badge: 'Creative' },
|
||||
];
|
||||
|
||||
const handleTabClick = (tabId: NavTab) => {
|
||||
soundManager.playPop();
|
||||
setActiveTab(tabId);
|
||||
};
|
||||
|
||||
const handleSoundToggle = () => {
|
||||
const newState = !soundEnabled;
|
||||
setSoundEnabled(newState);
|
||||
soundManager.enabled = newState;
|
||||
if (newState) soundManager.playChime();
|
||||
};
|
||||
|
||||
const handleBedtimeToggle = () => {
|
||||
soundManager.playHarp();
|
||||
setIsBedtimeMode(!isBedtimeMode);
|
||||
};
|
||||
|
||||
return (
|
||||
<header className={`sticky top-0 z-50 border-b transition-colors duration-300 backdrop-blur-md ${
|
||||
isBedtimeMode
|
||||
? 'bg-indigo-950/90 border-indigo-800 text-indigo-100 shadow-lg shadow-indigo-950/40'
|
||||
: 'bg-[#fffdfa]/95 border-amber-200/70 text-amber-950 shadow-sm'
|
||||
}`}>
|
||||
{/* Top Banner with Brand and Controls */}
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-2.5 flex flex-wrap items-center justify-between gap-3">
|
||||
{/* Brand */}
|
||||
<div
|
||||
onClick={() => handleTabClick('reader')}
|
||||
className="flex items-center gap-2.5 cursor-pointer group"
|
||||
>
|
||||
<div className="w-10 h-10 rounded-2xl bg-gradient-to-tr from-amber-400 via-rose-300 to-sky-300 flex items-center justify-center text-xl shadow-md group-hover:scale-105 transition-transform">
|
||||
🦔
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-heading font-bold text-lg sm:text-xl tracking-tight leading-none text-amber-900 dark:text-amber-100 group-hover:text-amber-600 transition-colors">
|
||||
Pip & the Whispering Cloud
|
||||
</span>
|
||||
<span className="hidden md:inline-flex text-[11px] font-semibold px-2 py-0.5 rounded-full bg-amber-100 text-amber-800 border border-amber-300/60">
|
||||
Storybook Studio
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-amber-800/70 dark:text-indigo-300">
|
||||
Heartwarming Children's Story & Concept Architecture
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Global Toolbar Controls */}
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
{/* Age Adaptability Selector */}
|
||||
<div className="flex items-center bg-amber-100/70 dark:bg-indigo-900/80 p-1 rounded-xl border border-amber-200/80 dark:border-indigo-700 text-xs font-semibold">
|
||||
<span className="px-2 text-amber-800/80 dark:text-indigo-300 hidden sm:inline">Edition:</span>
|
||||
<button
|
||||
onClick={() => { soundManager.playPop(); setAgeGroup('toddler'); }}
|
||||
className={`px-2.5 py-1 rounded-lg transition-all ${
|
||||
ageGroup === 'toddler'
|
||||
? 'bg-amber-500 text-white shadow-sm font-bold'
|
||||
: 'text-amber-900 dark:text-indigo-200 hover:bg-amber-200/50 dark:hover:bg-indigo-800'
|
||||
}`}
|
||||
title="Ages 1-3: Sensory, Onomatopoeia & Touch"
|
||||
>
|
||||
Toddler (1–3)
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { soundManager.playPop(); setAgeGroup('pictureBook'); }}
|
||||
className={`px-2.5 py-1 rounded-lg transition-all ${
|
||||
ageGroup === 'pictureBook'
|
||||
? 'bg-amber-500 text-white shadow-sm font-bold'
|
||||
: 'text-amber-900 dark:text-indigo-200 hover:bg-amber-200/50 dark:hover:bg-indigo-800'
|
||||
}`}
|
||||
title="Ages 4-7: Lyrical Picture Book & Read-Aloud"
|
||||
>
|
||||
Picture Book (4–7)
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { soundManager.playPop(); setAgeGroup('earlyReader'); }}
|
||||
className={`px-2.5 py-1 rounded-lg transition-all ${
|
||||
ageGroup === 'earlyReader'
|
||||
? 'bg-amber-500 text-white shadow-sm font-bold'
|
||||
: 'text-amber-900 dark:text-indigo-200 hover:bg-amber-200/50 dark:hover:bg-indigo-800'
|
||||
}`}
|
||||
title="Ages 7-9: Illustrated Chapters & Vocabulary"
|
||||
>
|
||||
Early Reader (7–9)
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Bedtime / Day Toggle */}
|
||||
<button
|
||||
onClick={handleBedtimeToggle}
|
||||
className={`p-2 rounded-xl border transition-all flex items-center gap-1.5 text-xs font-medium ${
|
||||
isBedtimeMode
|
||||
? 'bg-indigo-900 border-indigo-700 text-amber-300 shadow-inner'
|
||||
: 'bg-amber-50 border-amber-200 text-amber-800 hover:bg-amber-100'
|
||||
}`}
|
||||
title={isBedtimeMode ? "Switch to Daytime Mode" : "Switch to Bedtime Lullaby Mode"}
|
||||
>
|
||||
{isBedtimeMode ? <Moon className="w-4 h-4 fill-amber-300" /> : <Sun className="w-4 h-4 text-amber-500" />}
|
||||
<span className="hidden md:inline">{isBedtimeMode ? 'Bedtime Mode' : 'Day Mode'}</span>
|
||||
</button>
|
||||
|
||||
{/* Sound FX Toggle */}
|
||||
<button
|
||||
onClick={handleSoundToggle}
|
||||
className={`p-2 rounded-xl border transition-all ${
|
||||
soundEnabled
|
||||
? 'bg-emerald-50 border-emerald-200 text-emerald-700 dark:bg-emerald-950 dark:border-emerald-700 dark:text-emerald-300'
|
||||
: 'bg-stone-100 border-stone-200 text-stone-400 dark:bg-stone-900 dark:border-stone-800'
|
||||
}`}
|
||||
title={soundEnabled ? "Mute Sound Effects" : "Enable Sound Effects"}
|
||||
>
|
||||
{soundEnabled ? <Volume2 className="w-4 h-4" /> : <VolumeX className="w-4 h-4" />}
|
||||
</button>
|
||||
|
||||
{/* Pitch Deck / Presentation Button */}
|
||||
<button
|
||||
onClick={() => { soundManager.playHarp(); onOpenPitchDeck(); }}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-gradient-to-r from-amber-600 to-rose-600 hover:from-amber-700 hover:to-rose-700 text-white font-medium text-xs shadow-sm hover:shadow transition-all"
|
||||
>
|
||||
<Printer className="w-3.5 h-3.5" />
|
||||
<span className="hidden sm:inline">Pitch Deck</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Navigation Sub-Tabs */}
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 overflow-x-auto scrollbar-none">
|
||||
<nav className="flex space-x-1 sm:space-x-2 py-1.5 min-w-max">
|
||||
{tabs.map((tab) => {
|
||||
const Icon = tab.icon;
|
||||
const isActive = activeTab === tab.id;
|
||||
return (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => handleTabClick(tab.id)}
|
||||
className={`flex items-center gap-2 px-3.5 py-1.5 rounded-xl text-xs sm:text-sm font-semibold transition-all ${
|
||||
isActive
|
||||
? isBedtimeMode
|
||||
? 'bg-indigo-800 text-amber-200 shadow-sm border border-indigo-600'
|
||||
: 'bg-amber-500 text-white shadow-sm border border-amber-600/30'
|
||||
: isBedtimeMode
|
||||
? 'text-indigo-200 hover:bg-indigo-900/60 hover:text-white'
|
||||
: 'text-amber-900/80 hover:bg-amber-100/70 hover:text-amber-950'
|
||||
}`}
|
||||
>
|
||||
<Icon className={`w-4 h-4 ${isActive ? 'scale-110' : ''}`} />
|
||||
<span>{tab.label}</span>
|
||||
{tab.badge && (
|
||||
<span className={`text-[10px] px-1.5 py-0.2 rounded-full font-bold ${
|
||||
isActive
|
||||
? 'bg-white/20 text-white'
|
||||
: isBedtimeMode
|
||||
? 'bg-indigo-900 text-indigo-300'
|
||||
: 'bg-amber-200/60 text-amber-800'
|
||||
}`}>
|
||||
{tab.badge}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,217 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
X,
|
||||
Printer,
|
||||
Award,
|
||||
Heart,
|
||||
BookOpen,
|
||||
Palette,
|
||||
Smile
|
||||
} from 'lucide-react';
|
||||
import { BOOK_METADATA, CHARACTERS, COLOR_PALETTE } from '../data/storyData';
|
||||
import { soundManager } from '../utils/audio';
|
||||
|
||||
interface PitchDeckModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const PitchDeckModal: React.FC<PitchDeckModalProps> = ({ isOpen, onClose }) => {
|
||||
if (!isOpen) return null;
|
||||
|
||||
const handlePrint = () => {
|
||||
soundManager.playPop();
|
||||
window.print();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 overflow-y-auto bg-black/70 backdrop-blur-sm flex items-center justify-center p-4 sm:p-6">
|
||||
<div className="relative w-full max-w-4xl bg-white text-stone-900 rounded-3xl shadow-2xl overflow-hidden border border-amber-200 print:border-none print:shadow-none print:max-w-full">
|
||||
{/* Modal Top Bar */}
|
||||
<div className="sticky top-0 z-10 bg-[#fffdfa] border-b border-amber-200/80 px-6 py-4 flex items-center justify-between print:hidden">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="p-1.5 rounded-xl bg-amber-500 text-white text-lg">🦔</span>
|
||||
<div>
|
||||
<h3 className="font-heading font-bold text-base text-stone-900">
|
||||
Children's Picture Book Concept & Editorial Pitch Deck
|
||||
</h3>
|
||||
<p className="text-xs text-stone-500">
|
||||
Ready for literary agent submission, publisher pitch, or team review
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={handlePrint}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-amber-100 hover:bg-amber-200 text-amber-900 font-heading font-bold text-xs transition-colors"
|
||||
>
|
||||
<Printer className="w-3.5 h-3.5" />
|
||||
<span>Print / Save PDF</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { soundManager.playPop(); onClose(); }}
|
||||
className="p-1.5 rounded-xl hover:bg-stone-100 text-stone-500 transition-colors"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Printable Pitch Content */}
|
||||
<div className="p-6 sm:p-10 space-y-8 bg-[#fffdfa] print:p-0">
|
||||
{/* Header Banner */}
|
||||
<div className="text-center space-y-3 border-b border-amber-200/70 pb-6">
|
||||
<span className="inline-block text-xs font-bold uppercase tracking-widest px-3 py-1 rounded-full bg-amber-100 text-amber-800 border border-amber-300">
|
||||
Children's Picture Book Concept Pitch
|
||||
</span>
|
||||
<h1 className="text-3xl sm:text-4xl font-heading font-bold text-amber-950">
|
||||
{BOOK_METADATA.title}
|
||||
</h1>
|
||||
<p className="text-lg font-heading text-amber-700 italic">
|
||||
"{BOOK_METADATA.subtitle}"
|
||||
</p>
|
||||
<p className="max-w-2xl mx-auto text-sm sm:text-base font-reading leading-relaxed text-stone-700">
|
||||
{BOOK_METADATA.logline}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center gap-3 pt-2 text-xs font-semibold text-stone-600">
|
||||
<span className="px-3 py-1 rounded-lg bg-stone-100 border border-stone-200">
|
||||
🎯 Target Age: {BOOK_METADATA.targetAge}
|
||||
</span>
|
||||
<span className="px-3 py-1 rounded-lg bg-stone-100 border border-stone-200">
|
||||
📄 Format: {BOOK_METADATA.pageCount}
|
||||
</span>
|
||||
<span className="px-3 py-1 rounded-lg bg-stone-100 border border-stone-200">
|
||||
📝 Word Count: ~{BOOK_METADATA.wordCountPictureBook}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Core Themes */}
|
||||
<div className="space-y-3">
|
||||
<h3 className="font-heading font-bold text-lg text-stone-900 flex items-center gap-2">
|
||||
<Heart className="w-5 h-5 text-rose-500" />
|
||||
Core Thematic Pillars
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
{BOOK_METADATA.primaryThemes.map((th, i) => (
|
||||
<div key={i} className="p-3.5 rounded-2xl bg-amber-50/60 border border-amber-200/80">
|
||||
<h4 className="font-heading font-bold text-sm text-amber-900">
|
||||
{th.title}
|
||||
</h4>
|
||||
<p className="text-xs font-reading text-stone-600 mt-0.5">
|
||||
{th.desc}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Storyboard Synopsis: Beginning, Middle, End */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="font-heading font-bold text-lg text-stone-900 flex items-center gap-2">
|
||||
<BookOpen className="w-5 h-5 text-amber-600" />
|
||||
Storyboard Narrative Arc (3-Act Structure)
|
||||
</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="p-4 rounded-2xl bg-stone-50 border border-stone-200">
|
||||
<span className="text-xs font-bold uppercase text-amber-700 tracking-wider">
|
||||
Act I: The Beginning (Scenes 1–2)
|
||||
</span>
|
||||
<h4 className="font-heading font-bold text-sm text-stone-900 mt-1">
|
||||
The Hollow Oak Workshop & Meeting Caelum on Clover Hill
|
||||
</h4>
|
||||
<p className="text-xs font-reading text-stone-600 mt-1 leading-relaxed">
|
||||
Pip, a gentle hedgehog who captures nature's pigments in acorn cups, follows a curious whisper of mist to Clover Hill. He meets Caelum, an adorable baby cloud drooping close to the clover, ashamed because he can only make quiet murmurs instead of grand storm thunder. Pip validates Caelum's feelings and promises they will find his purpose together.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="p-4 rounded-2xl bg-stone-50 border border-stone-200">
|
||||
<span className="text-xs font-bold uppercase text-sky-700 tracking-wider">
|
||||
Act II: The Middle & Climax (Scenes 3–4)
|
||||
</span>
|
||||
<h4 className="font-heading font-bold text-sm text-stone-900 mt-1">
|
||||
The Meadow Art Trials & The Rainbow Blossom Shower
|
||||
</h4>
|
||||
<p className="text-xs font-reading text-stone-600 mt-1 leading-relaxed">
|
||||
Joined by wise old Barnaby Bear and spirited Squeak the bluebird, Pip tries creative thinking. When force and noise don't work, Pip paints colorful berry pigments directly onto the cloud's vapor. Caelum giggles, the gray knot of worry dissolves, and he releases not a scary downpour, but a warm, shimmering rainbow shower that revives the thirsty wildflowers.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="p-4 rounded-2xl bg-stone-50 border border-stone-200">
|
||||
<span className="text-xs font-bold uppercase text-purple-700 tracking-wider">
|
||||
Act III: The End & Resolution (Scene 5)
|
||||
</span>
|
||||
<h4 className="font-heading font-bold text-sm text-stone-900 mt-1">
|
||||
Twilight Constellations & Sweet Dreams
|
||||
</h4>
|
||||
<p className="text-xs font-reading text-stone-600 mt-1 leading-relaxed">
|
||||
Night descends in velvet indigo. Pip drinks honey chamomile tea on his treehouse porch while Caelum curves gently around the crescent moon as a silver scarf of mist. Safe, accepted, and deeply loved, both friends settle into peaceful sleep, knowing true kindness doesn't need to roar.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Character Ensemble */}
|
||||
<div className="space-y-3">
|
||||
<h3 className="font-heading font-bold text-lg text-stone-900 flex items-center gap-2">
|
||||
<Smile className="w-5 h-5 text-amber-600" />
|
||||
Core Character Cast
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 text-xs">
|
||||
{CHARACTERS.map((char) => (
|
||||
<div key={char.name} className="p-3.5 rounded-2xl bg-stone-50 border border-stone-200 space-y-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xl">{char.avatarEmoji}</span>
|
||||
<strong className="font-heading font-bold text-stone-900 text-sm">{char.name}</strong>
|
||||
<span className="text-stone-500">({char.role})</span>
|
||||
</div>
|
||||
<p className="text-stone-600 leading-snug">
|
||||
{char.tagline}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Art Direction & Color Palette Summary */}
|
||||
<div className="space-y-3">
|
||||
<h3 className="font-heading font-bold text-lg text-stone-900 flex items-center gap-2">
|
||||
<Palette className="w-5 h-5 text-rose-500" />
|
||||
Visual Art Direction & Color Palette
|
||||
</h3>
|
||||
<p className="text-xs font-reading text-stone-600">
|
||||
Medium: Soft digital gouache and watercolor texture with visible paper tooth grain, friendly rounded contours, and glowing golden-hour lighting keys.
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2 pt-1">
|
||||
{COLOR_PALETTE.map((c) => (
|
||||
<div key={c.hex} className="flex items-center gap-1.5 px-2.5 py-1 rounded-xl bg-stone-100 border border-stone-200 text-xs">
|
||||
<span style={{ backgroundColor: c.hex }} className="w-3.5 h-3.5 rounded-full border border-stone-300" />
|
||||
<span className="font-medium text-stone-800">{c.name}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Publishing Recommendation */}
|
||||
<div className="p-5 rounded-2xl bg-amber-500/10 border-2 border-amber-400 space-y-2">
|
||||
<h4 className="font-heading font-bold text-base text-amber-950 flex items-center gap-2">
|
||||
<Award className="w-5 h-5 text-amber-600" />
|
||||
Publisher Recommendation & Commercial Strategy
|
||||
</h4>
|
||||
<p className="text-xs sm:text-sm font-reading text-stone-800 leading-relaxed">
|
||||
Recommended as a <strong>32-page premium hardcover picture book (10x10 inches)</strong> with spot-UV varnish on rain droplets, paired with a companion <strong>Interactive Audio App edition</strong> for accessible tablet read-alouds. High franchise potential for early reader spin-offs (<em>The Bramblewood Chronicles</em>) and sensory board book editions for toddlers.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Footer sign-off */}
|
||||
<div className="text-center pt-4 border-t border-amber-200/60 text-xs text-stone-500">
|
||||
Concept created with thoughtful children's literature pacing, Social-Emotional Learning integration, and warm humanized storytelling.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,398 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
MapPin,
|
||||
Users,
|
||||
MessageCircle,
|
||||
Activity,
|
||||
Eye,
|
||||
Copy,
|
||||
Check,
|
||||
Sparkles,
|
||||
Camera
|
||||
} from 'lucide-react';
|
||||
import { SCENES } from '../data/storyData';
|
||||
import { soundManager } from '../utils/audio';
|
||||
|
||||
interface StoryboardViewProps {
|
||||
isBedtimeMode: boolean;
|
||||
selectedSceneId?: number;
|
||||
}
|
||||
|
||||
export const StoryboardView: React.FC<StoryboardViewProps> = ({
|
||||
isBedtimeMode,
|
||||
selectedSceneId,
|
||||
}) => {
|
||||
const [filterAct, setFilterAct] = useState<'all' | 'beginning' | 'middle' | 'end'>('all');
|
||||
const [activeSceneId, setActiveSceneId] = useState<number>(selectedSceneId || 1);
|
||||
const [copiedText, setCopiedText] = useState<string | null>(null);
|
||||
|
||||
const filteredScenes = filterAct === 'all'
|
||||
? SCENES
|
||||
: SCENES.filter(s => s.act === filterAct);
|
||||
|
||||
const currentScene = SCENES.find(s => s.id === activeSceneId) || SCENES[0];
|
||||
|
||||
const handleCopyPrompt = (text: string) => {
|
||||
soundManager.playPop();
|
||||
navigator.clipboard.writeText(text);
|
||||
setCopiedText(text);
|
||||
setTimeout(() => setCopiedText(null), 2500);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`min-h-[calc(100vh-7rem)] py-8 px-4 sm:px-6 lg:px-8 transition-colors duration-300 ${
|
||||
isBedtimeMode
|
||||
? 'bg-gradient-to-b from-indigo-950 via-slate-900 to-indigo-950 text-indigo-100'
|
||||
: 'bg-[#faf6ee] text-stone-900'
|
||||
}`}>
|
||||
<div className="max-w-7xl mx-auto space-y-8">
|
||||
{/* Header & Concept Pitch */}
|
||||
<div className={`p-6 sm:p-8 rounded-3xl border shadow-sm ${
|
||||
isBedtimeMode
|
||||
? 'bg-slate-900/90 border-indigo-800'
|
||||
: 'bg-white border-amber-200/90'
|
||||
}`}>
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="px-3 py-1 rounded-full text-xs font-bold uppercase tracking-wider bg-amber-500/10 text-amber-700 dark:text-amber-400 border border-amber-300/40">
|
||||
Visual Narrative Architecture
|
||||
</span>
|
||||
<span className="text-xs text-stone-500 dark:text-indigo-300">
|
||||
5 Key Visual Beats (Act I, II, III)
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="text-2xl sm:text-3xl font-heading font-bold text-amber-950 dark:text-white">
|
||||
Complete Storyboard & Scene Bible
|
||||
</h2>
|
||||
<p className="mt-1 text-sm sm:text-base text-stone-600 dark:text-indigo-200 max-w-3xl leading-relaxed">
|
||||
Every frame pairs narrative intent with rigorous art direction: environmental settings, natural dialogue, physical actions, camera perspectives, and exact color palettes.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Act Filter Tabs */}
|
||||
<div className="flex items-center gap-1.5 bg-stone-100 dark:bg-indigo-950 p-1.5 rounded-2xl border border-stone-200 dark:border-indigo-800 shrink-0">
|
||||
<button
|
||||
onClick={() => { soundManager.playPop(); setFilterAct('all'); }}
|
||||
className={`px-3 py-1.5 rounded-xl text-xs font-bold transition-all ${
|
||||
filterAct === 'all'
|
||||
? 'bg-amber-500 text-white shadow-sm'
|
||||
: 'text-stone-600 dark:text-indigo-300 hover:bg-stone-200/60 dark:hover:bg-indigo-900'
|
||||
}`}
|
||||
>
|
||||
All (5)
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { soundManager.playPop(); setFilterAct('beginning'); }}
|
||||
className={`px-3 py-1.5 rounded-xl text-xs font-bold transition-all ${
|
||||
filterAct === 'beginning'
|
||||
? 'bg-amber-500 text-white shadow-sm'
|
||||
: 'text-stone-600 dark:text-indigo-300 hover:bg-stone-200/60 dark:hover:bg-indigo-900'
|
||||
}`}
|
||||
>
|
||||
Act I: Beginning
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { soundManager.playPop(); setFilterAct('middle'); }}
|
||||
className={`px-3 py-1.5 rounded-xl text-xs font-bold transition-all ${
|
||||
filterAct === 'middle'
|
||||
? 'bg-amber-500 text-white shadow-sm'
|
||||
: 'text-stone-600 dark:text-indigo-300 hover:bg-stone-200/60 dark:hover:bg-indigo-900'
|
||||
}`}
|
||||
>
|
||||
Act II: Middle
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { soundManager.playPop(); setFilterAct('end'); }}
|
||||
className={`px-3 py-1.5 rounded-xl text-xs font-bold transition-all ${
|
||||
filterAct === 'end'
|
||||
? 'bg-amber-500 text-white shadow-sm'
|
||||
: 'text-stone-600 dark:text-indigo-300 hover:bg-stone-200/60 dark:hover:bg-indigo-900'
|
||||
}`}
|
||||
>
|
||||
Act III: End
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Storyboard Navigation Thumbnails */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-5 gap-3">
|
||||
{filteredScenes.map((sc) => {
|
||||
const isSelected = activeSceneId === sc.id;
|
||||
return (
|
||||
<div
|
||||
key={sc.id}
|
||||
onClick={() => {
|
||||
soundManager.playPop();
|
||||
setActiveSceneId(sc.id);
|
||||
}}
|
||||
className={`p-3 rounded-2xl cursor-pointer border transition-all text-left group ${
|
||||
isSelected
|
||||
? 'bg-amber-100/90 dark:bg-indigo-900 border-amber-400 dark:border-indigo-600 shadow-md ring-2 ring-amber-400/40'
|
||||
: 'bg-white dark:bg-slate-900/80 border-stone-200 dark:border-indigo-900 hover:bg-amber-50/60 dark:hover:bg-indigo-900/50'
|
||||
}`}
|
||||
>
|
||||
<div className="relative aspect-[16/10] rounded-xl overflow-hidden mb-2.5">
|
||||
<img
|
||||
src={sc.image}
|
||||
alt={sc.title}
|
||||
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||
/>
|
||||
<span className="absolute top-1.5 left-1.5 bg-black/60 text-white text-[10px] font-bold px-2 py-0.5 rounded-md backdrop-blur-sm">
|
||||
Scene {sc.id}
|
||||
</span>
|
||||
<span className={`absolute bottom-1.5 right-1.5 text-[9px] font-bold uppercase px-1.5 py-0.2 rounded-md ${
|
||||
sc.act === 'beginning' ? 'bg-amber-500 text-white' : sc.act === 'middle' ? 'bg-sky-500 text-white' : 'bg-purple-500 text-white'
|
||||
}`}>
|
||||
{sc.act}
|
||||
</span>
|
||||
</div>
|
||||
<h4 className="font-heading font-bold text-xs line-clamp-1 text-stone-900 dark:text-white">
|
||||
{sc.title}
|
||||
</h4>
|
||||
<p className="text-[11px] text-stone-500 dark:text-indigo-300 line-clamp-1">
|
||||
{sc.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Detailed Scene Storyboard Inspector */}
|
||||
<div className={`rounded-3xl border overflow-hidden shadow-xl ${
|
||||
isBedtimeMode
|
||||
? 'bg-slate-900 border-indigo-800'
|
||||
: 'bg-white border-amber-200/90'
|
||||
}`}>
|
||||
{/* Top Bar with Scene Header */}
|
||||
<div className="p-6 bg-gradient-to-r from-amber-500/10 via-rose-500/10 to-transparent border-b border-stone-200 dark:border-indigo-800 flex flex-wrap items-center justify-between gap-4">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className={`px-2.5 py-0.5 rounded-full text-xs font-bold uppercase tracking-wider ${
|
||||
currentScene.act === 'beginning'
|
||||
? 'bg-amber-100 text-amber-800 border border-amber-300'
|
||||
: currentScene.act === 'middle'
|
||||
? 'bg-sky-100 text-sky-800 border border-sky-300'
|
||||
: 'bg-purple-100 text-purple-800 border border-purple-300'
|
||||
}`}>
|
||||
Act: {currentScene.act.toUpperCase()}
|
||||
</span>
|
||||
<span className="text-xs font-semibold text-stone-500 dark:text-indigo-300">
|
||||
Scene #{currentScene.id} of {SCENES.length}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="text-2xl sm:text-3xl font-heading font-bold text-stone-900 dark:text-white">
|
||||
{currentScene.title}
|
||||
</h3>
|
||||
<p className="text-sm font-semibold text-amber-700 dark:text-amber-400">
|
||||
"{currentScene.subtitle}"
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-stone-500 dark:text-indigo-300">Key Palette:</span>
|
||||
<div className="flex items-center gap-1.5">
|
||||
{currentScene.artNotes.palette.map((color, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
style={{ backgroundColor: color }}
|
||||
className="w-6 h-6 rounded-full border-2 border-white dark:border-slate-800 shadow-sm"
|
||||
title={color}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 divide-y lg:divide-y-0 lg:divide-x divide-stone-200 dark:divide-indigo-800">
|
||||
{/* Visual Frame & Illustration Prompt Column (5 cols) */}
|
||||
<div className="lg:col-span-5 p-6 space-y-6">
|
||||
{/* Scene Frame Rendering */}
|
||||
<div className="relative rounded-2xl overflow-hidden shadow-lg border border-stone-200 dark:border-indigo-700 aspect-[4/3] group">
|
||||
<img
|
||||
src={currentScene.image}
|
||||
alt={currentScene.imageAlt}
|
||||
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent pointer-events-none" />
|
||||
<span className="absolute bottom-3 left-3 text-white text-xs font-bold drop-shadow-md">
|
||||
Frame #{currentScene.id} Preview
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Illustration Prompt Box */}
|
||||
<div className="p-4 rounded-2xl bg-amber-50/70 dark:bg-indigo-950/60 border border-amber-200 dark:border-indigo-800 space-y-2.5">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs font-heading font-bold uppercase tracking-wider text-amber-800 dark:text-amber-300 flex items-center gap-1.5">
|
||||
<Sparkles className="w-3.5 h-3.5 text-amber-600 dark:text-amber-400" />
|
||||
Illustration Prompt (AI / Artist Bible)
|
||||
</span>
|
||||
<button
|
||||
onClick={() => handleCopyPrompt(currentScene.illustrationPrompt)}
|
||||
className="flex items-center gap-1 text-[11px] font-bold text-amber-700 dark:text-amber-300 hover:text-amber-900 bg-white/80 dark:bg-indigo-900/80 px-2 py-1 rounded-lg border border-amber-200 dark:border-indigo-700 transition-colors"
|
||||
>
|
||||
{copiedText === currentScene.illustrationPrompt ? (
|
||||
<>
|
||||
<Check className="w-3 h-3 text-emerald-600" />
|
||||
<span>Copied!</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Copy className="w-3 h-3" />
|
||||
<span>Copy Prompt</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs sm:text-sm font-reading leading-relaxed italic text-stone-700 dark:text-stone-300">
|
||||
"{currentScene.illustrationPrompt}"
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Art Direction Specs */}
|
||||
<div className="space-y-3">
|
||||
<h5 className="font-heading font-bold text-xs uppercase tracking-wider text-stone-500 dark:text-indigo-400 flex items-center gap-1.5">
|
||||
<Camera className="w-3.5 h-3.5" />
|
||||
Visual Direction Parameters
|
||||
</h5>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 text-xs">
|
||||
<div className="p-3 rounded-xl bg-stone-50 dark:bg-indigo-950/40 border border-stone-200 dark:border-indigo-800">
|
||||
<span className="font-bold text-amber-800 dark:text-amber-300 block mb-1">
|
||||
Camera Perspective:
|
||||
</span>
|
||||
<p className="text-stone-700 dark:text-stone-300 leading-snug">
|
||||
{currentScene.artNotes.cameraAngle}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="p-3 rounded-xl bg-stone-50 dark:bg-indigo-950/40 border border-stone-200 dark:border-indigo-800">
|
||||
<span className="font-bold text-amber-800 dark:text-amber-300 block mb-1">
|
||||
Lighting & Atmosphere:
|
||||
</span>
|
||||
<p className="text-stone-700 dark:text-stone-300 leading-snug">
|
||||
{currentScene.artNotes.lighting}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="p-3 rounded-xl bg-stone-50 dark:bg-indigo-950/40 border border-stone-200 dark:border-indigo-800">
|
||||
<span className="font-bold text-amber-800 dark:text-amber-300 block mb-1">
|
||||
Focal Point:
|
||||
</span>
|
||||
<p className="text-stone-700 dark:text-stone-300 leading-snug">
|
||||
{currentScene.artNotes.focalPoint}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="p-3 rounded-xl bg-stone-50 dark:bg-indigo-950/40 border border-stone-200 dark:border-indigo-800">
|
||||
<span className="font-bold text-amber-800 dark:text-amber-300 block mb-1">
|
||||
Character Expressions:
|
||||
</span>
|
||||
<p className="text-stone-700 dark:text-stone-300 leading-snug">
|
||||
{currentScene.artNotes.expressions}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Script & Stage Direction Storyboard Column (7 cols) */}
|
||||
<div className="lg:col-span-7 p-6 space-y-6">
|
||||
{/* Setting */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2 text-xs font-heading font-bold uppercase tracking-wider text-stone-500 dark:text-indigo-400">
|
||||
<MapPin className="w-4 h-4 text-rose-500" />
|
||||
<span>Scene Setting & Environment</span>
|
||||
</div>
|
||||
<div className="p-4 rounded-2xl bg-stone-50 dark:bg-indigo-950/40 border border-stone-200 dark:border-indigo-800 text-sm leading-relaxed text-stone-800 dark:text-stone-200 font-reading">
|
||||
{currentScene.setting}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Characters Present */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2 text-xs font-heading font-bold uppercase tracking-wider text-stone-500 dark:text-indigo-400">
|
||||
<Users className="w-4 h-4 text-sky-500" />
|
||||
<span>Characters on Stage</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{currentScene.characters.map((charName) => (
|
||||
<span
|
||||
key={charName}
|
||||
className="px-3 py-1 rounded-xl bg-amber-100 dark:bg-indigo-900 border border-amber-200 dark:border-indigo-700 text-xs font-bold text-amber-900 dark:text-amber-300 flex items-center gap-1.5"
|
||||
>
|
||||
<span>{charName === 'Pip' ? '🦔' : charName === 'Caelum' ? '☁️' : charName === 'Barnaby' ? '🐻' : '🐦'}</span>
|
||||
<span>{charName}</span>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Script & Dialogue */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 text-xs font-heading font-bold uppercase tracking-wider text-stone-500 dark:text-indigo-400">
|
||||
<MessageCircle className="w-4 h-4 text-amber-500" />
|
||||
<span>Spoken Dialogue & Tone</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{currentScene.dialogue.map((dlg, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="p-4 rounded-2xl bg-amber-50/50 dark:bg-indigo-950/60 border border-amber-200/60 dark:border-indigo-800 flex flex-col sm:flex-row sm:items-baseline justify-between gap-2"
|
||||
>
|
||||
<div className="space-y-1">
|
||||
<span className="font-heading font-bold text-sm text-amber-900 dark:text-amber-300">
|
||||
{dlg.speaker}:
|
||||
</span>
|
||||
<p className="text-base font-reading text-stone-900 dark:text-white italic">
|
||||
"{dlg.text}"
|
||||
</p>
|
||||
</div>
|
||||
<span className="shrink-0 text-xs font-semibold px-2.5 py-1 rounded-lg bg-white dark:bg-indigo-900 text-stone-600 dark:text-indigo-300 border border-stone-200 dark:border-indigo-700 self-start sm:self-auto">
|
||||
🎭 Tone: {dlg.tone}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Key Physical Actions & Choreography */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 text-xs font-heading font-bold uppercase tracking-wider text-stone-500 dark:text-indigo-400">
|
||||
<Activity className="w-4 h-4 text-emerald-500" />
|
||||
<span>Key Physical Actions (Beat by Beat)</span>
|
||||
</div>
|
||||
|
||||
<div className="p-4 rounded-2xl bg-stone-50 dark:bg-indigo-950/40 border border-stone-200 dark:border-indigo-800 space-y-2.5">
|
||||
{currentScene.keyActions.map((action, i) => (
|
||||
<div key={i} className="flex items-start gap-3 text-sm text-stone-800 dark:text-stone-200">
|
||||
<span className="w-5 h-5 rounded-full bg-amber-200 dark:bg-indigo-800 text-amber-900 dark:text-amber-200 font-bold text-xs flex items-center justify-center shrink-0 mt-0.5">
|
||||
{i + 1}
|
||||
</span>
|
||||
<p className="font-reading leading-relaxed">
|
||||
{action}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Director's & Illustrator's Intent */}
|
||||
<div className="p-4 rounded-2xl bg-gradient-to-r from-amber-500/10 via-rose-500/10 to-indigo-500/10 border border-amber-300/60 dark:border-indigo-700 space-y-1.5">
|
||||
<div className="flex items-center gap-2 text-xs font-heading font-bold uppercase tracking-wider text-amber-900 dark:text-amber-300">
|
||||
<Eye className="w-4 h-4 text-amber-600 dark:text-amber-400" />
|
||||
<span>Director's Core Intention & Emotional Beat</span>
|
||||
</div>
|
||||
<p className="text-xs sm:text-sm font-reading text-stone-800 dark:text-stone-200 leading-relaxed italic">
|
||||
"{currentScene.directorNotes}"
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user