|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Video Story Generator</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
|
<style> |
|
|
.video-container { |
|
|
position: relative; |
|
|
padding-bottom: 56.25%; |
|
|
height: 0; |
|
|
overflow: hidden; |
|
|
} |
|
|
.video-container video { |
|
|
position: absolute; |
|
|
top: 0; |
|
|
left: 0; |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
object-fit: cover; |
|
|
} |
|
|
.dropzone { |
|
|
border: 2px dashed #ccc; |
|
|
transition: all 0.3s ease; |
|
|
min-height: 200px; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
} |
|
|
.dropzone.active { |
|
|
border-color: #4f46e5; |
|
|
background-color: #eef2ff; |
|
|
transform: scale(0.98); |
|
|
} |
|
|
.story-card { |
|
|
background: linear-gradient(135deg, #f5f7fa 0%, #e4e8f0 100%); |
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); |
|
|
} |
|
|
.loading-dots:after { |
|
|
content: '.'; |
|
|
animation: dots 1.5s steps(5, end) infinite; |
|
|
} |
|
|
@keyframes dots { |
|
|
0%, 20% { |
|
|
color: rgba(0,0,0,0); |
|
|
text-shadow: .25em 0 0 rgba(0,0,0,0), .5em 0 0 rgba(0,0,0,0); |
|
|
} |
|
|
40% { |
|
|
color: currentColor; |
|
|
text-shadow: .25em 0 0 rgba(0,0,0,0), .5em 0 0 rgba(0,0,0,0); |
|
|
} |
|
|
60% { |
|
|
text-shadow: .25em 0 0 currentColor, .5em 0 0 rgba(0,0,0,0); |
|
|
} |
|
|
80%, 100% { |
|
|
text-shadow: .25em 0 0 currentColor, .5em 0 0 currentColor; |
|
|
} |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gray-50 min-h-screen"> |
|
|
<div class="container mx-auto px-4 py-8 max-w-6xl"> |
|
|
<header class="text-center mb-12"> |
|
|
<h1 class="text-4xl md:text-5xl font-bold text-indigo-800 mb-4">Video Story Generator</h1> |
|
|
<p class="text-lg text-gray-600 max-w-2xl mx-auto"> |
|
|
Upload your video and let our AI craft a unique story based on what it observes. |
|
|
Perfect for content creators, storytellers, and anyone who loves creative narratives. |
|
|
</p> |
|
|
</header> |
|
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-12"> |
|
|
<div class="bg-white rounded-xl shadow-lg overflow-hidden"> |
|
|
<div class="p-6"> |
|
|
<h2 class="text-2xl font-semibold text-gray-800 mb-4">Upload Your Video</h2> |
|
|
|
|
|
<div id="dropzone" class="dropzone rounded-lg p-8 text-center cursor-pointer mb-6"> |
|
|
<div id="upload-content" class="space-y-3"> |
|
|
<div class="flex justify-center"> |
|
|
<i class="fas fa-cloud-upload-alt text-5xl text-indigo-500"></i> |
|
|
</div> |
|
|
<h3 class="text-lg font-medium text-gray-700">Drag & drop your video here</h3> |
|
|
<p class="text-sm text-gray-500">or click to browse files</p> |
|
|
<p class="text-xs text-gray-400 mt-2">Supports MP4, MOV, AVI (Max 100MB)</p> |
|
|
</div> |
|
|
<input type="file" id="video-upload" class="hidden" accept="video/mp4,video/quicktime,video/x-msvideo"> |
|
|
</div> |
|
|
|
|
|
<div id="video-preview-container" class="hidden mb-6"> |
|
|
<div class="video-container rounded-lg overflow-hidden shadow"> |
|
|
<video id="video-preview" controls></video> |
|
|
</div> |
|
|
<div class="mt-3 flex justify-between items-center"> |
|
|
<span id="video-name" class="text-sm font-medium text-gray-700 truncate"></span> |
|
|
<button id="remove-video" class="text-red-500 hover:text-red-700 text-sm"> |
|
|
<i class="fas fa-times mr-1"></i> Remove |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="space-y-4"> |
|
|
<div> |
|
|
<label for="story-style" class="block text-sm font-medium text-gray-700 mb-1">Story Style</label> |
|
|
<select id="story-style" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"> |
|
|
<option value="fantasy">Fantasy Adventure</option> |
|
|
<option value="scifi">Sci-Fi Mystery</option> |
|
|
<option value="romance">Romantic Drama</option> |
|
|
<option value="comedy">Humorous Tale</option> |
|
|
<option value="horror">Spooky Horror</option> |
|
|
<option value="realistic">Realistic Fiction</option> |
|
|
</select> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<label for="character-name" class="block text-sm font-medium text-gray-700 mb-1">Main Character Name (optional)</label> |
|
|
<input type="text" id="character-name" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" placeholder="e.g. Alex, Luna, Captain Nova"> |
|
|
</div> |
|
|
|
|
|
<button id="generate-btn" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-3 px-4 rounded-md transition duration-300 flex items-center justify-center disabled:opacity-50 disabled:cursor-not-allowed" disabled> |
|
|
<i class="fas fa-magic mr-2"></i> Generate Story |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="bg-white rounded-xl shadow-lg overflow-hidden"> |
|
|
<div class="p-6 h-full flex flex-col"> |
|
|
<h2 class="text-2xl font-semibold text-gray-800 mb-4">Your Generated Story</h2> |
|
|
|
|
|
<div id="story-placeholder" class="flex-1 flex items-center justify-center text-center p-8 bg-gray-50 rounded-lg"> |
|
|
<div> |
|
|
<i class="fas fa-book-open text-4xl text-gray-300 mb-4"></i> |
|
|
<p class="text-gray-500">Your story will appear here after you upload a video and click "Generate Story".</p> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="loading-indicator" class="hidden flex-1 items-center justify-center p-8"> |
|
|
<div class="text-center"> |
|
|
<div class="inline-block animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-indigo-500 mb-4"></div> |
|
|
<p class="text-gray-700 loading-dots">Analyzing video and crafting your story</p> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="story-container" class="hidden flex-1 story-card rounded-lg p-6 overflow-y-auto"> |
|
|
<h3 id="story-title" class="text-xl font-bold text-gray-800 mb-3"></h3> |
|
|
<div id="story-content" class="text-gray-700 leading-relaxed"></div> |
|
|
</div> |
|
|
|
|
|
<div id="story-actions" class="hidden mt-6 flex justify-between"> |
|
|
<button id="copy-story" class="px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded-md text-gray-700 transition duration-300"> |
|
|
<i class="fas fa-copy mr-2"></i> Copy Story |
|
|
</button> |
|
|
<button id="download-story" class="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 rounded-md text-white transition duration-300"> |
|
|
<i class="fas fa-download mr-2"></i> Download as TXT |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="bg-indigo-50 rounded-xl p-6 mb-8"> |
|
|
<h3 class="text-xl font-semibold text-indigo-800 mb-3">How It Works</h3> |
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4"> |
|
|
<div class="bg-white p-4 rounded-lg shadow"> |
|
|
<div class="text-indigo-600 mb-2"> |
|
|
<i class="fas fa-upload text-2xl"></i> |
|
|
</div> |
|
|
<h4 class="font-medium text-gray-800 mb-1">1. Upload Your Video</h4> |
|
|
<p class="text-sm text-gray-600">Select any video from your device. Our system will analyze the visuals and motion.</p> |
|
|
</div> |
|
|
<div class="bg-white p-4 rounded-lg shadow"> |
|
|
<div class="text-indigo-600 mb-2"> |
|
|
<i class="fas fa-brain text-2xl"></i> |
|
|
</div> |
|
|
<h4 class="font-medium text-gray-800 mb-1">2. AI Analysis</h4> |
|
|
<p class="text-sm text-gray-600">Our AI detects scenes, objects, colors, and movements to understand your video's content.</p> |
|
|
</div> |
|
|
<div class="bg-white p-4 rounded-lg shadow"> |
|
|
<div class="text-indigo-600 mb-2"> |
|
|
<i class="fas fa-book text-2xl"></i> |
|
|
</div> |
|
|
<h4 class="font-medium text-gray-800 mb-1">3. Story Generation</h4> |
|
|
<p class="text-sm text-gray-600">A unique story is crafted based on the analysis, tailored to your selected style.</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<footer class="text-center text-gray-500 text-sm mt-12"> |
|
|
<p>Video Story Generator © 2023 | All uploaded videos are processed locally in your browser</p> |
|
|
</footer> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
// DOM elements |
|
|
const dropzone = document.getElementById('dropzone'); |
|
|
const videoUpload = document.getElementById('video-upload'); |
|
|
const videoPreview = document.getElementById('video-preview'); |
|
|
const videoPreviewContainer = document.getElementById('video-preview-container'); |
|
|
const videoName = document.getElementById('video-name'); |
|
|
const removeVideoBtn = document.getElementById('remove-video'); |
|
|
const uploadContent = document.getElementById('upload-content'); |
|
|
const generateBtn = document.getElementById('generate-btn'); |
|
|
const storyPlaceholder = document.getElementById('story-placeholder'); |
|
|
const loadingIndicator = document.getElementById('loading-indicator'); |
|
|
const storyContainer = document.getElementById('story-container'); |
|
|
const storyTitle = document.getElementById('story-title'); |
|
|
const storyContent = document.getElementById('story-content'); |
|
|
const storyActions = document.getElementById('story-actions'); |
|
|
const copyStoryBtn = document.getElementById('copy-story'); |
|
|
const downloadStoryBtn = document.getElementById('download-story'); |
|
|
const storyStyleSelect = document.getElementById('story-style'); |
|
|
const characterNameInput = document.getElementById('character-name'); |
|
|
|
|
|
// Drag and drop events |
|
|
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { |
|
|
dropzone.addEventListener(eventName, preventDefaults, false); |
|
|
}); |
|
|
|
|
|
function preventDefaults(e) { |
|
|
e.preventDefault(); |
|
|
e.stopPropagation(); |
|
|
} |
|
|
|
|
|
['dragenter', 'dragover'].forEach(eventName => { |
|
|
dropzone.addEventListener(eventName, highlight, false); |
|
|
}); |
|
|
|
|
|
['dragleave', 'drop'].forEach(eventName => { |
|
|
dropzone.addEventListener(eventName, unhighlight, false); |
|
|
}); |
|
|
|
|
|
function highlight(e) { |
|
|
e.preventDefault(); |
|
|
dropzone.classList.add('active'); |
|
|
} |
|
|
|
|
|
function unhighlight() { |
|
|
dropzone.classList.remove('active'); |
|
|
} |
|
|
|
|
|
dropzone.addEventListener('drop', handleDrop, false); |
|
|
dropzone.addEventListener('click', () => videoUpload.click()); |
|
|
|
|
|
function handleDrop(e) { |
|
|
const dt = e.dataTransfer; |
|
|
const files = dt.files; |
|
|
|
|
|
// Reset dropzone appearance |
|
|
dropzone.classList.remove('active'); |
|
|
|
|
|
// Process files if any |
|
|
if (files.length > 0) { |
|
|
handleFiles(files); |
|
|
} |
|
|
} |
|
|
|
|
|
videoUpload.addEventListener('change', function() { |
|
|
if (this.files.length) { |
|
|
handleFiles(this.files); |
|
|
} |
|
|
}); |
|
|
|
|
|
function handleFiles(files) { |
|
|
const file = files[0]; |
|
|
if (!file) return; |
|
|
|
|
|
// Check if file is a video |
|
|
const validTypes = ['video/mp4', 'video/quicktime', 'video/x-msvideo']; |
|
|
if (!validTypes.includes(file.type)) { |
|
|
alert('Please upload a video file (MP4, MOV, AVI)'); |
|
|
return; |
|
|
} |
|
|
|
|
|
// Check file size (100MB max) |
|
|
if (file.size > 100 * 1024 * 1024) { |
|
|
alert('File is too large. Maximum size is 100MB.'); |
|
|
return; |
|
|
} |
|
|
|
|
|
// Display video preview |
|
|
const videoURL = URL.createObjectURL(file); |
|
|
videoPreview.src = videoURL; |
|
|
videoPreview.load(); |
|
|
videoName.textContent = file.name; |
|
|
videoPreviewContainer.classList.remove('hidden'); |
|
|
uploadContent.classList.add('hidden'); |
|
|
generateBtn.disabled = false; |
|
|
|
|
|
// Load metadata |
|
|
videoPreview.onloadedmetadata = function() { |
|
|
console.log(`Video loaded: ${file.name} (${Math.round(file.size/(1024*1024))}MB)`); |
|
|
}; |
|
|
|
|
|
videoPreview.onerror = function() { |
|
|
alert('Error loading video file'); |
|
|
resetVideoUpload(); |
|
|
}; |
|
|
} |
|
|
|
|
|
removeVideoBtn.addEventListener('click', resetVideoUpload); |
|
|
|
|
|
generateBtn.addEventListener('click', function() { |
|
|
if (!videoPreview.src) return; |
|
|
|
|
|
// Show loading state |
|
|
storyPlaceholder.classList.add('hidden'); |
|
|
loadingIndicator.classList.remove('hidden'); |
|
|
storyContainer.classList.add('hidden'); |
|
|
storyActions.classList.add('hidden'); |
|
|
generateBtn.disabled = true; |
|
|
generateBtn.innerHTML = '<i class="fas fa-cog fa-spin mr-2"></i> Generating...'; |
|
|
|
|
|
// Simulate analysis and story generation (in a real app, this would be an API call) |
|
|
setTimeout(() => { |
|
|
generateStory(); |
|
|
}, 3000); |
|
|
}); |
|
|
|
|
|
function generateStory() { |
|
|
// Get user inputs |
|
|
const storyStyle = storyStyleSelect.value; |
|
|
const characterName = characterNameInput.value.trim() || getRandomName(); |
|
|
|
|
|
// Simulate video analysis (in a real app, this would analyze actual video content) |
|
|
const simulatedAnalysis = simulateVideoAnalysis(); |
|
|
|
|
|
// Generate story based on style and analysis |
|
|
const story = createStory(simulatedAnalysis, storyStyle, characterName); |
|
|
|
|
|
// Display the story |
|
|
storyTitle.textContent = story.title; |
|
|
storyContent.innerHTML = story.content.split('\n').map(para => `<p class="mb-4">${para}</p>`).join(''); |
|
|
|
|
|
// Update UI |
|
|
loadingIndicator.classList.add('hidden'); |
|
|
storyContainer.classList.remove('hidden'); |
|
|
storyActions.classList.remove('hidden'); |
|
|
generateBtn.disabled = false; |
|
|
generateBtn.innerHTML = '<i class="fas fa-magic mr-2"></i> Generate Story'; |
|
|
} |
|
|
|
|
|
function simulateVideoAnalysis() { |
|
|
// This would normally analyze the video content |
|
|
// For demo purposes, we return simulated data |
|
|
const colorPalette = ['#3b82f6', '#10b981', '#f59e0b', '#ef4444']; |
|
|
const dominantColors = [...colorPalette].sort(() => 0.5 - Math.random()).slice(0, 3); |
|
|
|
|
|
const possibleScenes = [ |
|
|
'a person walking through a forest', |
|
|
'a city skyline at sunset', |
|
|
'waves crashing on a beach', |
|
|
'a busy street with cars and pedestrians', |
|
|
'a close-up of a face with emotional expression', |
|
|
'a group of animals interacting', |
|
|
'a slow panning shot of mountains', |
|
|
'raindrops falling on a window' |
|
|
]; |
|
|
|
|
|
const possibleMoods = [ |
|
|
'peaceful and serene', |
|
|
'tense and dramatic', |
|
|
'joyful and energetic', |
|
|
'mysterious and suspenseful', |
|
|
'melancholic and reflective' |
|
|
]; |
|
|
|
|
|
return { |
|
|
dominantColors, |
|
|
primaryScene: possibleScenes[Math.floor(Math.random() * possibleScenes.length)], |
|
|
secondaryScene: possibleScenes[Math.floor(Math.random() * possibleScenes.length)], |
|
|
mood: possibleMoods[Math.floor(Math.random() * possibleMoods.length)], |
|
|
movementLevel: Math.random() > 0.5 ? 'high' : 'low', |
|
|
containsFaces: Math.random() > 0.5, |
|
|
containsNature: Math.random() > 0.7, |
|
|
containsUrban: Math.random() > 0.7 |
|
|
}; |
|
|
} |
|
|
|
|
|
function createStory(analysis, style, characterName) { |
|
|
const styles = { |
|
|
fantasy: { |
|
|
title: `The ${characterName}'s Enchanted Journey`, |
|
|
templates: [ |
|
|
`In a realm where the ${analysis.dominantColors[0].slice(1)} mists swirled endlessly, ${characterName} found themselves standing before ${analysis.containsNature ? 'an ancient oak with bark like wrinkled parchment' : 'a towering obsidian monolith'}. The air was thick with ${analysis.mood.includes('serene') ? 'the scent of magic' : 'unspoken dread'}.`, |
|
|
`As ${characterName} ventured ${analysis.movementLevel === 'high' ? 'swiftly' : 'cautiously'} through the ${analysis.containsNature ? 'enchanted glade' : 'forbidden ruins'}, they noticed ${analysis.secondaryScene.includes('face') ? 'a pair of glowing eyes watching from the shadows' : 'strange symbols pulsating with an eerie light'}.`, |
|
|
`Suddenly, ${analysis.mood.includes('joyful') ? 'a chorus of celestial voices filled the air' : 'the ground trembled violently'}. ${characterName} realized this was no ordinary ${analysis.containsUrban ? 'city' : 'forest'}—it was ${analysis.mood.includes('mysterious') ? 'a gateway to another dimension' : 'the battleground of forgotten gods'}.` |
|
|
] |
|
|
}, |
|
|
scifi: { |
|
|
title: `${characterName} and the ${analysis.dominantColors[1].slice(1)} Signal`, |
|
|
templates: [ |
|
|
`The ${analysis.dominantColors[0].slice(1)} alert flashed across the viewscreen as ${characterName} adjusted the quantum scanners. "${analysis.primaryScene.includes('city') ? 'Metropolis Sector' : 'Outer Rim Quadrant'} is showing anomalous readings," they muttered, fingers dancing over the holographic controls.`, |
|
|
`Descending through ${analysis.mood.includes('tense') ? 'the ion storm' : 'the nebula'}, ${characterName}'s ship detected ${analysis.secondaryScene.includes('face') ? 'a humanoid lifeform' : 'an artificial structure'} where none should exist. The ${analysis.movementLevel === 'high' ? 'rapidly oscillating' : 'steady'} energy signature matched no known technology.`, |
|
|
`"Computer, analyze ${analysis.dominantColors[1].slice(1)} spectrum," ${characterName} ordered. What they discovered would ${analysis.mood.includes('dramatic') ? 'change the course of galactic history' : 'challenge everything science understood about reality'}.` |
|
|
] |
|
|
}, |
|
|
romance: { |
|
|
title: `${characterName}'s ${analysis.dominantColors[2].slice(1)} Heart`, |
|
|
templates: [ |
|
|
`The ${analysis.primaryScene.includes('sunset') ? 'golden hues of sunset' : 'soft glow of streetlights'} painted ${characterName}'s face as they stood ${analysis.containsUrban ? 'on the bustling city bridge' : 'at the quiet beach shore'}. Their heart beat ${analysis.movementLevel === 'high' ? 'wildly' : 'a steady rhythm'}—they were waiting for someone.`, |
|
|
`Memories flooded back: ${analysis.secondaryScene.includes('forest') ? 'that first meeting under the ancient trees' : 'the accidental coffee spill that started it all'}. The ${analysis.mood.includes('joyful') ? 'laughter they shared' : 'silent understanding between them'} had grown into something ${characterName} couldn't ignore.`, |
|
|
`Then, ${analysis.mood.includes('serene') ? 'as gentle as the morning mist' : 'with dramatic flair'}, ${analysis.containsFaces ? 'the familiar face appeared' : 'a letter arrived'}. The ${analysis.dominantColors[2].slice(1)} ${analysis.containsNature ? 'rose' : 'envelope'} held the answer ${characterName} had been ${analysis.movementLevel === 'high' ? 'desperately' : 'patiently'} waiting for.` |
|
|
] |
|
|
}, |
|
|
comedy: { |
|
|
title: `The ${analysis.dominantColors[0].slice(1)} Fiasco of ${characterName}`, |
|
|
templates: [ |
|
|
`It all started when ${characterName} decided to ${analysis.primaryScene.includes('walking') ? 'take what they thought would be a simple walk' : 'film what they assumed would be an ordinary video'}. Little did they know, ${analysis.mood.includes('dramatic') ? 'fate had other, much sillier plans' : 'the universe was about to play the ultimate prank'}.`, |
|
|
`First, there was the incident with the ${analysis.containsNature ? 'squirrel that turned out to be an undercover agent' : 'traffic cone that somehow became sentient'}. Then ${characterName} ${analysis.movementLevel === 'high' ? 'ran headfirst into' : 'slowly backed into'} ${analysis.secondaryScene.includes('face') ? 'their own reflection' : 'a situation no sane person would believe'}.`, |
|
|
`By the time the ${analysis.dominantColors[1].slice(1)} ${analysis.containsUrban ? 'taxi' : 'tractor'} arrived, ${characterName} had ${analysis.mood.includes('joyful') ? 'somehow become mayor of a small town' : 'accidentally started a cult dedicated to breakfast foods'}. And the weirdest part? ${analysis.movementLevel === 'high' ? 'It only took seven minutes' : 'This was just Tuesday'}.` |
|
|
] |
|
|
}, |
|
|
horror: { |
|
|
title: `The ${analysis.dominantColors[0].slice(1)} Whisper`, |
|
|
templates: [ |
|
|
`${characterName} shouldn't have ${analysis.primaryScene.includes('forest') ? 'ventured into those woods after dark' : 'watched that unmarked video tape'}. Now, the ${analysis.mood.includes('serene') ? 'deceptively calm' : 'oppressively heavy'} silence was broken only by ${analysis.movementLevel === 'high' ? 'frantic scratching sounds' : 'slow, deliberate footsteps'}.`, |
|
|
`The ${analysis.secondaryScene.includes('face') ? 'face in the window wasn't there when they looked directly at it' : 'shadows moved when nothing cast them'}. ${characterName}'s ${analysis.dominantColors[1].slice(1)} sweater offered no protection against the creeping dread that ${analysis.containsUrban ? 'even the city lights couldn't dispel' : 'the countryside emptiness amplified'}.`, |
|
|
`Then came the ${analysis.mood.includes('mysterious') ? 'whispers in a language that shouldn't exist' : 'screams that might have been their own'}. The ${analysis.containsFaces ? 'mirrors reflected something... else' : 'lights began to fail one by one'}. ${characterName} realized too late: ${analysis.movementLevel === 'high' ? 'it was already inside' : 'they were never alone'}.` |
|
|
] |
|
|
}, |
|
|
realistic: { |
|
|
title: `${characterName}'s ${analysis.primaryScene.includes('city') ? 'Urban' : 'Personal'} Journey`, |
|
|
templates: [ |
|
|
`The ${analysis.primaryScene.includes('beach') ? 'rhythm of the waves' : 'hustle of the city'} formed a backdrop to ${characterName}'s ${analysis.mood.includes('joyful') ? 'day of discovery' : 'period of reflection'}. Life had been ${analysis.movementLevel === 'high' ? 'a whirlwind lately' : 'strangely still'}, and ${analysis.containsFaces ? 'the faces around them told stories of their own' : 'the environment mirrored their inner state'}.`, |
|
|
`${analysis.secondaryScene.includes('walking') ? 'With each step' : 'As time passed'}, ${characterName} noticed ${analysis.dominantColors[0].slice(1)} ${analysis.containsNature ? 'flowers pushing through cracks in the pavement' : 'graffiti that seemed to speak directly to them'}. It was a reminder that ${analysis.mood.includes('serene') ? 'beauty persists' : 'change is inevitable'}.`, |
|
|
`By ${analysis.containsUrban ? 'the time the streetlights flickered on' : 'sunset'}, ${characterName} had reached ${analysis.movementLevel === 'high' ? 'an unexpected conclusion' : 'a quiet acceptance'}. The ${analysis.dominantColors[1].slice(1)} ${analysis.containsFaces ? 'smile of a stranger' : 'quality of the light'} marked this as a day they would ${analysis.mood.includes('dramatic') ? 'never forget' : 'look back on with quiet gratitude'}.` |
|
|
] |
|
|
} |
|
|
}; |
|
|
|
|
|
const selectedStyle = styles[style] || styles.fantasy; |
|
|
return { |
|
|
title: selectedStyle.title, |
|
|
content: selectedStyle.templates.join('\n\n') |
|
|
}; |
|
|
} |
|
|
|
|
|
function getRandomName() { |
|
|
const names = ['Alex', 'Morgan', 'Jordan', 'Taylor', 'Casey', 'Riley', 'Jamie', 'Quinn', 'Dakota', 'Skyler']; |
|
|
return names[Math.floor(Math.random() * names.length)]; |
|
|
} |
|
|
|
|
|
function resetVideoUpload() { |
|
|
if (videoPreview.src) { |
|
|
URL.revokeObjectURL(videoPreview.src); |
|
|
} |
|
|
videoPreview.src = ''; |
|
|
videoPreviewContainer.classList.add('hidden'); |
|
|
uploadContent.classList.remove('hidden'); |
|
|
generateBtn.disabled = true; |
|
|
videoUpload.value = ''; |
|
|
resetStoryOutput(); |
|
|
} |
|
|
|
|
|
function resetStoryOutput() { |
|
|
storyPlaceholder.classList.remove('hidden'); |
|
|
loadingIndicator.classList.add('hidden'); |
|
|
storyContainer.classList.add('hidden'); |
|
|
storyActions.classList.add('hidden'); |
|
|
storyTitle.textContent = ''; |
|
|
storyContent.textContent = ''; |
|
|
generateBtn.disabled = !videoPreview.src; |
|
|
generateBtn.innerHTML = '<i class="fas fa-magic mr-2"></i> Generate Story'; |
|
|
} |
|
|
|
|
|
// Copy story to clipboard |
|
|
copyStoryBtn.addEventListener('click', function() { |
|
|
const storyText = `${storyTitle.textContent}\n\n${storyContent.textContent}`; |
|
|
navigator.clipboard.writeText(storyText).then(() => { |
|
|
const originalText = copyStoryBtn.innerHTML; |
|
|
copyStoryBtn.innerHTML = '<i class="fas fa-check mr-2"></i> Copied!'; |
|
|
setTimeout(() => { |
|
|
copyStoryBtn.innerHTML = originalText; |
|
|
}, 2000); |
|
|
}); |
|
|
}); |
|
|
|
|
|
// Download story as text file |
|
|
downloadStoryBtn.addEventListener('click', function() { |
|
|
const storyText = `${storyTitle.textContent}\n\n${storyContent.textContent}`; |
|
|
const blob = new Blob([storyText], { type: 'text/plain' }); |
|
|
const url = URL.createObjectURL(blob); |
|
|
const a = document.createElement('a'); |
|
|
a.href = url; |
|
|
a.download = `${storyTitle.textContent.replace(/\s+/g, '_')}.txt`; |
|
|
document.body.appendChild(a); |
|
|
a.click(); |
|
|
document.body.removeChild(a); |
|
|
URL.revokeObjectURL(url); |
|
|
}); |
|
|
}); |
|
|
</script> |
|
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=suppressor14/testing-cog" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
|
</html> |