suppressor14 commited on
Commit
1353a91
·
verified ·
1 Parent(s): 857b93c

make an app that runs in the browser that allows the user to upload a video, the app then analyses the video, and generates a story about the video based on what it observes - Initial Deployment

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +468 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Testing Cog
3
- emoji: 📈
4
- colorFrom: yellow
5
- colorTo: pink
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: testing-cog
3
+ emoji: 🐳
4
+ colorFrom: pink
5
+ colorTo: gray
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,468 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Video Story Generator</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ .video-container {
11
+ position: relative;
12
+ padding-bottom: 56.25%; /* 16:9 aspect ratio */
13
+ height: 0;
14
+ overflow: hidden;
15
+ }
16
+ .video-container video {
17
+ position: absolute;
18
+ top: 0;
19
+ left: 0;
20
+ width: 100%;
21
+ height: 100%;
22
+ object-fit: cover;
23
+ }
24
+ .dropzone {
25
+ border: 2px dashed #ccc;
26
+ transition: all 0.3s ease;
27
+ }
28
+ .dropzone.active {
29
+ border-color: #4f46e5;
30
+ background-color: #eef2ff;
31
+ }
32
+ .story-card {
33
+ background: linear-gradient(135deg, #f5f7fa 0%, #e4e8f0 100%);
34
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
35
+ }
36
+ .loading-dots:after {
37
+ content: '.';
38
+ animation: dots 1.5s steps(5, end) infinite;
39
+ }
40
+ @keyframes dots {
41
+ 0%, 20% {
42
+ color: rgba(0,0,0,0);
43
+ text-shadow: .25em 0 0 rgba(0,0,0,0), .5em 0 0 rgba(0,0,0,0);
44
+ }
45
+ 40% {
46
+ color: currentColor;
47
+ text-shadow: .25em 0 0 rgba(0,0,0,0), .5em 0 0 rgba(0,0,0,0);
48
+ }
49
+ 60% {
50
+ text-shadow: .25em 0 0 currentColor, .5em 0 0 rgba(0,0,0,0);
51
+ }
52
+ 80%, 100% {
53
+ text-shadow: .25em 0 0 currentColor, .5em 0 0 currentColor;
54
+ }
55
+ }
56
+ </style>
57
+ </head>
58
+ <body class="bg-gray-50 min-h-screen">
59
+ <div class="container mx-auto px-4 py-8 max-w-6xl">
60
+ <header class="text-center mb-12">
61
+ <h1 class="text-4xl md:text-5xl font-bold text-indigo-800 mb-4">Video Story Generator</h1>
62
+ <p class="text-lg text-gray-600 max-w-2xl mx-auto">
63
+ Upload your video and let our AI craft a unique story based on what it observes.
64
+ Perfect for content creators, storytellers, and anyone who loves creative narratives.
65
+ </p>
66
+ </header>
67
+
68
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-12">
69
+ <div class="bg-white rounded-xl shadow-lg overflow-hidden">
70
+ <div class="p-6">
71
+ <h2 class="text-2xl font-semibold text-gray-800 mb-4">Upload Your Video</h2>
72
+
73
+ <div id="dropzone" class="dropzone rounded-lg p-8 text-center cursor-pointer mb-6">
74
+ <div id="upload-content" class="space-y-3">
75
+ <div class="flex justify-center">
76
+ <i class="fas fa-cloud-upload-alt text-5xl text-indigo-500"></i>
77
+ </div>
78
+ <h3 class="text-lg font-medium text-gray-700">Drag & drop your video here</h3>
79
+ <p class="text-sm text-gray-500">or click to browse files</p>
80
+ <p class="text-xs text-gray-400 mt-2">Supports MP4, MOV, AVI (Max 100MB)</p>
81
+ </div>
82
+ <input type="file" id="video-upload" class="hidden" accept="video/mp4,video/quicktime,video/x-msvideo">
83
+ </div>
84
+
85
+ <div id="video-preview-container" class="hidden mb-6">
86
+ <div class="video-container rounded-lg overflow-hidden shadow">
87
+ <video id="video-preview" controls></video>
88
+ </div>
89
+ <div class="mt-3 flex justify-between items-center">
90
+ <span id="video-name" class="text-sm font-medium text-gray-700 truncate"></span>
91
+ <button id="remove-video" class="text-red-500 hover:text-red-700 text-sm">
92
+ <i class="fas fa-times mr-1"></i> Remove
93
+ </button>
94
+ </div>
95
+ </div>
96
+
97
+ <div class="space-y-4">
98
+ <div>
99
+ <label for="story-style" class="block text-sm font-medium text-gray-700 mb-1">Story Style</label>
100
+ <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">
101
+ <option value="fantasy">Fantasy Adventure</option>
102
+ <option value="scifi">Sci-Fi Mystery</option>
103
+ <option value="romance">Romantic Drama</option>
104
+ <option value="comedy">Humorous Tale</option>
105
+ <option value="horror">Spooky Horror</option>
106
+ <option value="realistic">Realistic Fiction</option>
107
+ </select>
108
+ </div>
109
+
110
+ <div>
111
+ <label for="character-name" class="block text-sm font-medium text-gray-700 mb-1">Main Character Name (optional)</label>
112
+ <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">
113
+ </div>
114
+
115
+ <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>
116
+ <i class="fas fa-magic mr-2"></i> Generate Story
117
+ </button>
118
+ </div>
119
+ </div>
120
+ </div>
121
+
122
+ <div class="bg-white rounded-xl shadow-lg overflow-hidden">
123
+ <div class="p-6 h-full flex flex-col">
124
+ <h2 class="text-2xl font-semibold text-gray-800 mb-4">Your Generated Story</h2>
125
+
126
+ <div id="story-placeholder" class="flex-1 flex items-center justify-center text-center p-8 bg-gray-50 rounded-lg">
127
+ <div>
128
+ <i class="fas fa-book-open text-4xl text-gray-300 mb-4"></i>
129
+ <p class="text-gray-500">Your story will appear here after you upload a video and click "Generate Story".</p>
130
+ </div>
131
+ </div>
132
+
133
+ <div id="loading-indicator" class="hidden flex-1 items-center justify-center p-8">
134
+ <div class="text-center">
135
+ <div class="inline-block animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-indigo-500 mb-4"></div>
136
+ <p class="text-gray-700 loading-dots">Analyzing video and crafting your story</p>
137
+ </div>
138
+ </div>
139
+
140
+ <div id="story-container" class="hidden flex-1 story-card rounded-lg p-6 overflow-y-auto">
141
+ <h3 id="story-title" class="text-xl font-bold text-gray-800 mb-3"></h3>
142
+ <div id="story-content" class="text-gray-700 leading-relaxed"></div>
143
+ </div>
144
+
145
+ <div id="story-actions" class="hidden mt-6 flex justify-between">
146
+ <button id="copy-story" class="px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded-md text-gray-700 transition duration-300">
147
+ <i class="fas fa-copy mr-2"></i> Copy Story
148
+ </button>
149
+ <button id="download-story" class="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 rounded-md text-white transition duration-300">
150
+ <i class="fas fa-download mr-2"></i> Download as TXT
151
+ </button>
152
+ </div>
153
+ </div>
154
+ </div>
155
+ </div>
156
+
157
+ <div class="bg-indigo-50 rounded-xl p-6 mb-8">
158
+ <h3 class="text-xl font-semibold text-indigo-800 mb-3">How It Works</h3>
159
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
160
+ <div class="bg-white p-4 rounded-lg shadow">
161
+ <div class="text-indigo-600 mb-2">
162
+ <i class="fas fa-upload text-2xl"></i>
163
+ </div>
164
+ <h4 class="font-medium text-gray-800 mb-1">1. Upload Your Video</h4>
165
+ <p class="text-sm text-gray-600">Select any video from your device. Our system will analyze the visuals and motion.</p>
166
+ </div>
167
+ <div class="bg-white p-4 rounded-lg shadow">
168
+ <div class="text-indigo-600 mb-2">
169
+ <i class="fas fa-brain text-2xl"></i>
170
+ </div>
171
+ <h4 class="font-medium text-gray-800 mb-1">2. AI Analysis</h4>
172
+ <p class="text-sm text-gray-600">Our AI detects scenes, objects, colors, and movements to understand your video's content.</p>
173
+ </div>
174
+ <div class="bg-white p-4 rounded-lg shadow">
175
+ <div class="text-indigo-600 mb-2">
176
+ <i class="fas fa-book text-2xl"></i>
177
+ </div>
178
+ <h4 class="font-medium text-gray-800 mb-1">3. Story Generation</h4>
179
+ <p class="text-sm text-gray-600">A unique story is crafted based on the analysis, tailored to your selected style.</p>
180
+ </div>
181
+ </div>
182
+ </div>
183
+
184
+ <footer class="text-center text-gray-500 text-sm mt-12">
185
+ <p>Video Story Generator &copy; 2023 | All uploaded videos are processed locally in your browser</p>
186
+ </footer>
187
+ </div>
188
+
189
+ <script>
190
+ document.addEventListener('DOMContentLoaded', function() {
191
+ // DOM elements
192
+ const dropzone = document.getElementById('dropzone');
193
+ const videoUpload = document.getElementById('video-upload');
194
+ const videoPreview = document.getElementById('video-preview');
195
+ const videoPreviewContainer = document.getElementById('video-preview-container');
196
+ const videoName = document.getElementById('video-name');
197
+ const removeVideoBtn = document.getElementById('remove-video');
198
+ const uploadContent = document.getElementById('upload-content');
199
+ const generateBtn = document.getElementById('generate-btn');
200
+ const storyPlaceholder = document.getElementById('story-placeholder');
201
+ const loadingIndicator = document.getElementById('loading-indicator');
202
+ const storyContainer = document.getElementById('story-container');
203
+ const storyTitle = document.getElementById('story-title');
204
+ const storyContent = document.getElementById('story-content');
205
+ const storyActions = document.getElementById('story-actions');
206
+ const copyStoryBtn = document.getElementById('copy-story');
207
+ const downloadStoryBtn = document.getElementById('download-story');
208
+ const storyStyleSelect = document.getElementById('story-style');
209
+ const characterNameInput = document.getElementById('character-name');
210
+
211
+ // Drag and drop events
212
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
213
+ dropzone.addEventListener(eventName, preventDefaults, false);
214
+ });
215
+
216
+ function preventDefaults(e) {
217
+ e.preventDefault();
218
+ e.stopPropagation();
219
+ }
220
+
221
+ ['dragenter', 'dragover'].forEach(eventName => {
222
+ dropzone.addEventListener(eventName, highlight, false);
223
+ });
224
+
225
+ ['dragleave', 'drop'].forEach(eventName => {
226
+ dropzone.addEventListener(eventName, unhighlight, false);
227
+ });
228
+
229
+ function highlight() {
230
+ dropzone.classList.add('active');
231
+ }
232
+
233
+ function unhighlight() {
234
+ dropzone.classList.remove('active');
235
+ }
236
+
237
+ dropzone.addEventListener('drop', handleDrop, false);
238
+ dropzone.addEventListener('click', () => videoUpload.click());
239
+
240
+ function handleDrop(e) {
241
+ const dt = e.dataTransfer;
242
+ const files = dt.files;
243
+ if (files.length) {
244
+ handleFiles(files);
245
+ }
246
+ }
247
+
248
+ videoUpload.addEventListener('change', function() {
249
+ if (this.files.length) {
250
+ handleFiles(this.files);
251
+ }
252
+ });
253
+
254
+ function handleFiles(files) {
255
+ const file = files[0];
256
+ if (!file.type.match('video.*')) {
257
+ alert('Please upload a video file (MP4, MOV, AVI)');
258
+ return;
259
+ }
260
+
261
+ // Check file size (simulated)
262
+ if (file.size > 100 * 1024 * 1024) { // 100MB
263
+ alert('File is too large. Maximum size is 100MB.');
264
+ return;
265
+ }
266
+
267
+ // Display video preview
268
+ const videoURL = URL.createObjectURL(file);
269
+ videoPreview.src = videoURL;
270
+ videoName.textContent = file.name;
271
+ videoPreviewContainer.classList.remove('hidden');
272
+ uploadContent.classList.add('hidden');
273
+ generateBtn.disabled = false;
274
+
275
+ // Load metadata to get duration
276
+ videoPreview.onloadedmetadata = function() {
277
+ console.log(`Video duration: ${videoPreview.duration} seconds`);
278
+ };
279
+ }
280
+
281
+ removeVideoBtn.addEventListener('click', function() {
282
+ videoPreview.src = '';
283
+ videoPreviewContainer.classList.add('hidden');
284
+ uploadContent.classList.remove('hidden');
285
+ generateBtn.disabled = true;
286
+ videoUpload.value = '';
287
+ resetStoryOutput();
288
+ });
289
+
290
+ generateBtn.addEventListener('click', function() {
291
+ if (!videoPreview.src) return;
292
+
293
+ // Show loading state
294
+ storyPlaceholder.classList.add('hidden');
295
+ loadingIndicator.classList.remove('hidden');
296
+ storyContainer.classList.add('hidden');
297
+ storyActions.classList.add('hidden');
298
+ generateBtn.disabled = true;
299
+ generateBtn.innerHTML = '<i class="fas fa-cog fa-spin mr-2"></i> Generating...';
300
+
301
+ // Simulate analysis and story generation (in a real app, this would be an API call)
302
+ setTimeout(() => {
303
+ generateStory();
304
+ }, 3000);
305
+ });
306
+
307
+ function generateStory() {
308
+ // Get user inputs
309
+ const storyStyle = storyStyleSelect.value;
310
+ const characterName = characterNameInput.value.trim() || getRandomName();
311
+
312
+ // Simulate video analysis (in a real app, this would analyze actual video content)
313
+ const simulatedAnalysis = simulateVideoAnalysis();
314
+
315
+ // Generate story based on style and analysis
316
+ const story = createStory(simulatedAnalysis, storyStyle, characterName);
317
+
318
+ // Display the story
319
+ storyTitle.textContent = story.title;
320
+ storyContent.innerHTML = story.content.split('\n').map(para => `<p class="mb-4">${para}</p>`).join('');
321
+
322
+ // Update UI
323
+ loadingIndicator.classList.add('hidden');
324
+ storyContainer.classList.remove('hidden');
325
+ storyActions.classList.remove('hidden');
326
+ generateBtn.disabled = false;
327
+ generateBtn.innerHTML = '<i class="fas fa-magic mr-2"></i> Generate Story';
328
+ }
329
+
330
+ function simulateVideoAnalysis() {
331
+ // This would normally analyze the video content
332
+ // For demo purposes, we return simulated data
333
+ const colorPalette = ['#3b82f6', '#10b981', '#f59e0b', '#ef4444'];
334
+ const dominantColors = [...colorPalette].sort(() => 0.5 - Math.random()).slice(0, 3);
335
+
336
+ const possibleScenes = [
337
+ 'a person walking through a forest',
338
+ 'a city skyline at sunset',
339
+ 'waves crashing on a beach',
340
+ 'a busy street with cars and pedestrians',
341
+ 'a close-up of a face with emotional expression',
342
+ 'a group of animals interacting',
343
+ 'a slow panning shot of mountains',
344
+ 'raindrops falling on a window'
345
+ ];
346
+
347
+ const possibleMoods = [
348
+ 'peaceful and serene',
349
+ 'tense and dramatic',
350
+ 'joyful and energetic',
351
+ 'mysterious and suspenseful',
352
+ 'melancholic and reflective'
353
+ ];
354
+
355
+ return {
356
+ dominantColors,
357
+ primaryScene: possibleScenes[Math.floor(Math.random() * possibleScenes.length)],
358
+ secondaryScene: possibleScenes[Math.floor(Math.random() * possibleScenes.length)],
359
+ mood: possibleMoods[Math.floor(Math.random() * possibleMoods.length)],
360
+ movementLevel: Math.random() > 0.5 ? 'high' : 'low',
361
+ containsFaces: Math.random() > 0.5,
362
+ containsNature: Math.random() > 0.7,
363
+ containsUrban: Math.random() > 0.7
364
+ };
365
+ }
366
+
367
+ function createStory(analysis, style, characterName) {
368
+ const styles = {
369
+ fantasy: {
370
+ title: `The ${characterName}'s Enchanted Journey`,
371
+ templates: [
372
+ `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'}.`,
373
+ `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'}.`,
374
+ `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'}.`
375
+ ]
376
+ },
377
+ scifi: {
378
+ title: `${characterName} and the ${analysis.dominantColors[1].slice(1)} Signal`,
379
+ templates: [
380
+ `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.`,
381
+ `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.`,
382
+ `"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'}.`
383
+ ]
384
+ },
385
+ romance: {
386
+ title: `${characterName}'s ${analysis.dominantColors[2].slice(1)} Heart`,
387
+ templates: [
388
+ `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.`,
389
+ `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.`,
390
+ `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.`
391
+ ]
392
+ },
393
+ comedy: {
394
+ title: `The ${analysis.dominantColors[0].slice(1)} Fiasco of ${characterName}`,
395
+ templates: [
396
+ `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'}.`,
397
+ `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'}.`,
398
+ `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'}.`
399
+ ]
400
+ },
401
+ horror: {
402
+ title: `The ${analysis.dominantColors[0].slice(1)} Whisper`,
403
+ templates: [
404
+ `${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'}.`,
405
+ `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'}.`,
406
+ `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'}.`
407
+ ]
408
+ },
409
+ realistic: {
410
+ title: `${characterName}'s ${analysis.primaryScene.includes('city') ? 'Urban' : 'Personal'} Journey`,
411
+ templates: [
412
+ `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'}.`,
413
+ `${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'}.`,
414
+ `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'}.`
415
+ ]
416
+ }
417
+ };
418
+
419
+ const selectedStyle = styles[style] || styles.fantasy;
420
+ return {
421
+ title: selectedStyle.title,
422
+ content: selectedStyle.templates.join('\n\n')
423
+ };
424
+ }
425
+
426
+ function getRandomName() {
427
+ const names = ['Alex', 'Morgan', 'Jordan', 'Taylor', 'Casey', 'Riley', 'Jamie', 'Quinn', 'Dakota', 'Skyler'];
428
+ return names[Math.floor(Math.random() * names.length)];
429
+ }
430
+
431
+ function resetStoryOutput() {
432
+ storyPlaceholder.classList.remove('hidden');
433
+ loadingIndicator.classList.add('hidden');
434
+ storyContainer.classList.add('hidden');
435
+ storyActions.classList.add('hidden');
436
+ storyTitle.textContent = '';
437
+ storyContent.textContent = '';
438
+ }
439
+
440
+ // Copy story to clipboard
441
+ copyStoryBtn.addEventListener('click', function() {
442
+ const storyText = `${storyTitle.textContent}\n\n${storyContent.textContent}`;
443
+ navigator.clipboard.writeText(storyText).then(() => {
444
+ const originalText = copyStoryBtn.innerHTML;
445
+ copyStoryBtn.innerHTML = '<i class="fas fa-check mr-2"></i> Copied!';
446
+ setTimeout(() => {
447
+ copyStoryBtn.innerHTML = originalText;
448
+ }, 2000);
449
+ });
450
+ });
451
+
452
+ // Download story as text file
453
+ downloadStoryBtn.addEventListener('click', function() {
454
+ const storyText = `${storyTitle.textContent}\n\n${storyContent.textContent}`;
455
+ const blob = new Blob([storyText], { type: 'text/plain' });
456
+ const url = URL.createObjectURL(blob);
457
+ const a = document.createElement('a');
458
+ a.href = url;
459
+ a.download = `${storyTitle.textContent.replace(/\s+/g, '_')}.txt`;
460
+ document.body.appendChild(a);
461
+ a.click();
462
+ document.body.removeChild(a);
463
+ URL.revokeObjectURL(url);
464
+ });
465
+ });
466
+ </script>
467
+ <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>
468
+ </html>