feat: create stunning premium UI with advanced design elements

- Add animated gradient backgrounds with floating particles
- Implement advanced glass morphism with enhanced blur effects
- Add Google Fonts Inter for professional typography
- Create sophisticated button animations with ripple effects
- Add progress indicator dots showing current step
- Implement card hover effects with smooth transforms
- Add animated gradient text with shimmer effects
- Create premium loading spinners with dual rings
- Add character counter to textarea
- Implement numbered badges for roles and segments
- Add gradient backgrounds to all cards and sections
- Create pulsing glow animations for important elements
- Add emoji icons throughout for visual appeal
- Implement responsive grid layouts for settings
- Add detailed subtitles and descriptions to sections
- Create success celebration with bouncing animation
- Add shadow effects with color-matched glows
- Implement smooth transitions and cubic-bezier easing
- Add visual hierarchy with varied font weights
- Create immersive, modern user experience
This commit is contained in:
2025-12-09 13:20:20 +00:00
parent 706ada866c
commit fb294252c7
7 changed files with 365 additions and 186 deletions

View File

@@ -3,6 +3,8 @@
width: 100%; width: 100%;
margin: 0 auto; margin: 0 auto;
padding: 0; padding: 0;
position: relative;
z-index: 1;
} }
/* Ensure touch-friendly interactions */ /* Ensure touch-friendly interactions */
@@ -11,22 +13,41 @@
} }
/* Smooth animations */ /* Smooth animations */
* {
transition: all 0.2s ease-in-out;
}
button, input, select, textarea { button, input, select, textarea {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Advanced button hover effects */
button:not(:disabled) {
position: relative;
overflow: hidden;
}
button:not(:disabled)::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: 50%;
background: rgba(255, 255, 255, 0.3);
transform: translate(-50%, -50%);
transition: width 0.6s, height 0.6s;
}
button:not(:disabled):hover::before {
width: 300px;
height: 300px;
} }
/* Button hover effects */
button:not(:disabled):hover { button:not(:disabled):hover {
transform: translateY(-2px); transform: translateY(-4px) scale(1.02);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15); box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
} }
button:not(:disabled):active { button:not(:disabled):active {
transform: translateY(0); transform: translateY(-2px) scale(1.01);
} }
/* Custom range slider styling for better mobile support */ /* Custom range slider styling for better mobile support */

View File

@@ -158,24 +158,41 @@ function App() {
const allRolesAssigned = roles.length > 0 && roles.every(role => voiceAssignments.has(role.id)) const allRolesAssigned = roles.length > 0 && roles.every(role => voiceAssignments.has(role.id))
return ( return (
<div className="min-h-screen"> <div className="min-h-screen relative">
<div className="container mx-auto py-4 px-4 sm:py-8 sm:px-6 lg:px-8"> <div className="container mx-auto py-6 px-4 sm:py-12 sm:px-6 lg:px-8 relative z-10">
{/* Header with gradient text and icon */} {/* Header with gradient text and icon */}
<div className="text-center mb-6 sm:mb-10"> <div className="text-center mb-8 sm:mb-14">
<div className="inline-block mb-4"> <div className="inline-block mb-6 animate-bounce">
<div className="w-16 h-16 sm:w-20 sm:h-20 mx-auto bg-white rounded-2xl shadow-glow flex items-center justify-center"> <div className="w-20 h-20 sm:w-24 sm:h-24 mx-auto bg-white rounded-3xl shadow-glow-strong flex items-center justify-center relative overflow-hidden">
<span className="text-3xl sm:text-4xl">🎧</span> <div className="absolute inset-0 bg-gradient-to-br from-purple-100 to-indigo-100 opacity-50"></div>
<span className="text-4xl sm:text-5xl relative z-10">🎧</span>
</div> </div>
</div> </div>
<h1 className="text-3xl sm:text-4xl md:text-5xl font-bold text-white mb-2 drop-shadow-lg"> <h1 className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-extrabold text-white mb-4 drop-shadow-2xl tracking-tight">
Text to Audiobook Text to Audiobook
</h1> </h1>
<p className="text-white/90 text-sm sm:text-base">Transform your stories into immersive audio experiences</p> <p className="text-white/95 text-base sm:text-lg md:text-xl font-medium max-w-2xl mx-auto leading-relaxed">
Transform your stories into immersive audio experiences with AI-powered voices
</p>
{/* Progress indicator */}
<div className="mt-8 flex justify-center items-center gap-2 sm:gap-3">
<div className={`w-2 h-2 sm:w-3 sm:h-3 rounded-full transition-all duration-300 ${phase === 'input' ? 'bg-white scale-125' : 'bg-white/40'}`}></div>
<div className={`w-2 h-2 sm:w-3 sm:h-3 rounded-full transition-all duration-300 ${phase === 'assignment' ? 'bg-white scale-125' : 'bg-white/40'}`}></div>
<div className={`w-2 h-2 sm:w-3 sm:h-3 rounded-full transition-all duration-300 ${phase === 'review' ? 'bg-white scale-125' : 'bg-white/40'}`}></div>
<div className={`w-2 h-2 sm:w-3 sm:h-3 rounded-full transition-all duration-300 ${phase === 'generated' ? 'bg-white scale-125' : 'bg-white/40'}`}></div>
</div>
</div> </div>
{error && ( {error && (
<div className="max-w-4xl mx-auto mb-4 p-3 sm:p-4 bg-red-50 border-2 border-red-300 text-red-700 rounded-xl shadow-lg text-sm sm:text-base"> <div className="max-w-4xl mx-auto mb-6 p-4 sm:p-5 glass-effect border-2 border-red-400 text-red-700 rounded-2xl shadow-glow text-sm sm:text-base animate-pulse">
<span className="font-semibold">⚠️ Error:</span> {error} <div className="flex items-start gap-3">
<span className="text-2xl">⚠️</span>
<div>
<p className="font-bold text-lg mb-1">Error</p>
<p>{error}</p>
</div>
</div>
</div> </div>
)} )}
@@ -192,8 +209,11 @@ function App() {
{phase === 'assignment' && ( {phase === 'assignment' && (
<> <>
{isLongText && ( {isLongText && (
<div className="max-w-4xl mx-auto mb-4 p-3 sm:p-4 bg-yellow-100 border border-yellow-400 text-yellow-800 rounded text-sm sm:text-base"> <div className="max-w-4xl mx-auto mb-6 p-4 sm:p-5 glass-effect border-2 border-yellow-400 text-yellow-900 rounded-2xl shadow-glow text-sm sm:text-base">
Generation may take several minutes for very long content. <div className="flex items-center gap-3">
<span className="text-2xl">⏱️</span>
<p className="font-semibold">Generation may take several minutes for very long content.</p>
</div>
</div> </div>
)} )}
{console.log('Rendering VoiceAssignmentSection with roles:', roles, 'allRolesAssigned:', allRolesAssigned)} {console.log('Rendering VoiceAssignmentSection with roles:', roles, 'allRolesAssigned:', allRolesAssigned)}
@@ -216,8 +236,11 @@ function App() {
{phase === 'review' && ( {phase === 'review' && (
<> <>
{isLongText && ( {isLongText && (
<div className="max-w-4xl mx-auto mb-4 p-3 sm:p-4 bg-yellow-100 border border-yellow-400 text-yellow-800 rounded text-sm sm:text-base"> <div className="max-w-4xl mx-auto mb-6 p-4 sm:p-5 glass-effect border-2 border-yellow-400 text-yellow-900 rounded-2xl shadow-glow text-sm sm:text-base">
Generation may take several minutes for very long content. <div className="flex items-center gap-3">
<span className="text-2xl">⏱️</span>
<p className="font-semibold">Generation may take several minutes for very long content.</p>
</div>
</div> </div>
)} )}
<ScriptReviewSection <ScriptReviewSection
@@ -239,19 +262,31 @@ function App() {
)} )}
{isGenerating && isLongText && ( {isGenerating && isLongText && (
<div className="max-w-4xl mx-auto mt-4 p-3 sm:p-4 glass-effect border-2 border-blue-300 text-blue-800 rounded-xl shadow-glow text-center text-sm sm:text-base"> <div className="max-w-4xl mx-auto mt-6 p-5 sm:p-6 glass-effect border-2 border-blue-400 text-blue-900 rounded-2xl shadow-glow-strong text-center text-sm sm:text-base">
<div className="flex items-center justify-center gap-3"> <div className="flex flex-col items-center justify-center gap-4">
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-blue-800"></div> <div className="relative">
<span>Generation may take several minutes for very long content. Please be patient...</span> <div className="animate-spin rounded-full h-12 w-12 border-4 border-blue-200"></div>
<div className="animate-spin rounded-full h-12 w-12 border-4 border-blue-600 border-t-transparent absolute top-0 left-0"></div>
</div>
<div>
<p className="font-bold text-lg mb-1">Generating Your Audiobook</p>
<p className="text-sm">This may take several minutes for very long content. Please be patient...</p>
</div>
</div> </div>
</div> </div>
)} )}
{isGeneratingScript && ( {isGeneratingScript && (
<div className="max-w-4xl mx-auto mt-4 p-3 sm:p-4 glass-effect border-2 border-blue-300 text-blue-800 rounded-xl shadow-glow text-center text-sm sm:text-base"> <div className="max-w-4xl mx-auto mt-6 p-5 sm:p-6 glass-effect border-2 border-purple-400 text-purple-900 rounded-2xl shadow-glow-strong text-center text-sm sm:text-base">
<div className="flex items-center justify-center gap-3"> <div className="flex flex-col items-center justify-center gap-4">
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-blue-800"></div> <div className="relative">
<span>Generating script preview... This may take a moment.</span> <div className="animate-spin rounded-full h-12 w-12 border-4 border-purple-200"></div>
<div className="animate-spin rounded-full h-12 w-12 border-4 border-purple-600 border-t-transparent absolute top-0 left-0"></div>
</div>
<div>
<p className="font-bold text-lg mb-1">Generating Script Preview</p>
<p className="text-sm">AI is analyzing your text and assigning speakers...</p>
</div>
</div> </div>
</div> </div>
)} )}

View File

@@ -8,38 +8,41 @@ export default function AudioPlayerSection({
onDownload onDownload
}: AudioPlayerSectionProps) { }: AudioPlayerSectionProps) {
return ( return (
<div className="w-full max-w-4xl mx-auto p-4 sm:p-6 glass-effect rounded-2xl shadow-glow"> <div className="w-full max-w-4xl mx-auto p-6 sm:p-8 glass-effect rounded-3xl shadow-glow-strong card-hover">
{/* Success Header */} {/* Success Header */}
<div className="text-center mb-6"> <div className="text-center mb-8">
<div className="inline-block mb-4"> <div className="inline-block mb-6">
<div className="w-16 h-16 sm:w-20 sm:h-20 mx-auto bg-gradient-to-br from-green-400 to-emerald-600 rounded-2xl shadow-lg flex items-center justify-center animate-bounce"> <div className="w-24 h-24 sm:w-28 sm:h-28 mx-auto bg-gradient-to-br from-green-400 via-emerald-500 to-teal-600 rounded-3xl shadow-2xl flex items-center justify-center animate-bounce shadow-green-500/50">
<span className="text-3xl sm:text-4xl">🎉</span> <span className="text-5xl sm:text-6xl">🎉</span>
</div> </div>
</div> </div>
<h2 className="text-2xl sm:text-3xl font-bold gradient-text mb-2"> <h2 className="text-3xl sm:text-4xl md:text-5xl font-extrabold gradient-text-animated mb-3">
Your Audiobook is Ready! Your Audiobook is Ready!
</h2> </h2>
<p className="text-gray-600 text-sm sm:text-base"> <p className="text-gray-700 text-base sm:text-lg font-medium">
Listen below or download to enjoy offline Listen below or download to enjoy offline
</p> </p>
</div> </div>
{/* Audio Player Card */} {/* Audio Player Card */}
<div className="bg-gradient-to-br from-purple-50 to-indigo-50 rounded-2xl shadow-lg p-5 sm:p-8 mb-6 border-2 border-purple-200"> <div className="bg-gradient-to-br from-purple-50 via-indigo-50 to-pink-50 rounded-3xl shadow-2xl p-6 sm:p-10 mb-8 border-2 border-purple-300 hover:shadow-3xl transition-all">
<div className="flex items-center gap-3 mb-4"> <div className="flex items-center gap-4 mb-6">
<div className="w-10 h-10 bg-gradient-to-br from-purple-500 to-indigo-600 rounded-xl flex items-center justify-center shadow-md"> <div className="w-14 h-14 bg-gradient-to-br from-purple-500 via-purple-600 to-indigo-600 rounded-2xl flex items-center justify-center shadow-lg shadow-purple-500/50">
<span className="text-xl">🎧</span> <span className="text-2xl">🎧</span>
</div>
<div>
<h3 className="text-xl sm:text-2xl font-extrabold text-gray-800">Listen Now</h3>
<p className="text-sm text-gray-600">Your personalized audiobook</p>
</div> </div>
<h3 className="text-lg sm:text-xl font-bold text-gray-800">Listen Now</h3>
</div> </div>
<audio <audio
controls controls
src={audioUrl} src={audioUrl}
className="w-full min-h-[44px] rounded-xl shadow-md" className="w-full min-h-[54px] rounded-2xl shadow-xl"
preload="metadata" preload="metadata"
style={{ style={{
filter: 'drop-shadow(0 4px 6px rgba(0, 0, 0, 0.1))' filter: 'drop-shadow(0 8px 16px rgba(0, 0, 0, 0.15))'
}} }}
> >
Your browser does not support the audio element. Your browser does not support the audio element.
@@ -47,22 +50,26 @@ export default function AudioPlayerSection({
</div> </div>
{/* Download Button */} {/* Download Button */}
<div className="flex justify-center"> <div className="flex justify-center mb-6">
<button <button
onClick={onDownload} onClick={onDownload}
className="w-full sm:w-auto px-8 py-4 min-h-[44px] bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 text-white font-bold rounded-xl transition-all cursor-pointer touch-manipulation shadow-lg text-base sm:text-lg" className="px-12 py-6 min-h-[60px] bg-gradient-to-r from-green-500 via-emerald-600 to-teal-600 hover:from-green-600 hover:via-emerald-700 hover:to-teal-700 text-white font-extrabold rounded-2xl transition-all cursor-pointer touch-manipulation shadow-2xl text-lg sm:text-xl shadow-green-500/50 relative overflow-hidden"
> >
<span className="flex items-center justify-center gap-3"> <span className="flex items-center justify-center gap-4 relative z-10">
<span className="text-xl">⬇️</span> <span className="text-2xl">⬇️</span>
Download Audiobook Download Audiobook
</span> </span>
</button> </button>
</div> </div>
{/* Success Message */} {/* Success Message */}
<div className="mt-6 p-4 bg-gradient-to-r from-green-50 to-emerald-50 border-2 border-green-200 text-green-800 rounded-xl shadow-sm text-center text-sm sm:text-base"> <div className="p-6 bg-gradient-to-r from-green-50 via-emerald-50 to-teal-50 border-2 border-green-300 text-green-900 rounded-2xl shadow-lg text-center text-sm sm:text-base">
<p className="font-semibold"> <div className="flex items-center justify-center gap-3 mb-2">
✅ Your audiobook has been successfully generated! <span className="text-3xl">✅</span>
<p className="font-extrabold text-lg">Success!</p>
</div>
<p className="font-medium">
Your audiobook has been successfully generated with AI-powered voices!
</p> </p>
</div> </div>
</div> </div>

View File

@@ -22,40 +22,52 @@ export default function ScriptReviewSection({
}; };
return ( return (
<div className="w-full max-w-4xl mx-auto p-4 sm:p-6 glass-effect rounded-2xl shadow-glow"> <div className="w-full max-w-4xl mx-auto p-6 sm:p-8 glass-effect rounded-3xl shadow-glow card-hover">
<div className="flex items-center gap-3 mb-4"> <div className="flex items-center gap-4 mb-6">
<div className="w-10 h-10 bg-gradient-to-br from-purple-500 to-indigo-600 rounded-xl flex items-center justify-center shadow-lg"> <div className="w-14 h-14 bg-gradient-to-br from-purple-500 via-purple-600 to-indigo-600 rounded-2xl flex items-center justify-center shadow-lg shadow-purple-500/50">
<span className="text-xl">📋</span> <span className="text-2xl">📋</span>
</div>
<div>
<h2 className="text-2xl sm:text-3xl font-extrabold gradient-text-animated">Review Script</h2>
<p className="text-gray-600 text-sm mt-1">Fine-tune speaker assignments</p>
</div> </div>
<h2 className="text-xl sm:text-2xl font-bold gradient-text">Review Script</h2>
</div> </div>
<p className="text-sm sm:text-base text-gray-700 mb-6 bg-purple-50 p-3 rounded-xl border-2 border-purple-100"> <div className="text-sm sm:text-base text-gray-700 mb-8 bg-gradient-to-r from-purple-50 to-indigo-50 p-5 rounded-2xl border-2 border-purple-200 shadow-inner">
Review the AI-generated script and adjust speaker assignments as needed. <div className="flex items-start gap-3">
</p> <span className="text-xl">💡</span>
<p className="font-medium">Review the AI-generated script and adjust speaker assignments as needed.</p>
</div>
</div>
{/* Script Segments */} {/* Script Segments */}
<div className="space-y-4 sm:space-y-5 mb-6 sm:mb-8"> <div className="space-y-5 mb-8">
{segments.map((segment, index) => ( {segments.map((segment, index) => (
<div <div
key={segment.id} key={segment.id}
className="border-2 border-purple-200 rounded-xl p-4 sm:p-5 bg-white shadow-md hover:shadow-lg transition-all" className="border-2 border-purple-300 rounded-2xl p-5 sm:p-6 bg-gradient-to-br from-white to-purple-50/30 shadow-lg hover:shadow-2xl transition-all card-hover"
style={{ animationDelay: `${index * 0.05}s` }}
> >
{/* Segment Header */} {/* Segment Header */}
<div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4 mb-3"> <div className="flex flex-col sm:flex-row sm:items-center gap-3 sm:gap-5 mb-4">
<span className="inline-flex items-center justify-center w-20 px-2 py-1 text-xs sm:text-sm font-bold text-purple-700 bg-purple-100 rounded-lg"> <div className="flex items-center gap-3">
#{index + 1} <div className="w-12 h-12 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-xl flex items-center justify-center text-white font-extrabold shadow-md text-lg">
</span> {index + 1}
</div>
<span className="text-xs font-bold text-purple-600 bg-purple-100 px-3 py-1 rounded-lg">
SEGMENT
</span>
</div>
{/* Speaker Dropdown */} {/* Speaker Dropdown */}
<div className="flex items-center gap-2 flex-1"> <div className="flex items-center gap-3 flex-1">
<label className="text-xs sm:text-sm font-semibold text-gray-700 whitespace-nowrap"> <label className="text-sm font-bold text-gray-700 whitespace-nowrap flex items-center gap-2">
🎤 Speaker: <span className="text-lg">🎤</span> Speaker:
</label> </label>
<select <select
value={segment.speaker} value={segment.speaker}
onChange={(e) => handleSpeakerChange(segment.id, e.target.value)} onChange={(e) => handleSpeakerChange(segment.id, e.target.value)}
className="flex-1 p-3 min-h-[44px] border-2 border-purple-200 rounded-xl focus:border-purple-500 focus:ring-4 focus:ring-purple-200 focus:outline-none text-sm sm:text-base touch-manipulation bg-white shadow-sm" className="flex-1 p-4 min-h-[52px] border-2 border-purple-300 rounded-xl focus:border-purple-600 focus:ring-4 focus:ring-purple-300 focus:outline-none text-sm sm:text-base touch-manipulation bg-white shadow-sm font-medium"
disabled={isGenerating} disabled={isGenerating}
> >
{roles.map((role) => ( {roles.map((role) => (
@@ -68,7 +80,7 @@ export default function ScriptReviewSection({
</div> </div>
{/* Segment Text */} {/* Segment Text */}
<div className="w-full min-h-[80px] p-4 border-2 border-purple-100 rounded-xl bg-gradient-to-br from-purple-50 to-indigo-50 text-sm sm:text-base whitespace-pre-wrap shadow-inner"> <div className="w-full min-h-[100px] p-5 border-2 border-indigo-200 rounded-xl bg-gradient-to-br from-indigo-50 via-purple-50 to-pink-50 text-sm sm:text-base whitespace-pre-wrap shadow-inner leading-relaxed">
{segment.text} {segment.text}
</div> </div>
</div> </div>
@@ -76,36 +88,40 @@ export default function ScriptReviewSection({
</div> </div>
{/* Action Buttons */} {/* Action Buttons */}
<div className="flex flex-col sm:flex-row gap-3 sm:gap-4 justify-center"> <div className="flex flex-col sm:flex-row gap-4 sm:gap-5 justify-center">
<button <button
onClick={onSkipReview} onClick={onSkipReview}
disabled={isGenerating} disabled={isGenerating}
className={`w-full sm:w-auto px-6 py-4 min-h-[44px] rounded-xl font-semibold transition-all touch-manipulation shadow-lg ${ className={`px-8 py-5 min-h-[56px] rounded-2xl font-bold transition-all touch-manipulation shadow-2xl text-lg relative overflow-hidden ${
isGenerating isGenerating
? 'bg-gray-300 text-gray-500 cursor-not-allowed opacity-60' ? 'bg-gray-300 text-gray-500 cursor-not-allowed opacity-60'
: 'bg-gradient-to-r from-gray-500 to-gray-600 text-white hover:from-gray-600 hover:to-gray-700 cursor-pointer' : 'bg-gradient-to-r from-gray-500 via-gray-600 to-gray-700 text-white hover:from-gray-600 hover:via-gray-700 hover:to-gray-800 cursor-pointer shadow-gray-500/50'
}`} }`}
> >
Skip Review <span className="relative z-10">Skip Review</span>
</button> </button>
<button <button
onClick={onGenerate} onClick={onGenerate}
disabled={isGenerating} disabled={isGenerating}
className={`w-full sm:w-auto px-8 py-4 min-h-[44px] rounded-xl font-semibold text-white transition-all touch-manipulation shadow-lg ${ className={`px-10 py-5 min-h-[56px] rounded-2xl font-bold text-white text-lg transition-all touch-manipulation shadow-2xl relative overflow-hidden ${
isGenerating isGenerating
? 'bg-gray-400 cursor-not-allowed opacity-60' ? 'bg-gray-400 cursor-not-allowed opacity-60'
: 'bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 cursor-pointer' : 'bg-gradient-to-r from-green-500 via-emerald-600 to-teal-600 hover:from-green-600 hover:via-emerald-700 hover:to-teal-700 cursor-pointer shadow-green-500/50'
}`} }`}
> >
{isGenerating ? ( {isGenerating ? (
<span className="flex items-center justify-center gap-2"> <span className="flex items-center justify-center gap-3 relative z-10">
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-white"></div> <div className="relative">
<div className="animate-spin rounded-full h-6 w-6 border-3 border-white/30"></div>
<div className="animate-spin rounded-full h-6 w-6 border-3 border-white border-t-transparent absolute top-0 left-0"></div>
</div>
Generating... Generating...
</span> </span>
) : ( ) : (
<span className="flex items-center justify-center gap-2"> <span className="flex items-center justify-center gap-3 relative z-10">
✨ Generate Audiobook <span className="text-xl">✨</span>
Generate Audiobook
</span> </span>
)} )}
</button> </button>
@@ -113,12 +129,12 @@ export default function ScriptReviewSection({
{/* Info Message */} {/* Info Message */}
{!isGenerating && ( {!isGenerating && (
<div className="mt-6 p-4 bg-gradient-to-r from-blue-50 to-indigo-50 border-2 border-blue-200 text-blue-800 rounded-xl shadow-sm text-sm sm:text-base"> <div className="mt-8 p-6 bg-gradient-to-r from-blue-50 via-indigo-50 to-purple-50 border-2 border-blue-300 text-blue-900 rounded-2xl shadow-lg text-sm sm:text-base">
<p className="font-bold mb-2 flex items-center gap-2"> <p className="font-extrabold mb-3 flex items-center gap-3 text-lg">
<span className="text-lg">💡</span> Pro Tip: <span className="text-2xl">💡</span> Pro Tip
</p> </p>
<p> <p className="leading-relaxed">
Review the AI-generated speaker assignments and make any necessary corrections before generating the audiobook. Review the AI-generated speaker assignments and make any necessary corrections before generating the audiobook. This ensures the best possible listening experience!
</p> </p>
</div> </div>
)} )}

View File

@@ -36,51 +36,69 @@ export default function TextInputSection({
const isButtonDisabled = disabled || isLoading || text.trim() === ''; const isButtonDisabled = disabled || isLoading || text.trim() === '';
return ( return (
<div className="w-full max-w-4xl mx-auto p-4 sm:p-6 glass-effect rounded-2xl shadow-glow"> <div className="w-full max-w-4xl mx-auto p-6 sm:p-8 glass-effect rounded-3xl shadow-glow card-hover">
<div className="flex items-center gap-3 mb-4 sm:mb-6"> <div className="flex items-center gap-4 mb-6 sm:mb-8">
<div className="w-10 h-10 bg-gradient-to-br from-purple-500 to-indigo-600 rounded-xl flex items-center justify-center shadow-lg"> <div className="w-14 h-14 bg-gradient-to-br from-purple-500 via-purple-600 to-indigo-600 rounded-2xl flex items-center justify-center shadow-lg shadow-purple-500/50">
<span className="text-xl">📝</span> <span className="text-2xl">📝</span>
</div>
<div>
<h2 className="text-2xl sm:text-3xl font-extrabold gradient-text-animated">Enter Your Text</h2>
<p className="text-gray-600 text-sm mt-1">Paste or type your story below</p>
</div> </div>
<h2 className="text-xl sm:text-2xl font-bold gradient-text">Enter Your Text</h2>
</div> </div>
<div className="mb-4"> <div className="mb-6 relative">
<textarea <textarea
value={text} value={text}
onChange={handleTextChange} onChange={handleTextChange}
placeholder="Enter your text here. Use patterns like 'Name:' or '[Name]' to mark different speakers..." placeholder="Enter your text here. Use patterns like 'Name:' or '[Name]' to mark different speakers...
className="w-full h-64 sm:h-80 md:h-96 p-4 sm:p-5 border-2 border-purple-200 rounded-xl focus:border-purple-500 focus:ring-4 focus:ring-purple-200 focus:outline-none resize-y text-sm sm:text-base touch-manipulation shadow-sm"
Example:
Sarah: Hello! How are you today?
John: I'm doing great, thanks for asking!"
className="w-full h-72 sm:h-80 md:h-96 p-5 sm:p-6 border-2 border-purple-300 rounded-2xl focus:border-purple-600 focus:ring-4 focus:ring-purple-300 focus:outline-none resize-y text-sm sm:text-base touch-manipulation shadow-inner bg-gradient-to-br from-white to-purple-50/30 font-mono leading-relaxed"
disabled={disabled || isLoading} disabled={disabled || isLoading}
/> />
<div className="absolute bottom-4 right-4 text-xs text-gray-500 bg-white/80 px-3 py-1 rounded-lg backdrop-blur-sm">
{text.length} characters
</div>
</div> </div>
{showValidation && text.trim() === '' && ( {showValidation && text.trim() === '' && (
<div className="mb-4 p-3 sm:p-4 bg-red-50 border-2 border-red-300 text-red-700 rounded-xl shadow-sm text-sm sm:text-base"> <div className="mb-6 p-4 sm:p-5 bg-gradient-to-r from-red-50 to-pink-50 border-2 border-red-400 text-red-700 rounded-2xl shadow-lg text-sm sm:text-base animate-pulse">
<span className="font-semibold">⚠️</span> Please enter some text before proceeding. <div className="flex items-center gap-3">
<span className="text-2xl">⚠️</span>
<p className="font-bold">Please enter some text before proceeding.</p>
</div>
</div> </div>
)} )}
<button <div className="flex justify-center">
onClick={handleNext} <button
disabled={isButtonDisabled} onClick={handleNext}
className={`w-full sm:w-auto px-8 py-4 min-h-[44px] rounded-xl font-semibold text-white transition-all touch-manipulation shadow-lg ${ disabled={isButtonDisabled}
isButtonDisabled className={`px-10 py-5 min-h-[56px] rounded-2xl font-bold text-white text-lg transition-all touch-manipulation shadow-2xl relative overflow-hidden ${
? 'bg-gray-400 cursor-not-allowed opacity-60' isButtonDisabled
: 'bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 cursor-pointer' ? 'bg-gray-400 cursor-not-allowed opacity-60'
}`} : 'bg-gradient-to-r from-purple-600 via-purple-700 to-indigo-600 hover:from-purple-700 hover:via-purple-800 hover:to-indigo-700 cursor-pointer shadow-purple-500/50'
> }`}
{isLoading ? ( >
<span className="flex items-center justify-center gap-2"> {isLoading ? (
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-white"></div> <span className="flex items-center justify-center gap-3 relative z-10">
Processing... <div className="relative">
</span> <div className="animate-spin rounded-full h-6 w-6 border-3 border-white/30"></div>
) : ( <div className="animate-spin rounded-full h-6 w-6 border-3 border-white border-t-transparent absolute top-0 left-0"></div>
<span className="flex items-center justify-center gap-2"> </div>
Next: Assign voices Processing...
<span>→</span> </span>
</span> ) : (
)} <span className="flex items-center justify-center gap-3 relative z-10">
</button> Next: Assign Voices
<span className="text-xl">→</span>
</span>
)}
</button>
</div>
</div> </div>
); );
} }

View File

@@ -159,31 +159,44 @@ export default function VoiceAssignmentSection({
const isGenerateDisabled = !allRolesAssigned || isGenerating; const isGenerateDisabled = !allRolesAssigned || isGenerating;
return ( return (
<div className="w-full max-w-4xl mx-auto p-4 sm:p-6 glass-effect rounded-2xl shadow-glow"> <div className="w-full max-w-4xl mx-auto p-6 sm:p-8 glass-effect rounded-3xl shadow-glow card-hover">
<div className="flex items-center gap-3 mb-6"> <div className="flex items-center gap-4 mb-8">
<div className="w-10 h-10 bg-gradient-to-br from-purple-500 to-indigo-600 rounded-xl flex items-center justify-center shadow-lg"> <div className="w-14 h-14 bg-gradient-to-br from-purple-500 via-purple-600 to-indigo-600 rounded-2xl flex items-center justify-center shadow-lg shadow-purple-500/50">
<span className="text-xl">🎤</span> <span className="text-2xl">🎤</span>
</div>
<div>
<h2 className="text-2xl sm:text-3xl font-extrabold gradient-text-animated">Assign Voices to Roles</h2>
<p className="text-gray-600 text-sm mt-1">Choose the perfect voice for each character</p>
</div> </div>
<h2 className="text-xl sm:text-2xl font-bold gradient-text">Assign Voices to Roles</h2>
</div> </div>
{/* Role-to-Voice Mapping Section */} {/* Role-to-Voice Mapping Section */}
<div className="mb-6 sm:mb-8"> <div className="mb-8">
<h3 className="text-lg sm:text-xl font-semibold mb-4 text-gray-800 flex items-center gap-2"> <h3 className="text-xl font-bold mb-5 text-gray-800 flex items-center gap-3">
<span>👥</span> Voice Assignments <span className="text-2xl">👥</span>
<span>Voice Assignments</span>
</h3> </h3>
<div className="space-y-3 sm:space-y-4"> <div className="space-y-4">
{roles.map((role) => ( {roles.map((role, index) => (
<div key={role.id} className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4 bg-white p-4 rounded-xl shadow-sm border-2 border-purple-100 hover:border-purple-300 transition-all"> <div
<label className="font-semibold text-gray-700 text-sm sm:text-base sm:w-40"> key={role.id}
{role.label}: className="flex flex-col sm:flex-row sm:items-center gap-3 sm:gap-5 bg-gradient-to-r from-white to-purple-50/50 p-5 rounded-2xl shadow-md border-2 border-purple-200 hover:border-purple-400 hover:shadow-xl transition-all card-hover"
</label> style={{ animationDelay: `${index * 0.1}s` }}
>
<div className="flex items-center gap-3 sm:w-48">
<div className="w-10 h-10 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-xl flex items-center justify-center text-white font-bold shadow-md">
{index + 1}
</div>
<label className="font-bold text-gray-800 text-base sm:text-lg">
{role.label}
</label>
</div>
<select <select
value={voiceAssignments.get(role.id) || ''} value={voiceAssignments.get(role.id) || ''}
onChange={(e) => handleVoiceSelect(role.id, e.target.value)} onChange={(e) => handleVoiceSelect(role.id, e.target.value)}
className="flex-1 p-3 min-h-[44px] border-2 border-purple-200 rounded-xl focus:border-purple-500 focus:ring-4 focus:ring-purple-200 focus:outline-none text-sm sm:text-base touch-manipulation bg-white shadow-sm" className="flex-1 p-4 min-h-[52px] border-2 border-purple-300 rounded-xl focus:border-purple-600 focus:ring-4 focus:ring-purple-300 focus:outline-none text-sm sm:text-base touch-manipulation bg-white shadow-sm font-medium"
> >
<option value="">Select a voice...</option> <option value="">🎭 Select a voice...</option>
{KOKORO_VOICES.map((voice) => ( {KOKORO_VOICES.map((voice) => (
<option key={voice.id} value={voice.id}> <option key={voice.id} value={voice.id}>
{voice.label} {voice.label}
@@ -195,43 +208,46 @@ export default function VoiceAssignmentSection({
</div> </div>
</div> </div>
{/* Language Selector */} {/* Language and Speed Settings */}
<div className="mb-6 bg-white p-4 rounded-xl shadow-sm border-2 border-purple-100"> <div className="grid sm:grid-cols-2 gap-6 mb-8">
<h3 className="text-lg sm:text-xl font-semibold mb-3 text-gray-800 flex items-center gap-2"> {/* Language Selector */}
<span>🌍</span> Language <div className="bg-gradient-to-br from-white to-indigo-50/50 p-6 rounded-2xl shadow-md border-2 border-indigo-200 hover:shadow-xl transition-all">
</h3> <h3 className="text-xl font-bold mb-4 text-gray-800 flex items-center gap-3">
<select <span className="text-2xl">🌍</span> Language
value={language} </h3>
onChange={handleLanguageSelect} <select
className="w-full p-3 min-h-[44px] border-2 border-purple-200 rounded-xl focus:border-purple-500 focus:ring-4 focus:ring-purple-200 focus:outline-none text-sm sm:text-base touch-manipulation bg-white shadow-sm" value={language}
> onChange={handleLanguageSelect}
{LANGUAGES.map((lang) => ( className="w-full p-4 min-h-[52px] border-2 border-indigo-300 rounded-xl focus:border-indigo-600 focus:ring-4 focus:ring-indigo-300 focus:outline-none text-sm sm:text-base touch-manipulation bg-white shadow-sm font-medium"
<option key={lang.code} value={lang.code}> >
{lang.label} {LANGUAGES.map((lang) => (
</option> <option key={lang.code} value={lang.code}>
))} {lang.label}
</select> </option>
</div> ))}
</select>
</div>
{/* Speed Slider */} {/* Speed Slider */}
<div className="mb-6 sm:mb-8 bg-white p-4 rounded-xl shadow-sm border-2 border-purple-100"> <div className="bg-gradient-to-br from-white to-purple-50/50 p-6 rounded-2xl shadow-md border-2 border-purple-200 hover:shadow-xl transition-all">
<h3 className="text-lg sm:text-xl font-semibold mb-4 text-gray-800 flex items-center gap-2"> <h3 className="text-xl font-bold mb-4 text-gray-800 flex items-center gap-3">
<span>⚡</span> Playback Speed <span className="text-2xl">⚡</span> Playback Speed
</h3> </h3>
<div className="flex items-center gap-3 sm:gap-4"> <div className="flex items-center gap-4">
<span className="text-xs sm:text-sm text-gray-600 w-10 sm:w-12 font-medium">0.5x</span> <span className="text-sm text-gray-600 font-semibold">0.5x</span>
<input <input
type="range" type="range"
min="0.5" min="0.5"
max="1.5" max="1.5"
step="0.1" step="0.1"
value={speed} value={speed}
onChange={handleSpeedChange} onChange={handleSpeedChange}
className="flex-1 h-8 sm:h-auto touch-manipulation" className="flex-1 h-8 touch-manipulation"
/> />
<span className="text-xs sm:text-sm text-gray-600 w-10 sm:w-12 font-medium">1.5x</span> <span className="text-sm text-gray-600 font-semibold">1.5x</span>
<div className="w-16 sm:w-20 text-center"> </div>
<span className="font-bold text-purple-600 text-base sm:text-lg bg-purple-50 px-3 py-1 rounded-lg"> <div className="mt-4 text-center">
<span className="inline-block font-extrabold text-purple-700 text-2xl bg-gradient-to-r from-purple-100 to-indigo-100 px-6 py-2 rounded-xl shadow-inner">
{speed.toFixed(1)}x {speed.toFixed(1)}x
</span> </span>
</div> </div>
@@ -239,25 +255,28 @@ export default function VoiceAssignmentSection({
</div> </div>
{/* Action Buttons */} {/* Action Buttons */}
<div className="flex flex-col sm:flex-row justify-center gap-3 sm:gap-4"> <div className="flex flex-col sm:flex-row justify-center gap-4 sm:gap-5">
<button <button
onClick={onGenerate} onClick={onGenerate}
disabled={isGenerateDisabled} disabled={isGenerateDisabled}
className={`w-full sm:w-auto px-8 py-4 min-h-[44px] rounded-xl font-semibold text-white transition-all touch-manipulation shadow-lg ${ className={`px-10 py-5 min-h-[56px] rounded-2xl font-bold text-white text-lg transition-all touch-manipulation shadow-2xl relative overflow-hidden ${
isGenerateDisabled isGenerateDisabled
? 'bg-gray-400 cursor-not-allowed opacity-60' ? 'bg-gray-400 cursor-not-allowed opacity-60'
: 'bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 cursor-pointer' : 'bg-gradient-to-r from-purple-600 via-purple-700 to-indigo-600 hover:from-purple-700 hover:via-purple-800 hover:to-indigo-700 cursor-pointer shadow-purple-500/50'
}`} }`}
> >
{isGenerating ? ( {isGenerating ? (
<span className="flex items-center justify-center gap-2"> <span className="flex items-center justify-center gap-3 relative z-10">
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-white"></div> <div className="relative">
<div className="animate-spin rounded-full h-6 w-6 border-3 border-white/30"></div>
<div className="animate-spin rounded-full h-6 w-6 border-3 border-white border-t-transparent absolute top-0 left-0"></div>
</div>
Generating Script... Generating Script...
</span> </span>
) : ( ) : (
<span className="flex items-center justify-center gap-2"> <span className="flex items-center justify-center gap-3 relative z-10">
Next: Review Script Next: Review Script
<span>→</span> <span className="text-xl">→</span>
</span> </span>
)} )}
</button> </button>
@@ -266,14 +285,15 @@ export default function VoiceAssignmentSection({
<button <button
onClick={onSkipReview} onClick={onSkipReview}
disabled={isGenerateDisabled} disabled={isGenerateDisabled}
className={`w-full sm:w-auto px-8 py-4 min-h-[44px] rounded-xl font-semibold text-white transition-all touch-manipulation shadow-lg ${ className={`px-10 py-5 min-h-[56px] rounded-2xl font-bold text-white text-lg transition-all touch-manipulation shadow-2xl relative overflow-hidden ${
isGenerateDisabled isGenerateDisabled
? 'bg-gray-400 cursor-not-allowed opacity-60' ? 'bg-gray-400 cursor-not-allowed opacity-60'
: 'bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 cursor-pointer' : 'bg-gradient-to-r from-green-500 via-emerald-600 to-teal-600 hover:from-green-600 hover:via-emerald-700 hover:to-teal-700 cursor-pointer shadow-green-500/50'
}`} }`}
> >
<span className="flex items-center justify-center gap-2"> <span className="flex items-center justify-center gap-3 relative z-10">
⚡ Skip Review & Generate <span className="text-xl">⚡</span>
Skip Review & Generate
</span> </span>
</button> </button>
)} )}
@@ -281,8 +301,11 @@ export default function VoiceAssignmentSection({
{/* Validation Message */} {/* Validation Message */}
{!allRolesAssigned && !isGenerating && ( {!allRolesAssigned && !isGenerating && (
<div className="mt-4 p-4 bg-yellow-50 border-2 border-yellow-300 text-yellow-800 rounded-xl text-center text-sm sm:text-base shadow-sm"> <div className="mt-6 p-5 bg-gradient-to-r from-yellow-50 to-orange-50 border-2 border-yellow-400 text-yellow-900 rounded-2xl text-center text-sm sm:text-base shadow-lg">
<span className="font-semibold">⚠️</span> Please assign voices to all roles before generating the audiobook. <div className="flex items-center justify-center gap-3">
<span className="text-2xl">⚠️</span>
<p className="font-bold">Please assign voices to all roles before generating the audiobook.</p>
</div>
</div> </div>
)} )}
</div> </div>

View File

@@ -2,6 +2,8 @@
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
:root { :root {
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
line-height: 1.6; line-height: 1.6;
@@ -14,13 +16,48 @@ body {
min-height: 100vh; min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background-attachment: fixed; background-attachment: fixed;
position: relative;
overflow-x: hidden;
}
/* Animated background particles */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(circle at 20% 50%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 80%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
radial-gradient(circle at 40% 20%, rgba(255, 255, 255, 0.08) 0%, transparent 50%);
animation: float 20s ease-in-out infinite;
pointer-events: none;
z-index: 0;
}
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-20px); }
}
@keyframes pulse-glow {
0%, 100% { box-shadow: 0 0 20px rgba(102, 126, 234, 0.4), 0 0 40px rgba(118, 75, 162, 0.2); }
50% { box-shadow: 0 0 30px rgba(102, 126, 234, 0.6), 0 0 60px rgba(118, 75, 162, 0.3); }
}
@keyframes shimmer {
0% { background-position: -1000px 0; }
100% { background-position: 1000px 0; }
} }
@layer utilities { @layer utilities {
.glass-effect { .glass-effect {
background: rgba(255, 255, 255, 0.95); background: rgba(255, 255, 255, 0.98);
backdrop-filter: blur(10px); backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(20px) saturate(180%);
border: 1px solid rgba(255, 255, 255, 0.3);
} }
.gradient-text { .gradient-text {
@@ -30,7 +67,29 @@ body {
background-clip: text; background-clip: text;
} }
.gradient-text-animated {
background: linear-gradient(90deg, #667eea, #764ba2, #667eea);
background-size: 200% auto;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
animation: shimmer 3s linear infinite;
}
.shadow-glow { .shadow-glow {
box-shadow: 0 10px 40px rgba(102, 126, 234, 0.2); box-shadow: 0 20px 60px rgba(102, 126, 234, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.1);
}
.shadow-glow-strong {
animation: pulse-glow 3s ease-in-out infinite;
}
.card-hover {
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.card-hover:hover {
transform: translateY(-8px) scale(1.02);
box-shadow: 0 30px 80px rgba(102, 126, 234, 0.4);
} }
} }