pranit144 commited on
Commit
09d8270
·
verified ·
1 Parent(s): 856dcce

Upload 15 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,10 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ static/images/1.png filter=lfs diff=lfs merge=lfs -text
37
+ static/videos/Animated_Drought_Relief_Video_Creation.mp4 filter=lfs diff=lfs merge=lfs -text
38
+ static/videos/Animated_Rainwater_Harvesting_Video.mp4 filter=lfs diff=lfs merge=lfs -text
39
+ static/videos/Animated_Rainwater_Usage_Video.mp4 filter=lfs diff=lfs merge=lfs -text
40
+ static/videos/Animated_Water_Filtration_Process_Video.mp4 filter=lfs diff=lfs merge=lfs -text
41
+ static/videos/Water_Filtration_Animation_Ready.mp4 filter=lfs diff=lfs merge=lfs -text
42
+ static/videos/Water_Hero_Video_Generation_Complete.mp4 filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Base image
2
+ FROM python:3.9-slim
3
+
4
+ # Set the working directory
5
+ WORKDIR /app
6
+
7
+ # Copy application files
8
+ COPY market_trial /app
9
+
10
+ # Install dependencies
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ # Expose the port your app runs on
14
+ EXPOSE 7860
15
+
16
+ # Command to run the application
17
+ CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:7860", "app:app"]
app.py ADDED
@@ -0,0 +1,420 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, session, redirect, url_for, request
2
+ import time
3
+ from pathlib import Path
4
+ import random
5
+
6
+ app = Flask(__name__)
7
+ # Set a secret key for session management. CHANGE THIS IN PRODUCTION!
8
+ app.secret_key = 'your_super_secret_key_for_flask_session_security'
9
+
10
+ # --- Game Data ---
11
+ # Updated video paths to be relative to the 'static/videos' directory
12
+ videos = [
13
+ "static/videos/Animated_Drought_Relief_Video_Creation.mp4",
14
+ "static/videos/Animated_Rainwater_Harvesting_Video.mp4",
15
+ "static/videos/Animated_Water_Filtration_Process_Video.mp4",
16
+ "static/videos/Water_Filtration_Animation_Ready.mp4",
17
+ "static/videos/Animated_Rainwater_Usage_Video.mp4",
18
+ "static/videos/Water_Hero_Video_Generation_Complete.mp4"
19
+ ]
20
+
21
+
22
+ video_descriptions = [
23
+ "🌍 Water Crisis Alert",
24
+ "🏠 Collection Master",
25
+ "💧 Purification Expert",
26
+ "🗃️ Storage Specialist",
27
+ "♻️ Usage Strategist",
28
+ "🌱 Environmental Champion"
29
+ ]
30
+
31
+ # Enhanced task cards with detailed scenarios and consequences
32
+ task_scenarios = [
33
+ # Video 0 - Water Crisis (has question)
34
+ {
35
+ "question": "🌍 After watching the video, what is the FIRST step to address water scarcity at your home?",
36
+ "choices": [
37
+ {
38
+ "action": "🌧️ Start collecting rainwater immediately",
39
+ "description": "Begin harvesting nature's free water supply",
40
+ "consequence": "✅ SMART START! You're taking the first step toward water independence! +100 XP",
41
+ "xp_reward": 100,
42
+ "icon": "🏆"
43
+ },
44
+ {
45
+ "action": "🚿 Take longer showers to enjoy water while it lasts",
46
+ "description": "Use more water before it runs out",
47
+ "consequence": "❌ WRONG APPROACH! This worsens the problem. Conservation is key! -20 Health",
48
+ "xp_reward": 0,
49
+ "icon": "💸"
50
+ },
51
+ {
52
+ "action": "⏳ Wait for government to solve the problem",
53
+ "description": "Rely on authorities to fix water issues",
54
+ "consequence": "❌ MISSED OPPORTUNITY! Individual action is crucial. Start now! -20 Health",
55
+ "xp_reward": 0,
56
+ "icon": "⏰"
57
+ }
58
+ ]
59
+ },
60
+ # Video 1 - Collection
61
+ {
62
+ "question": "🌧️ It's raining heavily! Your house has a metal roof. What's your FIRST action as a Water Conservation Hero?",
63
+ "choices": [
64
+ {
65
+ "action": "🏠 Install gutters and downspouts on your roof",
66
+ "description": "Channel rainwater efficiently into collection system",
67
+ "consequence": "✅ SMART MOVE! You capture 80% of the rainfall from your roof area. +100 XP",
68
+ "xp_reward": 100,
69
+ "icon": "🏆"
70
+ },
71
+ {
72
+ "action": "🛣️ Let the water flow down the streets",
73
+ "description": "Allow natural drainage to carry water away",
74
+ "consequence": "❌ MISSED OPPORTUNITY! Thousands of liters wasted into storm drains. -20 Health",
75
+ "xp_reward": 0,
76
+ "icon": "💸"
77
+ },
78
+ {
79
+ "action": "🛍️ Place plastic bags under the roof edge",
80
+ "description": "Use bags to catch dripping water",
81
+ "consequence": "❌ INEFFICIENT! Bags tear easily and collect very little water. Try a proper system! -20 Health",
82
+ "xp_reward": 0,
83
+ "icon": "🗑️"
84
+ }
85
+ ]
86
+ },
87
+ # Video 2 - Filtration
88
+ {
89
+ "question": "💧 Your collection tank is filling up with rainwater, but you notice leaves and debris floating in it. What's your next critical step?",
90
+ "choices": [
91
+ {
92
+ "action": "🔍 Install a first-flush diverter and filtration system",
93
+ "description": "Remove initial dirty water and filter the rest",
94
+ "consequence": "✅ EXCELLENT CHOICE! Clean, safe water ready for storage. Your filtration efficiency: 95%! +100 XP",
95
+ "xp_reward": 100,
96
+ "icon": "🧪"
97
+ },
98
+ {
99
+ "action": "⚠️ Use the dirty water directly without treatment",
100
+ "description": "Proceed with unfiltered rainwater",
101
+ "consequence": "❌ HEALTH HAZARD! Contaminated water can cause serious illness. Always filter first! -20 Health",
102
+ "xp_reward": 0,
103
+ "icon": "☠️"
104
+ },
105
+ {
106
+ "action": "🧪 Add household bleach to kill germs",
107
+ "description": "Chemical treatment of collected water",
108
+ "consequence": "❌ WRONG CHEMICAL! Bleach is not safe for water treatment. Use proper filtration! -20 Health",
109
+ "xp_reward": 0,
110
+ "icon": "⚗️"
111
+ }
112
+ ]
113
+ },
114
+ # Video 3 - Storage
115
+ {
116
+ "question": "🏺 You have 500 liters of clean, filtered rainwater. The monsoon season is ending soon. Where should you store this precious resource?",
117
+ "choices": [
118
+ {
119
+ "action": "🏺 Install an underground concrete tank with cover",
120
+ "description": "Professional storage solution with temperature control",
121
+ "consequence": "✅ MASTER STRATEGY! Water stays cool, clean, and safe for months. Storage efficiency: 100%! +100 XP",
122
+ "xp_reward": 100,
123
+ "icon": "🏛️"
124
+ },
125
+ {
126
+ "action": "🪣 Store in open buckets around your yard",
127
+ "description": "Multiple container approach for easy access",
128
+ "consequence": "❌ BREEDING GROUND! Open water attracts mosquitoes and gets contaminated. -20 Health",
129
+ "xp_reward": 0,
130
+ "icon": "🦟"
131
+ },
132
+ {
133
+ "action": "🌍 Dig a pit and let water sit directly in ground",
134
+ "description": "Natural ground storage method",
135
+ "consequence": "❌ CONTAMINATION RISK! Soil bacteria and insects will pollute your water. -20 Health",
136
+ "xp_reward": 0,
137
+ "icon": "🕳️"
138
+ }
139
+ ]
140
+ },
141
+ # Video 4 - Usage
142
+ {
143
+ "question": "♻️ Dry season has arrived! You have 200 liters of stored rainwater left. Your neighbor is asking for help with their garden. How do you maximize the impact?",
144
+ "choices": [
145
+ {
146
+ "action": "🌿 Use for drip irrigation and toilet flushing",
147
+ "description": "Strategic usage for maximum water conservation",
148
+ "consequence": "✅ WATER WISDOM! You save 300L of municipal water and help the environment! +100 XP",
149
+ "xp_reward": 100,
150
+ "icon": "🌱"
151
+ },
152
+ {
153
+ "action": "⏰ Save all water for potential emergencies later",
154
+ "description": "Conservative approach for future needs",
155
+ "consequence": "❌ MISSED IMPACT! Water can spoil if stored too long. Use it wisely now! -20 Health",
156
+ "xp_reward": 0,
157
+ "icon": "⏰"
158
+ },
159
+ {
160
+ "action": "🚽 Mix with sewage water for disposal",
161
+ "description": "Combine with waste water system",
162
+ "consequence": "❌ TERRIBLE WASTE! Never contaminate clean water with sewage! -20 Health",
163
+ "xp_reward": 0,
164
+ "icon": "🤮"
165
+ }
166
+ ]
167
+ },
168
+ # Video 5 - Environmental Impact (has question)
169
+ {
170
+ "question": "🌱 Now that you've mastered rainwater harvesting, how will you inspire your community to join the movement?",
171
+ "choices": [
172
+ {
173
+ "action": "👥 Organize community workshops and share your knowledge",
174
+ "description": "Teach others about water conservation benefits",
175
+ "consequence": "✅ COMMUNITY HERO! You inspire 50 families to start rainwater harvesting! +100 XP",
176
+ "xp_reward": 100,
177
+ "icon": "🌟"
178
+ },
179
+ {
180
+ "action": "🤐 Keep the knowledge to yourself for competitive advantage",
181
+ "description": "Maintain exclusive access to water conservation techniques",
182
+ "consequence": "❌ SELFISH APPROACH! Environmental problems need collective solutions! -20 Health",
183
+ "xp_reward": 0,
184
+ "icon": "🚫"
185
+ },
186
+ {
187
+ "action": "📱 Only post about it on social media occasionally",
188
+ "description": "Share water conservation tips online when convenient",
189
+ "consequence": "❌ MINIMAL IMPACT! Real change needs active community engagement! -20 Health",
190
+ "xp_reward": 0,
191
+ "icon": "📱"
192
+ }
193
+ ]
194
+ }
195
+ ]
196
+
197
+ # Achievement definitions
198
+ achievements_list = [
199
+ {"name": "🌟 First Steps", "desc": "Started your water journey", "condition": "start"},
200
+ {"name": "💧 Drop Collector", "desc": "Learned about collection", "condition": "video_1"},
201
+ {"name": "🔬 Purification Pro", "desc": "Mastered filtration", "condition": "video_2"},
202
+ {"name": "🏺 Storage Sage", "desc": "Understood storage methods", "condition": "video_3"},
203
+ {"name": "♻️ Usage Expert", "desc": "Optimized water usage", "condition": "video_4"},
204
+ {"name": "🎯 Perfect Score", "desc": "100% accuracy achieved", "condition": "perfect"},
205
+ {"name": "🔥 On Fire", "desc": "3 correct answers in a row", "condition": "streak_3"},
206
+ {"name": "🌍 Water Hero", "desc": "Completed the full quest", "condition": "complete"}
207
+ ]
208
+
209
+
210
+ # --- Helper Functions (adapted for Flask session) ---
211
+ def calculate_level() -> int:
212
+ return min(10, 1 + session.get('xp_points', 0) // 100)
213
+
214
+
215
+ def add_xp(amount: int):
216
+ old_level = session.get('player_level', 1)
217
+ session['xp_points'] = session.get('xp_points', 0) + amount
218
+ session['player_level'] = calculate_level()
219
+ if session['player_level'] > old_level:
220
+ session['message'] = f"🎉 LEVEL UP! You reached Level {session['player_level']}!"
221
+
222
+
223
+ def unlock_achievement(condition: str):
224
+ for achievement in achievements_list:
225
+ if achievement["condition"] == condition and achievement not in session.get('achievements', []):
226
+ session['achievements'] = session.get('achievements', []) + [achievement]
227
+ session['message'] = f"🏆 Achievement Unlocked: {achievement['name']} - {achievement['desc']}"
228
+ add_xp(50)
229
+
230
+
231
+ def initialize_game_state():
232
+ session.setdefault("video_index", 0)
233
+ session.setdefault("feedback", "")
234
+ session.setdefault("quiz_correct_count", 0)
235
+ session.setdefault("quiz_attempts", 0)
236
+ session.setdefault("player_level", 1)
237
+ session.setdefault("xp_points", 0)
238
+ session.setdefault("achievements", [])
239
+ session.setdefault("streak", 0)
240
+ session.setdefault("health", 100)
241
+ session.setdefault("video_watched", False)
242
+ session.setdefault("question_answered", False)
243
+ session.setdefault("message", "") # For temporary messages like level-up
244
+
245
+
246
+ def next_video_logic():
247
+ if session['video_index'] < len(videos) - 1:
248
+ session['video_index'] += 1
249
+ session['feedback'] = ""
250
+ session['video_watched'] = False
251
+ session['question_answered'] = False
252
+ session['message'] = ""
253
+ if session['video_index'] > 0:
254
+ unlock_achievement(f"video_{session['video_index']}")
255
+ return redirect(url_for('main_app'))
256
+ else:
257
+ unlock_achievement("complete")
258
+ return redirect(url_for('summary_page'))
259
+
260
+
261
+ def mark_video_watched_logic():
262
+ session['video_watched'] = True
263
+ session['message'] = ""
264
+
265
+
266
+ def replay_video_logic():
267
+ session['feedback'] = ""
268
+ session['video_watched'] = False
269
+ session['question_answered'] = False
270
+ session['message'] = "🔄 Video ready for replay! Click play to watch again."
271
+
272
+
273
+ def reset_quest_logic():
274
+ session.clear() # Clears all session data
275
+ initialize_game_state() # Reinitialize with default values
276
+
277
+
278
+ def select_card_logic(choice_index: int):
279
+ current_scenario = task_scenarios[session['video_index']]
280
+ choice = current_scenario["choices"][choice_index]
281
+
282
+ session['quiz_attempts'] = session.get('quiz_attempts', 0) + 1
283
+ session['question_answered'] = True
284
+
285
+ session['feedback'] = choice["consequence"]
286
+ session['message'] = "" # Clear any previous messages
287
+
288
+ if choice["xp_reward"] > 0:
289
+ session['quiz_correct_count'] = session.get('quiz_correct_count', 0) + 1
290
+ session['streak'] = session.get('streak', 0) + 1
291
+ add_xp(choice["xp_reward"])
292
+ if session['streak'] >= 3:
293
+ unlock_achievement("streak_3")
294
+ else:
295
+ session['streak'] = 0
296
+ session['health'] = max(0, session.get('health', 100) - 20)
297
+
298
+
299
+ # --- Flask Routes ---
300
+
301
+ @app.route('/')
302
+ def welcome_page():
303
+ if 'player_level' not in session: # Only initialize if not already in session (e.g., first visit or after full reset)
304
+ initialize_game_state()
305
+ return render_template('welcome.html')
306
+
307
+
308
+ @app.route('/begin_quest', methods=['POST'])
309
+ def begin_quest():
310
+ unlock_achievement("start")
311
+ return redirect(url_for('main_app'))
312
+
313
+
314
+ @app.route('/main', methods=['GET', 'POST'])
315
+ def main_app():
316
+ if 'player_level' not in session: # If somehow landed here without initializing
317
+ return redirect(url_for('welcome_page'))
318
+
319
+ # Handle form submissions
320
+ if request.method == 'POST':
321
+ if 'mark_watched' in request.form:
322
+ mark_video_watched_logic()
323
+ elif 'select_choice' in request.form:
324
+ choice_index = int(request.form['select_choice'])
325
+ select_card_logic(choice_index)
326
+ elif 'next_level' in request.form or 'continue_quest' in request.form:
327
+ return next_video_logic() # This function handles redirection
328
+ elif 'replay_video' in request.form:
329
+ replay_video_logic()
330
+ return redirect(url_for('main_app')) # Redirect to show replay message
331
+ elif 'main_menu' in request.form:
332
+ return redirect(url_for('welcome_page'))
333
+
334
+ return redirect(url_for('main_app')) # Redirect after POST to prevent resubmission
335
+
336
+ current_video_index = session.get('video_index', 0)
337
+ current_video_path = videos[current_video_index]
338
+
339
+ # Determine if it's a local video (starts with 'videos/') or a URL
340
+ is_local_video = current_video_path.startswith('videos/')
341
+ if is_local_video:
342
+ video_src = url_for('static', filename=current_video_path)
343
+ else:
344
+ video_src = current_video_path # It's a full URL
345
+
346
+ # Check if local file exists if it's a local video
347
+ local_file_exists = True
348
+ if is_local_video:
349
+ full_path = Path(app.static_folder) / current_video_path
350
+ if not full_path.exists():
351
+ local_file_exists = False
352
+
353
+ context = {
354
+ 'current_video_index': current_video_index,
355
+ 'total_videos': len(videos),
356
+ 'video_src': video_src,
357
+ 'is_local_video': is_local_video,
358
+ 'local_file_exists': local_file_exists,
359
+ 'video_description': video_descriptions[current_video_index],
360
+ 'current_scenario': task_scenarios[current_video_index],
361
+ 'player_level': session.get('player_level', 1),
362
+ 'xp_points': session.get('xp_points', 0),
363
+ 'health': session.get('health', 100),
364
+ 'streak': session.get('streak', 0),
365
+ 'quiz_correct_count': session.get('quiz_correct_count', 0),
366
+ 'quiz_attempts': session.get('quiz_attempts', 0),
367
+ 'video_watched': session.get('video_watched', False),
368
+ 'question_answered': session.get('question_answered', False),
369
+ 'feedback': session.get('feedback', ''),
370
+ 'achievements': session.get('achievements', []),
371
+ 'message': session.pop('message', '') # Pop message so it only shows once
372
+ }
373
+ return render_template('main.html', **context)
374
+
375
+
376
+ @app.route('/summary')
377
+ def summary_page():
378
+ if 'player_level' not in session:
379
+ return redirect(url_for('welcome_page'))
380
+
381
+ total_quizzes = len([s for s in task_scenarios if s.get("choices")])
382
+ score = session.get('quiz_correct_count', 0)
383
+
384
+ if score == total_quizzes and total_quizzes > 0:
385
+ unlock_achievement("perfect")
386
+
387
+ context = {
388
+ 'player_level': session.get('player_level', 1),
389
+ 'xp_points': session.get('xp_points', 0),
390
+ 'quiz_score': score,
391
+ 'total_quizzes': total_quizzes,
392
+ 'accuracy': (score / max(1, total_quizzes)) * 100,
393
+ 'streak': session.get('streak', 0),
394
+ 'achievements': session.get('achievements', []),
395
+ 'current_date': time.strftime("%B %d, %Y"),
396
+ 'message': session.pop('message', '')
397
+ }
398
+ return render_template('summary.html', **context)
399
+
400
+
401
+ @app.route('/restart_quest', methods=['POST'])
402
+ def restart_quest():
403
+ reset_quest_logic()
404
+ return redirect(url_for('welcome_page'))
405
+
406
+
407
+ @app.route('/view_stats', methods=['POST'])
408
+ def view_stats():
409
+ session['message'] = "Detailed stats coming soon!"
410
+ return redirect(url_for('summary_page'))
411
+
412
+
413
+ @app.route('/share_impact', methods=['POST'])
414
+ def share_impact():
415
+ session['message'] = "🌟 Your impact is shared!"
416
+ return redirect(url_for('summary_page'))
417
+
418
+
419
+ if __name__ == '__main__':
420
+ app.run(debug=True) # Set debug=False in production
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ flask
2
+ gunicorn
3
+ requests
4
+ pandas
5
+ numpy
6
+ plotly
7
+ googletrans
8
+ python-dotenv
static/images/1.png ADDED

Git LFS Details

  • SHA256: f0f74aafdc4c2f65b43a047ff7e47847f51bd0e65088fa1d739a52819de91a37
  • Pointer size: 131 Bytes
  • Size of remote file: 117 kB
static/videos/Animated_Drought_Relief_Video_Creation.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a4300c3ec805f611a5abe09b7f94bf7ccd9d5d30cedb286b418d475833e0f48e
3
+ size 2952339
static/videos/Animated_Rainwater_Harvesting_Video.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:15f59778be780e21942f809d786c30bbac2d93ff0e6dd8ed911630a4638e6645
3
+ size 9256576
static/videos/Animated_Rainwater_Usage_Video.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:093c9c1c02ad7f672b99070a0256d60867d81ff20e25e7d2ff2757de39be0a4c
3
+ size 2632673
static/videos/Animated_Water_Filtration_Process_Video.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:de15525bb8e29dd57ebb69b0692d66491fcc52e80e59408529986eb666fee9cb
3
+ size 3766881
static/videos/Water_Filtration_Animation_Ready.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3f050f1b5dd4ae6398d66ed8c10f70fc03ff1f796374485b30ec50979ce86122
3
+ size 3460781
static/videos/Water_Hero_Video_Generation_Complete.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0a77a3e6bc131896a7939dfcfac02e2cb514f4d4f9dde637750a0c81b5eceacb
3
+ size 4969869
templates/base.html ADDED
@@ -0,0 +1,540 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Rainwater Chronicles</title>
7
+ <style>
8
+ /* Animated Game Background */
9
+ body {
10
+ background: linear-gradient(135deg, #232526 0%, #414345 100%);
11
+ min-height: 100vh;
12
+ position: relative;
13
+ overflow-x: hidden;
14
+ }
15
+ body::before {
16
+ content: '';
17
+ position: fixed;
18
+ top: 0; left: 0; right: 0; bottom: 0;
19
+ z-index: 0;
20
+ background: url('https://www.transparenttextures.com/patterns/stardust.png'), linear-gradient(120deg, #232526 0%, #414345 100%);
21
+ opacity: 0.25;
22
+ pointer-events: none;
23
+ animation: bgmove 30s linear infinite alternate;
24
+ }
25
+ @keyframes bgmove {
26
+ 0% { background-position: 0 0, 0 0; }
27
+ 100% { background-position: 200px 200px, 0 0; }
28
+ }
29
+ /* static/style.css */
30
+ /* Import Google Fonts */
31
+ @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
32
+
33
+ /* Global Styles */
34
+ body {
35
+ font-family: 'Poppins', 'Press Start 2P', 'Orbitron', sans-serif;
36
+ margin: 0;
37
+ background: none;
38
+ color: #e0e6f8;
39
+ line-height: 1.6; /* Improve readability */
40
+ }
41
+
42
+ /* Main Container - Desktop First */
43
+ .main-container {
44
+ display: flex;
45
+ min-height: 100vh;
46
+ }
47
+
48
+ /* Sidebar - Desktop First */
49
+ .sidebar {
50
+ width: 280px;
51
+ background: linear-gradient(180deg, #0f2027 0%, #2c5364 100%);
52
+ padding: 2rem;
53
+ color: #fff;
54
+ box-shadow: 2px 0 20px rgba(44,83,100,0.25), 0 0 20px 2px #00ffe7a0 inset;
55
+ position: sticky;
56
+ top: 0;
57
+ height: 100vh;
58
+ overflow-y: auto;
59
+ flex-shrink: 0; /* Prevent sidebar from shrinking */
60
+ }
61
+
62
+ .sidebar h3 {
63
+ color: white;
64
+ margin-bottom: 1.5rem;
65
+ font-weight: 700;
66
+ font-size: 1.7rem;
67
+ letter-spacing: 1px;
68
+ text-shadow: 0 2px 8px #00ffe7a0;
69
+ text-align: center; /* Center header in sidebar */
70
+ }
71
+
72
+ .sidebar select,
73
+ .sidebar button {
74
+ width: 100%;
75
+ padding: 0.75rem;
76
+ margin-bottom: 1rem;
77
+ border-radius: 8px;
78
+ border: none;
79
+ font-family: 'Poppins', sans-serif;
80
+ font-size: 1rem;
81
+ cursor: pointer;
82
+ transition: all 0.2s ease;
83
+ box-sizing: border-box; /* Include padding and border in the element's total width and height */
84
+ }
85
+
86
+ .sidebar select {
87
+ background-color: rgba(0, 255, 231, 0.15);
88
+ color: #fff;
89
+ appearance: none;
90
+ background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23ffffff%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13.6-6.4H18.9c-5%200-9.6%202-13.6%206.4-8.8%208.8-8.8%2023.2%200%2032l128%20128c4.8%204.8%2011.2%207.2%2017.6%207.2s12.8-2.4%2017.6-7.2l128-128c8.8-8.8%208.8-23.2%200-32z%22%2F%3E%3C%2Fsvg%3E');
91
+ background-repeat: no-repeat;
92
+ background-position: right 0.7em top 50%, 0 0;
93
+ background-size: 0.65em auto, 100%;
94
+ }
95
+
96
+ .sidebar select option {
97
+ background: #232526;
98
+ color: #00ffe7;
99
+ }
100
+
101
+ .sidebar button {
102
+ background: linear-gradient(90deg, #00ffe7 0%, #43e97b 100%);
103
+ color: #232526;
104
+ font-weight: 700;
105
+ box-shadow: 0 0 10px #00ffe7a0;
106
+ font-weight: 500;
107
+ }
108
+
109
+ .sidebar button:hover {
110
+ background: #43e97b;
111
+ color: #232526;
112
+ }
113
+
114
+ .sidebar hr {
115
+ border: 0;
116
+ height: 1px;
117
+ background: rgba(255, 255, 255, 0.3);
118
+ margin: 1.5rem 0;
119
+ }
120
+
121
+ /* Main Content Area - Desktop First */
122
+ .content {
123
+ flex-grow: 1;
124
+ padding: 2rem 4rem;
125
+ max-width: 1000px;
126
+ margin: 0 auto;
127
+ box-sizing: border-box;
128
+ }
129
+
130
+ /* Header Styles */
131
+ .app-header {
132
+ background: linear-gradient(135deg, #00ffe7 0%, #43e97b 100%);
133
+ padding: 2rem;
134
+ border-radius: 15px;
135
+ color: #232526;
136
+ text-align: center;
137
+ margin-bottom: 2rem;
138
+ box-shadow: 0 10px 30px rgba(0,0,0,0.1);
139
+ }
140
+
141
+ .app-title {
142
+ font-size: 2.5rem;
143
+ font-weight: 700;
144
+ margin-bottom: 0.5rem;
145
+ text-shadow: 0 2px 8px #00ffe7a0, 2px 2px 4px rgba(0,0,0,0.3);
146
+ }
147
+
148
+ .app-subtitle {
149
+ font-size: 1.2rem;
150
+ font-weight: 300;
151
+ opacity: 0.9;
152
+ }
153
+
154
+ /* Story Card Styles */
155
+ .story-card {
156
+ background: rgba(44,83,100,0.95);
157
+ border-radius: 20px;
158
+ padding: 2.2rem;
159
+ box-shadow: 0 8px 40px 0 #00ffe7a0, 0 2px 8px rgba(0,0,0,0.2);
160
+ border-left: 6px solid #00ffe7;
161
+ margin-bottom: 2rem;
162
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
163
+ }
164
+
165
+ .story-card:hover {
166
+ transform: translateY(-5px);
167
+ box-shadow: 0 15px 35px rgba(0,0,0,0.15);
168
+ }
169
+
170
+ .story-title {
171
+ color: #00ffe7;
172
+ font-size: 1.8rem;
173
+ font-weight: 600;
174
+ margin-bottom: 1rem;
175
+ display: flex;
176
+ align-items: center;
177
+ gap: 10px;
178
+ flex-wrap: wrap; /* Allow title to wrap on smaller screens */
179
+ }
180
+
181
+ .story-title span {
182
+ white-space: nowrap; /* Prevent "Scene X of Y" from breaking awkwardly */
183
+ }
184
+
185
+ .story-content-wrapper {
186
+ background: white;
187
+ border-radius: 15px;
188
+ padding: 2rem;
189
+ box-shadow: 0 8px 25px rgba(0,0,0,0.1);
190
+ margin: 1rem 0;
191
+ line-height: 1.8;
192
+ font-size: 1.1rem;
193
+ color: #e0e6f8;
194
+ text-align: justify;
195
+ }
196
+
197
+ /* Navigation Styles */
198
+ .nav-container {
199
+ display: flex;
200
+ justify-content: space-between;
201
+ align-items: center;
202
+ padding: 1rem;
203
+ margin: 1rem 0;
204
+ }
205
+
206
+ .progress-bar-container {
207
+ width: 100%;
208
+ height: 8px;
209
+ background: rgba(0, 0, 0, 0.1);
210
+ border-radius: 4px;
211
+ overflow: hidden;
212
+ margin: 1rem 0;
213
+ }
214
+
215
+ .progress-fill {
216
+ height: 100%;
217
+ background: linear-gradient(90deg, #00ffe7 0%, #43e97b 100%);
218
+ border-radius: 4px;
219
+ transition: width 0.3s ease;
220
+ }
221
+
222
+ /* Button Styles (Navigation, Audio) */
223
+ .btn-group {
224
+ display: flex;
225
+ gap: 1rem;
226
+ justify-content: center;
227
+ margin-top: 2rem;
228
+ flex-wrap: wrap; /* Allow buttons to wrap */
229
+ }
230
+
231
+ .btn-group button {
232
+ flex: 1;
233
+ min-width: 120px; /* Ensure buttons don't get too small */
234
+ max-width: 150px;
235
+ }
236
+
237
+ .btn {
238
+ background: linear-gradient(90deg, #00ffe7 0%, #43e97b 100%);
239
+ box-shadow: 0 0 16px #00ffe7a0, 0 2px 8px rgba(0,0,0,0.2);
240
+ font-family: 'Orbitron', 'Poppins', sans-serif;
241
+ font-size: 1.1rem;
242
+ border: none;
243
+ color: white;
244
+ padding: 0.75rem 1.5rem;
245
+ border-radius: 25px;
246
+ cursor: pointer;
247
+ font-weight: 500;
248
+ transition: all 0.3s ease;
249
+ box-shadow: 0 4px 15px rgba(67,233,123,0.3);
250
+ text-decoration: none;
251
+ display: inline-block;
252
+ text-align: center;
253
+ box-sizing: border-box; /* Crucial for consistent sizing */
254
+ }
255
+
256
+ .btn:hover {
257
+ transform: translateY(-2px);
258
+ box-shadow: 0 6px 20px rgba(67,233,123,0.4);
259
+ }
260
+
261
+ .btn:disabled {
262
+ opacity: 0.6;
263
+ cursor: not-allowed;
264
+ box-shadow: none;
265
+ transform: none;
266
+ }
267
+
268
+ .nav-btn {
269
+ background: linear-gradient(90deg, #f093fb 0%, #f5576c 100%);
270
+ box-shadow: 0 0 16px #f093fba0, 0 2px 8px rgba(0,0,0,0.2);
271
+ }
272
+
273
+ .nav-btn:hover:not(:disabled) {
274
+ background: linear-gradient(90deg, #f5576c 0%, #f093fb 100%);
275
+ box-shadow: 0 0 24px #f093fba0, 0 2px 8px rgba(0,0,0,0.2);
276
+ }
277
+
278
+ /* Audio Button */
279
+ .audio-button-container {
280
+ margin: 1rem 0;
281
+ text-align: center;
282
+ }
283
+ .audio-btn {
284
+ background: linear-gradient(90deg, #00ffe7 0%, #43e97b 100%);
285
+ border: none;
286
+ color: white;
287
+ padding: 0.75rem 1.5rem;
288
+ border-radius: 25px;
289
+ cursor: pointer;
290
+ font-weight: 500;
291
+ transition: all 0.3s ease;
292
+ box-shadow: 0 4px 15px rgba(67,233,123,0.3);
293
+ width: auto;
294
+ display: inline-block;
295
+ }
296
+
297
+ .audio-btn:hover {
298
+ transform: translateY(-2px);
299
+ box-shadow: 0 6px 20px rgba(67,233,123,0.4);
300
+ }
301
+
302
+ .audio-btn.playing {
303
+ background: linear-gradient(90deg, #ff6b6b 0%, #feca57 100%);
304
+ box-shadow: 0 0 16px #ff6b6ba0, 0 2px 8px rgba(0,0,0,0.2);
305
+ }
306
+
307
+ /* Footer */
308
+ .footer {
309
+ text-align: center;
310
+ padding: 2rem;
311
+ color: #00ffe7;
312
+ margin-top: 4rem;
313
+ border-top: 1px solid #232526;
314
+ font-size: 0.9em;
315
+ }
316
+
317
+
318
+ /* --- Mobile Responsiveness --- */
319
+
320
+ /* For screens smaller than 768px (common breakpoint for tablets/laptops to phones) */
321
+ @media (max-width: 768px) {
322
+ .main-container {
323
+ flex-direction: column; /* Stack sidebar and content vertically */
324
+ }
325
+
326
+ .sidebar {
327
+ width: 100%; /* Sidebar takes full width */
328
+ height: auto; /* Allow height to adjust to content */
329
+ position: relative; /* Remove sticky behavior for mobile */
330
+ padding: 1.5rem 1rem; /* Adjust padding */
331
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
332
+ order: -1; /* Place sidebar at the top of the stack */
333
+ }
334
+
335
+ .sidebar h3 {
336
+ font-size: 1.3rem;
337
+ margin-bottom: 1rem;
338
+ }
339
+
340
+ .sidebar select,
341
+ .sidebar button {
342
+ font-size: 0.95rem;
343
+ padding: 0.6rem 1rem;
344
+ }
345
+
346
+ .sidebar p {
347
+ font-size: 0.8em;
348
+ }
349
+
350
+ .content {
351
+ padding: 1.5rem 1rem; /* Adjust content padding for smaller screens */
352
+ max-width: 100%; /* No max-width restriction on small screens */
353
+ }
354
+
355
+ .app-header {
356
+ padding: 1.5rem;
357
+ margin-bottom: 1.5rem;
358
+ border-radius: 10px; /* Slightly smaller radius */
359
+ }
360
+
361
+ .app-title {
362
+ font-size: 2rem; /* Reduce font size for smaller screens */
363
+ }
364
+
365
+ .app-subtitle {
366
+ font-size: 1rem; /* Reduce font size */
367
+ }
368
+
369
+ .story-card {
370
+ padding: 1.5rem;
371
+ border-radius: 10px;
372
+ margin-bottom: 1.5rem;
373
+ }
374
+
375
+ .story-title {
376
+ font-size: 1.5rem; /* Adjust story title size */
377
+ flex-direction: column; /* Stack emoji/title and scene count */
378
+ align-items: flex-start;
379
+ gap: 5px;
380
+ }
381
+ .story-title span {
382
+ font-size: 0.7em; /* Make scene count smaller */
383
+ }
384
+
385
+ .story-content-wrapper {
386
+ padding: 1.5rem;
387
+ font-size: 1rem;
388
+ margin: 0.5rem 0;
389
+ border-radius: 10px;
390
+ }
391
+
392
+ .audio-btn {
393
+ padding: 0.6rem 1.2rem;
394
+ font-size: 0.95rem;
395
+ }
396
+
397
+ .btn-group {
398
+ flex-direction: column; /* Stack navigation buttons vertically */
399
+ gap: 0.8rem;
400
+ }
401
+
402
+ .btn-group button {
403
+ width: 100%; /* Full width buttons */
404
+ max-width: none; /* Remove max-width constraint */
405
+ }
406
+
407
+ .footer {
408
+ padding: 1.5rem;
409
+ margin-top: 2rem;
410
+ font-size: 0.8em;
411
+ }
412
+ }
413
+
414
+ /* Further adjustments for very small screens (e.g., older phones) */
415
+ @media (max-width: 480px) {
416
+ .sidebar {
417
+ padding: 1rem 0.8rem;
418
+ }
419
+ .content {
420
+ padding: 1rem 0.8rem;
421
+ }
422
+ .app-header {
423
+ padding: 1rem;
424
+ }
425
+ .app-title {
426
+ font-size: 1.8rem;
427
+ }
428
+ .app-subtitle {
429
+ font-size: 0.9rem;
430
+ }
431
+ .story-card {
432
+ padding: 1rem;
433
+ }
434
+ .story-title {
435
+ font-size: 1.3rem;
436
+ }
437
+ .story-content-wrapper {
438
+ padding: 1rem;
439
+ font-size: 0.95rem;
440
+ }
441
+ .audio-btn {
442
+ padding: 0.5rem 1rem;
443
+ font-size: 0.9rem;
444
+ }
445
+ }
446
+ </style>
447
+ <!-- Poppins font import (also in CSS, but explicit here for broader support if needed) -->
448
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
449
+ </head>
450
+ <body>
451
+ <div class="main-container" style="z-index:1; position:relative;">
452
+ <aside class="sidebar">
453
+ <h3 style="text-align: center; letter-spacing:2px; font-family:'Orbitron','Poppins',sans-serif; font-size:1.3em; text-shadow:0 2px 8px #00ffe7a0;">🎮 Game Stories</h3>
454
+
455
+ <form action="{{ url_for('index') }}" method="POST">
456
+ <select name="select_story" onchange="this.form.submit()">
457
+ {% for story_key, story_value in stories.items() %}
458
+ <option value="{{ story_key }}" {% if story_key == selected_story_title %}selected{% endif %}>
459
+ {{ story_value.emoji }} {{ story_key.replace(story_value.emoji + " ", "") }}
460
+ </option>
461
+ {% endfor %}
462
+ </select>
463
+ <!-- This button is just to ensure form submission if JS is off, though onchange handles it -->
464
+ <button type="submit" style="display: none;">Go to Story</button>
465
+ </form>
466
+
467
+ <hr>
468
+ <p style="opacity: 0.9; font-size: 1em; text-align: center; color:#00ffe7; font-family:'Orbitron','Poppins',sans-serif; text-shadow:0 2px 8px #00ffe7a0;">Choose your adventure!</p>
469
+
470
+ <hr>
471
+ <p style="opacity: 0.8; font-size: 0.9em; text-align: center; color:#43e97b; font-family:'Orbitron','Poppins',sans-serif; text-shadow:0 2px 8px #43e97ba0;">
472
+ 💧 Rainwater Chronicles - The Game
473
+ </p>
474
+ </aside>
475
+
476
+ <main class="content">
477
+ <header class="app-header" style="box-shadow:0 0 32px #00ffe7a0;">
478
+ <div class="app-title" style="font-family:'Orbitron','Poppins',sans-serif; letter-spacing:2px;">💧 Rainwater Chronicles 💧</div>
479
+ <div class="app-subtitle" style="font-family:'Orbitron','Poppins',sans-serif; color:#232526;">Discover the Magic of Water Conservation Through Stories</div>
480
+ </header>
481
+
482
+ {% block content %}{% endblock %}
483
+
484
+ <footer class="footer">
485
+ <p>💧 <strong>Rainwater Chronicles</strong> - Making water conservation accessible through storytelling</p>
486
+ <p>Together, we can make every drop count! 🌍</p>
487
+ </footer>
488
+ </main>
489
+ </div>
490
+
491
+ <!-- Global Text-to-Speech script -->
492
+ <script>
493
+ let currentUtterance = null; // To keep track of the currently speaking utterance
494
+
495
+ function playAudio(text, buttonId) {
496
+ const btn = document.getElementById(buttonId);
497
+
498
+ // Stop any currently playing audio
499
+ if (currentUtterance && window.speechSynthesis.speaking) {
500
+ window.speechSynthesis.cancel();
501
+ }
502
+
503
+ // If the same button is clicked again, stop it and reset
504
+ if (btn.classList.contains('playing')) {
505
+ btn.innerHTML = '🔊 Listen to Story';
506
+ btn.classList.remove('playing');
507
+ return;
508
+ }
509
+
510
+ currentUtterance = new SpeechSynthesisUtterance(text);
511
+
512
+ btn.innerHTML = '⏸️ Playing...';
513
+ btn.classList.add('playing');
514
+
515
+ currentUtterance.onend = function() {
516
+ btn.innerHTML = '🔊 Listen to Story';
517
+ btn.classList.remove('playing');
518
+ };
519
+ currentUtterance.onerror = function(event) {
520
+ console.error('SpeechSynthesisUtterance.onerror', event);
521
+ btn.innerHTML = '🔊 Listen to Story (Error)';
522
+ btn.classList.remove('playing');
523
+ };
524
+
525
+ currentUtterance.lang = 'en-US';
526
+ currentUtterance.rate = 0.9;
527
+ currentUtterance.pitch = 1.0;
528
+
529
+ window.speechSynthesis.speak(currentUtterance);
530
+ }
531
+
532
+ // Add event listener to stop speech when navigating away or closing
533
+ window.addEventListener('beforeunload', function() {
534
+ if (window.speechSynthesis.speaking) {
535
+ window.speechSynthesis.cancel();
536
+ }
537
+ });
538
+ </script>
539
+ </body>
540
+ </html>
templates/index.html ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <div class="story-card">
5
+ <h2 class="story-title">
6
+ {{ story_emoji }} {{ selected_story_title.replace(story_emoji + " ", "") }}
7
+ <span style="font-size: 0.8em; color: #7f8c8d;">Scene {{ current_scene_index + 1 }} of {{ total_scenes }}</span>
8
+ </h2>
9
+
10
+ <div class="progress-bar-container">
11
+ <div class="progress-fill" style="width: {{ progress_percentage }}%;"></div>
12
+ </div>
13
+ </div>
14
+
15
+ <div class="story-content-wrapper">
16
+ <p>{{ scene_text }}</p>
17
+ </div>
18
+
19
+ <div class="audio-button-container">
20
+ <!-- Use a dynamic ID for the button to avoid conflicts if multiple audio buttons were on a page -->
21
+ <button class="audio-btn" id="audio-btn-{{ current_scene_index }}" onclick="playAudio('{{ scene_text | e }}', 'audio-btn-{{ current_scene_index }}')">
22
+ 🔊 Listen to Story
23
+ </button>
24
+ </div>
25
+
26
+ <form action="{{ url_for('index') }}" method="POST" class="btn-group">
27
+ <button type="submit" name="navigate_prev" class="btn nav-btn" {% if current_scene_index == 0 %}disabled{% endif %}>
28
+ ⬅️ Previous
29
+ </button>
30
+ <button type="submit" name="navigate_next" class="btn nav-btn" {% if current_scene_index == total_scenes - 1 %}disabled{% endif %}>
31
+ Next ➡️
32
+ </button>
33
+ </form>
34
+
35
+ {% if current_scene_index == total_scenes - 1 %}
36
+ <div style="text-align: center; padding: 2rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
37
+ border-radius: 15px; color: white; margin: 2rem 0;">
38
+ <h2>🎉 Story Complete!</h2>
39
+ <p>You've finished this amazing journey. Thanks for reading!</p>
40
+ </div>
41
+ {% endif %}
42
+
43
+ {% endblock %}
templates/main.html ADDED
@@ -0,0 +1,442 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>🌧️ Rainwater Quest Adventure</title>
7
+ <style>
8
+ /* Import Google Fonts */
9
+ @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Roboto:wght@300;400;500;700&display=swap');
10
+ @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap');
11
+
12
+
13
+ /* Main styling */
14
+ body {
15
+ background: linear-gradient(135deg, #e0f2f7 0%, #b3e0f2 100%); /* Light blue gradient */
16
+ font-family: 'Roboto', sans-serif;
17
+ margin: 0;
18
+ padding: 0;
19
+ color: #333; /* Default text color */
20
+ }
21
+
22
+ .container {
23
+ max-width: 1200px;
24
+ margin: 20px auto;
25
+ padding: 20px;
26
+ }
27
+
28
+ /* Header styling */
29
+ .game-header {
30
+ background: linear-gradient(90deg, #FF6B6B, #4ECDC4, #45B7D1);
31
+ background-size: 300% 300%;
32
+ animation: gradientShift 3s ease infinite;
33
+ color: white;
34
+ text-align: center;
35
+ padding: 20px;
36
+ border-radius: 15px;
37
+ margin-bottom: 20px;
38
+ box-shadow: 0 8px 32px rgba(0,0,0,0.3);
39
+ font-family: 'Orbitron', monospace;
40
+ font-weight: 900;
41
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
42
+ }
43
+
44
+ @keyframes gradientShift {
45
+ 0% { background-position: 0% 50%; }
46
+ 50% { background-position: 100% 50%; }
47
+ 100% { background-position: 0% 50%; }
48
+ }
49
+
50
+ /* Game card styling */
51
+ .game-card {
52
+ background: rgba(255, 255, 255, 0.95);
53
+ border-radius: 20px;
54
+ padding: 25px;
55
+ margin: 15px 0;
56
+ box-shadow: 0 15px 35px rgba(0,0,0,0.1);
57
+ backdrop-filter: blur(10px);
58
+ border: 2px solid rgba(255,255,255,0.18);
59
+ transition: all 0.3s ease;
60
+ }
61
+
62
+ .game-card:hover {
63
+ transform: translateY(-5px);
64
+ box-shadow: 0 20px 40px rgba(0,0,0,0.15);
65
+ }
66
+
67
+ /* Progress bar styling */
68
+ .progress-container {
69
+ background: rgba(255,255,255,0.2);
70
+ border-radius: 25px;
71
+ padding: 5px;
72
+ margin: 20px 0;
73
+ }
74
+
75
+ .progress-bar {
76
+ background: linear-gradient(90deg, #00C9FF, #92FE9D);
77
+ height: 30px;
78
+ border-radius: 20px;
79
+ transition: width 0.5s ease;
80
+ display: flex;
81
+ align-items: center;
82
+ justify-content: center;
83
+ color: white;
84
+ font-weight: bold;
85
+ text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
86
+ min-width: 0; /* Allow it to be 0% width */
87
+ }
88
+
89
+ /* XP and Level styling */
90
+ .stats-container {
91
+ display: flex;
92
+ justify-content: space-around;
93
+ margin: 20px 0;
94
+ flex-wrap: wrap; /* Allow wrapping on smaller screens */
95
+ gap: 10px; /* Space between items */
96
+ }
97
+
98
+ .stat-item {
99
+ background: linear-gradient(135deg, #667eea, #764ba2);
100
+ color: white;
101
+ padding: 15px 20px;
102
+ border-radius: 15px;
103
+ text-align: center;
104
+ min-width: 120px;
105
+ box-shadow: 0 5px 15px rgba(0,0,0,0.2);
106
+ font-family: 'Orbitron', monospace;
107
+ flex-grow: 1; /* Allow items to grow */
108
+ }
109
+
110
+ .stat-value {
111
+ font-size: 24px;
112
+ font-weight: 900;
113
+ margin-bottom: 5px;
114
+ }
115
+
116
+ .stat-label {
117
+ font-size: 12px;
118
+ opacity: 0.8;
119
+ }
120
+
121
+ /* General button styling */
122
+ .button-group {
123
+ display: flex;
124
+ gap: 10px; /* Space between buttons */
125
+ flex-wrap: wrap;
126
+ justify-content: center;
127
+ margin-top: 20px;
128
+ }
129
+
130
+ .button-group button, .button-full-width {
131
+ background: linear-gradient(135deg, #667eea, #764ba2);
132
+ color: white;
133
+ border: none;
134
+ border-radius: 15px;
135
+ padding: 15px 25px;
136
+ cursor: pointer;
137
+ transition: all 0.3s ease;
138
+ font-size: 16px;
139
+ font-weight: 500;
140
+ text-align: center;
141
+ box-shadow: 0 5px 15px rgba(0,0,0,0.2);
142
+ flex: 1; /* Make buttons grow to fill space */
143
+ min-width: 150px; /* Minimum width for buttons */
144
+ text-decoration: none; /* For anchor tags acting as buttons */
145
+ display: inline-block;
146
+ }
147
+
148
+ .button-full-width {
149
+ width: 100%;
150
+ }
151
+
152
+ .button-group button:hover, .button-full-width:hover {
153
+ transform: translateY(-3px);
154
+ box-shadow: 0 8px 25px rgba(0,0,0,0.3);
155
+ background: linear-gradient(135deg, #7b8cec, #8a5bb8);
156
+ }
157
+
158
+
159
+ /* Achievement badge */
160
+ .achievement {
161
+ background: linear-gradient(45deg, #FFD700, #FFA500);
162
+ color: #333;
163
+ padding: 10px 20px;
164
+ border-radius: 25px;
165
+ display: inline-block;
166
+ margin: 10px 5px;
167
+ font-weight: bold;
168
+ box-shadow: 0 5px 15px rgba(255, 215, 0, 0.3);
169
+ animation: pulse 2s infinite;
170
+ }
171
+
172
+ @keyframes pulse {
173
+ 0% { transform: scale(1); }
174
+ 50% { transform: scale(1.05); }
175
+ 100% { transform: scale(1); }
176
+ }
177
+
178
+ /* Welcome screen styling */
179
+ .welcome-container {
180
+ text-align: center;
181
+ padding: 50px;
182
+ background: rgba(255,255,255,0.1);
183
+ border-radius: 30px;
184
+ backdrop-filter: blur(10px);
185
+ margin: 20px auto; /* Centered */
186
+ max-width: 900px;
187
+ box-shadow: 0 20px 40px rgba(0,0,0,0.1);
188
+ }
189
+
190
+ .welcome-title {
191
+ font-size: 3.5em;
192
+ font-family: 'Orbitron', monospace;
193
+ font-weight: 900;
194
+ background: linear-gradient(45deg, #FF6B6B, #4ECDC4, #45B7D1);
195
+ -webkit-background-clip: text;
196
+ -webkit-text-fill-color: transparent;
197
+ margin-bottom: 20px;
198
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
199
+ }
200
+
201
+ .welcome-text {
202
+ font-size: 1.3em;
203
+ color: #34495e; /* Darker text for readability */
204
+ margin: 30px 0;
205
+ line-height: 1.6;
206
+ }
207
+
208
+ .welcome-text strong {
209
+ color: #2c3e50;
210
+ }
211
+
212
+ .welcome-image {
213
+ max-width: 100%;
214
+ border-radius: 15px;
215
+ margin-top: 30px;
216
+ box-shadow: 0 10px 20px rgba(0,0,0,0.2);
217
+ }
218
+
219
+ /* Certificate styling */
220
+ .certificate {
221
+ background: linear-gradient(135deg, #667eea, #764ba2);
222
+ color: white;
223
+ padding: 40px;
224
+ border-radius: 20px;
225
+ text-align: center;
226
+ margin: 20px auto;
227
+ max-width: 600px;
228
+ box-shadow: 0 20px 40px rgba(0,0,0,0.3);
229
+ border: 3px solid #FFD700;
230
+ }
231
+
232
+ /* Video container styling */
233
+ .video-container {
234
+ border-radius: 20px;
235
+ overflow: hidden;
236
+ box-shadow: 0 15px 35px rgba(0,0,0,0.2);
237
+ margin: 20px 0;
238
+ width: 100%; /* Ensure video container takes full width */
239
+ }
240
+
241
+ .video-container video {
242
+ width: 100%;
243
+ height: auto;
244
+ display: block;
245
+ }
246
+
247
+ /* Question container styling */
248
+ .question-container {
249
+ background: linear-gradient(135deg, #FF6B6B, #4ECDC4);
250
+ color: white;
251
+ padding: 25px;
252
+ border-radius: 20px;
253
+ margin: 20px 0;
254
+ box-shadow: 0 10px 30px rgba(0,0,0,0.2);
255
+ text-align: center;
256
+ }
257
+
258
+ /* Video watched indicator */
259
+ .video-watched {
260
+ background: rgba(76, 175, 80, 0.1);
261
+ border: 2px solid #4CAF50;
262
+ border-radius: 10px;
263
+ padding: 15px;
264
+ margin: 10px 0;
265
+ color: #2E7D32;
266
+ font-weight: bold;
267
+ text-align: center;
268
+ }
269
+
270
+ /* Feedback messages */
271
+ .feedback.success {
272
+ background-color: #d4edda;
273
+ color: #155724;
274
+ border: 1px solid #badbcc;
275
+ padding: 15px;
276
+ margin: 15px 0;
277
+ border-radius: 8px;
278
+ text-align: center;
279
+ }
280
+
281
+ .feedback.error {
282
+ background-color: #f8d7da;
283
+ color: #721c24;
284
+ border: 1px solid #f5c6cb;
285
+ padding: 15px;
286
+ margin: 15px 0;
287
+ border-radius: 8px;
288
+ text-align: center;
289
+ }
290
+
291
+ .feedback.info {
292
+ background-color: #d1ecf1;
293
+ color: #0c5460;
294
+ border: 1px solid #bee5eb;
295
+ padding: 15px;
296
+ margin: 15px 0;
297
+ border-radius: 8px;
298
+ text-align: center;
299
+ }
300
+
301
+ /* Utility classes for layout */
302
+ .columns-3 {
303
+ display: flex;
304
+ justify-content: center;
305
+ gap: 20px;
306
+ margin-top: 20px;
307
+ flex-wrap: wrap; /* Allow wrapping */
308
+ }
309
+
310
+ .columns-3 > div {
311
+ flex: 1;
312
+ min-width: 250px; /* Ensure columns don't get too small */
313
+ }
314
+
315
+ .columns-5 {
316
+ display: flex;
317
+ justify-content: space-around;
318
+ gap: 10px;
319
+ margin-top: 20px;
320
+ flex-wrap: wrap;
321
+ }
322
+
323
+ .columns-5 > div {
324
+ flex: 1;
325
+ min-width: 150px;
326
+ }
327
+
328
+ h1, h2, h3, h4, h5, h6 {
329
+ font-family: 'Orbitron', monospace;
330
+ color: #34495e;
331
+ }
332
+
333
+ p {
334
+ line-height: 1.6;
335
+ }
336
+ </style>
337
+ </head>
338
+
339
+ <body style="margin:0; padding:0; height:100vh; overflow:hidden; background:linear-gradient(135deg,#f8fafc 0%,#e0f7fa 100%);">
340
+ <div id="game-root" style="display:flex; flex-direction:column; height:100vh; width:100vw; justify-content:stretch; align-items:stretch; background:none;">
341
+ <!-- Top Bar: Stats & Progress -->
342
+ <div style="flex:0 0 auto; background:rgba(255,255,255,0.95); color:#2c3e50; display:flex; align-items:center; justify-content:space-between; padding:0.5em 2vw; min-height:54px; box-shadow:0 2px 8px #b2ebf2; z-index:2;">
343
+ <div style="display:flex; gap:2vw; align-items:center;">
344
+ <span style="font-family:'Orbitron',sans-serif; font-weight:700;">Lv.{{ player_level }}</span>
345
+ <span>XP: {{ xp_points }}</span>
346
+ <span>Health: <span style="color:{{ health > 50 and '#43a047' or (health > 20 and '#fbc02d' or '#e53935') }}">{{ health }}%</span></span>
347
+ <span>Streak: {{ streak }}</span>
348
+ <span>Accuracy: {{ "%.0f" | format((quiz_correct_count / (quiz_attempts if quiz_attempts > 0 else 1)) * 100) }}%</span>
349
+ </div>
350
+ {% set progress_percentage = ((current_video_index + 1) / total_videos) * 100 %}
351
+ <div style="flex:1; margin-left:2vw; margin-right:2vw;">
352
+ <div style="background:rgba(0,0,0,0.07); border-radius:8px; height:14px; width:100%; overflow:hidden;">
353
+ <div style="background:linear-gradient(90deg,#4fc3f7,#b2ebf2); height:100%; width:{{ progress_percentage }}%; border-radius:8px; transition:width 0.5s;"></div>
354
+ </div>
355
+ <div style="font-size:0.9em; color:#00bcd4; text-align:right;">Stage {{ current_video_index + 1 }}/{{ total_videos }}</div>
356
+ </div>
357
+ </div>
358
+
359
+ <!-- Main Game Area: Centered, No Scroll, Light Colors -->
360
+ <div style="flex:1 1 auto; display:flex; align-items:center; justify-content:center; min-height:0; min-width:0;">
361
+ <div style="background:rgba(255,255,255,0.98); border-radius:20px; box-shadow:0 8px 32px #b2ebf2, 0 2px 8px rgba(0,0,0,0.07); max-width:900px; width:99vw; max-height:68vh; min-height:320px; display:flex; flex-direction:column; align-items:center; justify-content:flex-start; padding:1.2vw 1.2vw 0.7vw 1.2vw; overflow:hidden; position:relative;">
362
+ <div style="width:100%; text-align:center; margin-bottom:0.4em;">
363
+ <h2 style="font-family:'Orbitron',sans-serif; color:#00bcd4; margin:0; font-size:1.6em;">🎯 Stage {{ current_video_index + 1 }}: {{ video_description }}</h2>
364
+ </div>
365
+ {% if message %}
366
+ <div class="feedback info" style="margin-bottom:0.4em;">{{ message }}</div>
367
+ {% endif %}
368
+ <div style="flex:1 1 auto; width:100%; display:flex; flex-direction:column; align-items:center; justify-content:center; overflow:auto;">
369
+ {% if not video_watched %}
370
+ <div style="width:100%; max-width:500px; margin:0 auto;">
371
+ {% if is_local_video and not local_file_exists %}
372
+ <div class="feedback error">⚠️ Video file not found: {{ video_src }}. Please check the path or use an online URL.</div>
373
+ <form action="{{ url_for('main_app') }}" method="post">
374
+ <button type="submit" name="mark_watched" class="button-full-width">Skip missing video</button>
375
+ </form>
376
+ {% else %}
377
+ <form id="autoMarkWatchedForm" action="{{ url_for('main_app') }}" method="post" style="margin:0;">
378
+ <video id="gameVideo" controls src="{{ video_src }}" {% if is_local_video %} type="video/mp4" {% endif %} style="width:100%; max-height:180px; border-radius:12px; background:#e0f7fa;"></video>
379
+ <input type="hidden" name="mark_watched" value="1">
380
+ </form>
381
+ <script>
382
+ document.addEventListener('DOMContentLoaded', function() {
383
+ var video = document.getElementById('gameVideo');
384
+ if (video) {
385
+ video.addEventListener('ended', function() {
386
+ document.getElementById('autoMarkWatchedForm').submit();
387
+ });
388
+ }
389
+ });
390
+ </script>
391
+ {% endif %}
392
+ </div>
393
+ {% else %}
394
+ <div class="video-watched" style="margin-bottom:0.4em; background:#e0f7fa; color:#388e3c; border:1px solid #b2ebf2;">✅ Video completed! Time for a challenge!</div>
395
+ {% if current_scenario.choices and not question_answered %}
396
+ <div class="question-container" style="margin-bottom:0.4em; background:linear-gradient(135deg,#b2ebf2,#e1f5fe); color:#0277bd;">
397
+ <h3 style="margin:0;">{{ current_scenario.question }}</h3>
398
+ </div>
399
+ <h3 style="margin:0.4em 0 0.15em 0; color:#00bcd4;">🎮 Choose Your Answer:</h3>
400
+ <p style="margin:0 0 0.4em 0; font-size:1em; color:#00bcd4;">*Select the most effective water conservation strategy:*</p>
401
+ <form action="{{ url_for('main_app') }}" method="post" class="button-group" style="width:100%; display:flex; flex-direction:column; gap:0.4em; align-items:center;">
402
+ {% for choice in current_scenario.choices %}
403
+ <button type="submit" name="select_choice" value="{{ loop.index0 }}" title="{{ choice.description }}" style="width:100%; max-width:370px; font-size:1em; padding:0.7em 0; border-radius:12px; background:linear-gradient(90deg,#b2ebf2,#4fc3f7); color:#0277bd; font-family:'Orbitron',sans-serif; font-weight:700; box-shadow:0 2px 8px #b2ebf2;">{{ choice.action }}</button>
404
+ {% endfor %}
405
+ </form>
406
+ {% endif %}
407
+ {% if feedback and question_answered %}
408
+ <div class="feedback {% if '✅' in feedback %}success{% else %}error{% endif %}" style="margin:0.4em 0; background:#e0f7fa; color:#388e3c; border:1px solid #b2ebf2;">{{ feedback }}</div>
409
+ <form action="{{ url_for('main_app') }}" method="post" style="margin-top: 0.4em;">
410
+ <button type="submit" name="next_level" class="button-full-width" style="width:100%; max-width:370px; font-size:1em; border-radius:12px; background:linear-gradient(90deg,#b2ebf2,#4fc3f7); color:#0277bd; font-family:'Orbitron',sans-serif; font-weight:700; box-shadow:0 2px 8px #b2ebf2;">⏭️ Next Level</button>
411
+ </form>
412
+ {% elif video_watched and not current_scenario.choices and not question_answered %}
413
+ <form action="{{ url_for('main_app') }}" method="post" style="margin-top: 0.4em;">
414
+ <button type="submit" name="continue_quest" class="button-full-width" style="width:100%; max-width:370px; font-size:1em; border-radius:12px; background:linear-gradient(90deg,#b2ebf2,#4fc3f7); color:#0277bd; font-family:'Orbitron',sans-serif; font-weight:700; box-shadow:0 2px 8px #b2ebf2;">⏭️ Continue Quest</button>
415
+ </form>
416
+ {% endif %}
417
+ {% endif %}
418
+ </div>
419
+ </div>
420
+ </div>
421
+
422
+ <!-- Bottom Bar: Controls & Achievements -->
423
+ <div style="flex:0 0 auto; background:rgba(255,255,255,0.95); color:#2c3e50; display:flex; align-items:center; justify-content:space-between; padding:0.5em 2vw; min-height:54px; box-shadow:0 -2px 8px #b2ebf2; z-index:2;">
424
+ <div style="display:flex; gap:1vw; align-items:center;">
425
+ <form action="{{ url_for('main_app') }}" method="post" style="display:inline; margin:0;">
426
+ <button type="submit" name="replay_video" style="font-size:1em; border-radius:10px; background:linear-gradient(90deg,#b2ebf2,#4fc3f7); color:#0277bd; font-family:'Orbitron',sans-serif; font-weight:700; box-shadow:0 2px 8px #b2ebf2; padding:0.4em 1em;">🔄 Replay Video</button>
427
+ </form>
428
+ <form action="{{ url_for('main_app') }}" method="post" style="display:inline; margin:0;">
429
+ <button type="submit" name="main_menu" style="font-size:1em; border-radius:10px; background:linear-gradient(90deg,#b2ebf2,#4fc3f7); color:#0277bd; font-family:'Orbitron',sans-serif; font-weight:700; box-shadow:0 2px 8px #b2ebf2; padding:0.4em 1em;">🏠 Main Menu</button>
430
+ </form>
431
+ </div>
432
+ {% if achievements %}
433
+ <div style="display:flex; gap:0.5vw; align-items:center; flex-wrap:wrap;">
434
+ {% for achievement in achievements %}
435
+ <span class="achievement" style="background:linear-gradient(45deg,#fffde7,#ffe082); color:#795548; padding:0.3em 0.8em; border-radius:12px; font-weight:700; font-size:0.95em; box-shadow:0 2px 8px #ffe08280; margin:0 0.15em;">{{ achievement.name }}</span>
436
+ {% endfor %}
437
+ </div>
438
+ {% endif %}
439
+ </div>
440
+ </div>
441
+ </body>
442
+ </html>
templates/summary.html ADDED
@@ -0,0 +1,386 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>🌧️ Quest Completed!</title>
7
+ <style>
8
+ /* Import Google Fonts */
9
+ @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Roboto:wght@300;400;500;700&display=swap');
10
+ @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap');
11
+
12
+
13
+ /* Main styling */
14
+ body {
15
+ background: linear-gradient(135deg, #e0f2f7 0%, #b3e0f2 100%); /* Light blue gradient */
16
+ font-family: 'Roboto', sans-serif;
17
+ margin: 0;
18
+ padding: 0;
19
+ color: #333; /* Default text color */
20
+ }
21
+
22
+ .container {
23
+ max-width: 1200px;
24
+ margin: 20px auto;
25
+ padding: 20px;
26
+ }
27
+
28
+ /* Header styling */
29
+ .game-header {
30
+ background: linear-gradient(90deg, #FF6B6B, #4ECDC4, #45B7D1);
31
+ background-size: 300% 300%;
32
+ animation: gradientShift 3s ease infinite;
33
+ color: white;
34
+ text-align: center;
35
+ padding: 20px;
36
+ border-radius: 15px;
37
+ margin-bottom: 20px;
38
+ box-shadow: 0 8px 32px rgba(0,0,0,0.3);
39
+ font-family: 'Orbitron', monospace;
40
+ font-weight: 900;
41
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
42
+ }
43
+
44
+ @keyframes gradientShift {
45
+ 0% { background-position: 0% 50%; }
46
+ 50% { background-position: 100% 50%; }
47
+ 100% { background-position: 0% 50%; }
48
+ }
49
+
50
+ /* Game card styling */
51
+ .game-card {
52
+ background: rgba(255, 255, 255, 0.95);
53
+ border-radius: 20px;
54
+ padding: 25px;
55
+ margin: 15px 0;
56
+ box-shadow: 0 15px 35px rgba(0,0,0,0.1);
57
+ backdrop-filter: blur(10px);
58
+ border: 2px solid rgba(255,255,255,0.18);
59
+ transition: all 0.3s ease;
60
+ }
61
+
62
+ .game-card:hover {
63
+ transform: translateY(-5px);
64
+ box-shadow: 0 20px 40px rgba(0,0,0,0.15);
65
+ }
66
+
67
+ /* Progress bar styling */
68
+ .progress-container {
69
+ background: rgba(255,255,255,0.2);
70
+ border-radius: 25px;
71
+ padding: 5px;
72
+ margin: 20px 0;
73
+ }
74
+
75
+ .progress-bar {
76
+ background: linear-gradient(90deg, #00C9FF, #92FE9D);
77
+ height: 30px;
78
+ border-radius: 20px;
79
+ transition: width 0.5s ease;
80
+ display: flex;
81
+ align-items: center;
82
+ justify-content: center;
83
+ color: white;
84
+ font-weight: bold;
85
+ text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
86
+ min-width: 0; /* Allow it to be 0% width */
87
+ }
88
+
89
+ /* XP and Level styling */
90
+ .stats-container {
91
+ display: flex;
92
+ justify-content: space-around;
93
+ margin: 20px 0;
94
+ flex-wrap: wrap; /* Allow wrapping on smaller screens */
95
+ gap: 10px; /* Space between items */
96
+ }
97
+
98
+ .stat-item {
99
+ background: linear-gradient(135deg, #667eea, #764ba2);
100
+ color: white;
101
+ padding: 15px 20px;
102
+ border-radius: 15px;
103
+ text-align: center;
104
+ min-width: 120px;
105
+ box-shadow: 0 5px 15px rgba(0,0,0,0.2);
106
+ font-family: 'Orbitron', monospace;
107
+ flex-grow: 1; /* Allow items to grow */
108
+ }
109
+
110
+ .stat-value {
111
+ font-size: 24px;
112
+ font-weight: 900;
113
+ margin-bottom: 5px;
114
+ }
115
+
116
+ .stat-label {
117
+ font-size: 12px;
118
+ opacity: 0.8;
119
+ }
120
+
121
+ /* General button styling */
122
+ .button-group {
123
+ display: flex;
124
+ gap: 10px; /* Space between buttons */
125
+ flex-wrap: wrap;
126
+ justify-content: center;
127
+ margin-top: 20px;
128
+ }
129
+
130
+ .button-group button, .button-full-width {
131
+ background: linear-gradient(135deg, #667eea, #764ba2);
132
+ color: white;
133
+ border: none;
134
+ border-radius: 15px;
135
+ padding: 15px 25px;
136
+ cursor: pointer;
137
+ transition: all 0.3s ease;
138
+ font-size: 16px;
139
+ font-weight: 500;
140
+ text-align: center;
141
+ box-shadow: 0 5px 15px rgba(0,0,0,0.2);
142
+ flex: 1; /* Make buttons grow to fill space */
143
+ min-width: 150px; /* Minimum width for buttons */
144
+ text-decoration: none; /* For anchor tags acting as buttons */
145
+ display: inline-block;
146
+ }
147
+
148
+ .button-full-width {
149
+ width: 100%;
150
+ }
151
+
152
+ .button-group button:hover, .button-full-width:hover {
153
+ transform: translateY(-3px);
154
+ box-shadow: 0 8px 25px rgba(0,0,0,0.3);
155
+ background: linear-gradient(135deg, #7b8cec, #8a5bb8);
156
+ }
157
+
158
+
159
+ /* Achievement badge */
160
+ .achievement {
161
+ background: linear-gradient(45deg, #FFD700, #FFA500);
162
+ color: #333;
163
+ padding: 10px 20px;
164
+ border-radius: 25px;
165
+ display: inline-block;
166
+ margin: 10px 5px;
167
+ font-weight: bold;
168
+ box-shadow: 0 5px 15px rgba(255, 215, 0, 0.3);
169
+ animation: pulse 2s infinite;
170
+ }
171
+
172
+ @keyframes pulse {
173
+ 0% { transform: scale(1); }
174
+ 50% { transform: scale(1.05); }
175
+ 100% { transform: scale(1); }
176
+ }
177
+
178
+ /* Welcome screen styling */
179
+ .welcome-container {
180
+ text-align: center;
181
+ padding: 50px;
182
+ background: rgba(255,255,255,0.1);
183
+ border-radius: 30px;
184
+ backdrop-filter: blur(10px);
185
+ margin: 20px auto; /* Centered */
186
+ max-width: 900px;
187
+ box-shadow: 0 20px 40px rgba(0,0,0,0.1);
188
+ }
189
+
190
+ .welcome-title {
191
+ font-size: 3.5em;
192
+ font-family: 'Orbitron', monospace;
193
+ font-weight: 900;
194
+ background: linear-gradient(45deg, #FF6B6B, #4ECDC4, #45B7D1);
195
+ -webkit-background-clip: text;
196
+ -webkit-text-fill-color: transparent;
197
+ margin-bottom: 20px;
198
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
199
+ }
200
+
201
+ .welcome-text {
202
+ font-size: 1.3em;
203
+ color: #34495e; /* Darker text for readability */
204
+ margin: 30px 0;
205
+ line-height: 1.6;
206
+ }
207
+
208
+ .welcome-text strong {
209
+ color: #2c3e50;
210
+ }
211
+
212
+ .welcome-image {
213
+ max-width: 100%;
214
+ border-radius: 15px;
215
+ margin-top: 30px;
216
+ box-shadow: 0 10px 20px rgba(0,0,0,0.2);
217
+ }
218
+
219
+ /* Certificate styling */
220
+ .certificate {
221
+ background: linear-gradient(135deg, #667eea, #764ba2);
222
+ color: white;
223
+ padding: 40px;
224
+ border-radius: 20px;
225
+ text-align: center;
226
+ margin: 20px auto;
227
+ max-width: 600px;
228
+ box-shadow: 0 20px 40px rgba(0,0,0,0.3);
229
+ border: 3px solid #FFD700;
230
+ }
231
+
232
+ /* Video container styling */
233
+ .video-container {
234
+ border-radius: 20px;
235
+ overflow: hidden;
236
+ box-shadow: 0 15px 35px rgba(0,0,0,0.2);
237
+ margin: 20px 0;
238
+ width: 100%; /* Ensure video container takes full width */
239
+ }
240
+
241
+ .video-container video {
242
+ width: 100%;
243
+ height: auto;
244
+ display: block;
245
+ }
246
+
247
+ /* Question container styling */
248
+ .question-container {
249
+ background: linear-gradient(135deg, #FF6B6B, #4ECDC4);
250
+ color: white;
251
+ padding: 25px;
252
+ border-radius: 20px;
253
+ margin: 20px 0;
254
+ box-shadow: 0 10px 30px rgba(0,0,0,0.2);
255
+ text-align: center;
256
+ }
257
+
258
+ /* Video watched indicator */
259
+ .video-watched {
260
+ background: rgba(76, 175, 80, 0.1);
261
+ border: 2px solid #4CAF50;
262
+ border-radius: 10px;
263
+ padding: 15px;
264
+ margin: 10px 0;
265
+ color: #2E7D32;
266
+ font-weight: bold;
267
+ text-align: center;
268
+ }
269
+
270
+ /* Feedback messages */
271
+ .feedback.success {
272
+ background-color: #d4edda;
273
+ color: #155724;
274
+ border: 1px solid #badbcc;
275
+ padding: 15px;
276
+ margin: 15px 0;
277
+ border-radius: 8px;
278
+ text-align: center;
279
+ }
280
+
281
+ .feedback.error {
282
+ background-color: #f8d7da;
283
+ color: #721c24;
284
+ border: 1px solid #f5c6cb;
285
+ padding: 15px;
286
+ margin: 15px 0;
287
+ border-radius: 8px;
288
+ text-align: center;
289
+ }
290
+
291
+ .feedback.info {
292
+ background-color: #d1ecf1;
293
+ color: #0c5460;
294
+ border: 1px solid #bee5eb;
295
+ padding: 15px;
296
+ margin: 15px 0;
297
+ border-radius: 8px;
298
+ text-align: center;
299
+ }
300
+
301
+ /* Utility classes for layout */
302
+ .columns-3 {
303
+ display: flex;
304
+ justify-content: center;
305
+ gap: 20px;
306
+ margin-top: 20px;
307
+ flex-wrap: wrap; /* Allow wrapping */
308
+ }
309
+
310
+ .columns-3 > div {
311
+ flex: 1;
312
+ min-width: 250px; /* Ensure columns don't get too small */
313
+ }
314
+
315
+ .columns-5 {
316
+ display: flex;
317
+ justify-content: space-around;
318
+ gap: 10px;
319
+ margin-top: 20px;
320
+ flex-wrap: wrap;
321
+ }
322
+
323
+ .columns-5 > div {
324
+ flex: 1;
325
+ min-width: 150px;
326
+ }
327
+
328
+ h1, h2, h3, h4, h5, h6 {
329
+ font-family: 'Orbitron', monospace;
330
+ color: #34495e;
331
+ }
332
+
333
+ p {
334
+ line-height: 1.6;
335
+ }
336
+ </style>
337
+ </head>
338
+ <body>
339
+ <div class="container">
340
+ <div class="game-header"><h1>🎉 QUEST COMPLETED! 🎉</h1></div>
341
+
342
+ <!-- Temporary Message Display -->
343
+ {% if message %}
344
+ <div class="feedback info">{{ message }}</div>
345
+ {% endif %}
346
+
347
+ <!-- Final Stats / Certificate -->
348
+ <div class="certificate">
349
+ <h2>🌟 WATER CONSERVATION HERO 🌟</h2>
350
+ <h3>Certificate of Excellence</h3>
351
+ <p><strong>Final Level:</strong> {{ player_level }}</p>
352
+ <p><strong>Total XP Earned:</strong> {{ xp_points }}</p>
353
+ <p><strong>Quiz Score:</strong> {{ quiz_score }}/{{ total_quizzes }} ({{ "%.0f" | format(accuracy) }}%)</p>
354
+ <p><strong>Best Streak:</strong> {{ streak }}</p>
355
+ <p><strong>Achievements Unlocked:</strong> {{ achievements | length }}</p>
356
+ <br>
357
+ <p><em>"You have mastered the ancient art of rainwater harvesting and become a true guardian of our planet's most precious resource!"</em></p>
358
+ <p><strong>Date:</strong> {{ current_date }}</p>
359
+ <p><strong>Certified by:</strong> The Global Water Conservation Academy</p>
360
+ </div>
361
+
362
+ <!-- All Achievements Display -->
363
+ {% if achievements %}
364
+ <h3>🏆 All Your Achievements</h3>
365
+ <div class="columns-3">
366
+ {% for achievement in achievements %}
367
+ <div class="achievement">{{ achievement.name }} - {{ achievement.desc }}</div>
368
+ {% endfor %}
369
+ </div>
370
+ {% endif %}
371
+
372
+ <!-- Action Buttons for Summary Page -->
373
+ <div class="button-group" style="margin-top: 30px;">
374
+ <form action="{{ url_for('restart_quest') }}" method="post">
375
+ <button type="submit">🔄 Play Again</button>
376
+ </form>
377
+ <form action="{{ url_for('view_stats') }}" method="post">
378
+ <button type="submit">📊 View Detailed Stats</button>
379
+ </form>
380
+ <form action="{{ url_for('share_impact') }}" method="post">
381
+ <button type="submit">🌍 Share Your Impact</button>
382
+ </form>
383
+ </div>
384
+ </div>
385
+ </body>
386
+ </html>
templates/welcome.html ADDED
@@ -0,0 +1,356 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>🌧️ Rainwater Quest Adventure</title>
7
+ <style>
8
+ /* Import Google Fonts */
9
+ @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Roboto:wght@300;400;500;700&display=swap');
10
+ @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap');
11
+
12
+
13
+ /* Main styling */
14
+ body {
15
+ background: linear-gradient(135deg, #e0f2f7 0%, #b3e0f2 100%); /* Light blue gradient */
16
+ font-family: 'Roboto', sans-serif;
17
+ margin: 0;
18
+ padding: 0;
19
+ color: #333; /* Default text color */
20
+ }
21
+
22
+ .container {
23
+ max-width: 1200px;
24
+ margin: 20px auto;
25
+ padding: 20px;
26
+ }
27
+
28
+ /* Header styling */
29
+ .game-header {
30
+ background: linear-gradient(90deg, #FF6B6B, #4ECDC4, #45B7D1);
31
+ background-size: 300% 300%;
32
+ animation: gradientShift 3s ease infinite;
33
+ color: white;
34
+ text-align: center;
35
+ padding: 20px;
36
+ border-radius: 15px;
37
+ margin-bottom: 20px;
38
+ box-shadow: 0 8px 32px rgba(0,0,0,0.3);
39
+ font-family: 'Orbitron', monospace;
40
+ font-weight: 900;
41
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
42
+ }
43
+
44
+ @keyframes gradientShift {
45
+ 0% { background-position: 0% 50%; }
46
+ 50% { background-position: 100% 50%; }
47
+ 100% { background-position: 0% 50%; }
48
+ }
49
+
50
+ /* Game card styling */
51
+ .game-card {
52
+ background: rgba(255, 255, 255, 0.95);
53
+ border-radius: 20px;
54
+ padding: 25px;
55
+ margin: 15px 0;
56
+ box-shadow: 0 15px 35px rgba(0,0,0,0.1);
57
+ backdrop-filter: blur(10px);
58
+ border: 2px solid rgba(255,255,255,0.18);
59
+ transition: all 0.3s ease;
60
+ }
61
+
62
+ .game-card:hover {
63
+ transform: translateY(-5px);
64
+ box-shadow: 0 20px 40px rgba(0,0,0,0.15);
65
+ }
66
+
67
+ /* Progress bar styling */
68
+ .progress-container {
69
+ background: rgba(255,255,255,0.2);
70
+ border-radius: 25px;
71
+ padding: 5px;
72
+ margin: 20px 0;
73
+ }
74
+
75
+ .progress-bar {
76
+ background: linear-gradient(90deg, #00C9FF, #92FE9D);
77
+ height: 30px;
78
+ border-radius: 20px;
79
+ transition: width 0.5s ease;
80
+ display: flex;
81
+ align-items: center;
82
+ justify-content: center;
83
+ color: white;
84
+ font-weight: bold;
85
+ text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
86
+ min-width: 0; /* Allow it to be 0% width */
87
+ }
88
+
89
+ /* XP and Level styling */
90
+ .stats-container {
91
+ display: flex;
92
+ justify-content: space-around;
93
+ margin: 20px 0;
94
+ flex-wrap: wrap; /* Allow wrapping on smaller screens */
95
+ gap: 10px; /* Space between items */
96
+ }
97
+
98
+ .stat-item {
99
+ background: linear-gradient(135deg, #667eea, #764ba2);
100
+ color: white;
101
+ padding: 15px 20px;
102
+ border-radius: 15px;
103
+ text-align: center;
104
+ min-width: 120px;
105
+ box-shadow: 0 5px 15px rgba(0,0,0,0.2);
106
+ font-family: 'Orbitron', monospace;
107
+ flex-grow: 1; /* Allow items to grow */
108
+ }
109
+
110
+ .stat-value {
111
+ font-size: 24px;
112
+ font-weight: 900;
113
+ margin-bottom: 5px;
114
+ }
115
+
116
+ .stat-label {
117
+ font-size: 12px;
118
+ opacity: 0.8;
119
+ }
120
+
121
+ /* General button styling */
122
+ .button-group {
123
+ display: flex;
124
+ gap: 10px; /* Space between buttons */
125
+ flex-wrap: wrap;
126
+ justify-content: center;
127
+ margin-top: 20px;
128
+ }
129
+
130
+ .button-group button, .button-full-width {
131
+ background: linear-gradient(135deg, #667eea, #764ba2);
132
+ color: white;
133
+ border: none;
134
+ border-radius: 15px;
135
+ padding: 15px 25px;
136
+ cursor: pointer;
137
+ transition: all 0.3s ease;
138
+ font-size: 16px;
139
+ font-weight: 500;
140
+ text-align: center;
141
+ box-shadow: 0 5px 15px rgba(0,0,0,0.2);
142
+ flex: 1; /* Make buttons grow to fill space */
143
+ min-width: 150px; /* Minimum width for buttons */
144
+ text-decoration: none; /* For anchor tags acting as buttons */
145
+ display: inline-block;
146
+ }
147
+
148
+ .button-full-width {
149
+ width: 100%;
150
+ }
151
+
152
+ .button-group button:hover, .button-full-width:hover {
153
+ transform: translateY(-3px);
154
+ box-shadow: 0 8px 25px rgba(0,0,0,0.3);
155
+ background: linear-gradient(135deg, #7b8cec, #8a5bb8);
156
+ }
157
+
158
+
159
+ /* Achievement badge */
160
+ .achievement {
161
+ background: linear-gradient(45deg, #FFD700, #FFA500);
162
+ color: #333;
163
+ padding: 10px 20px;
164
+ border-radius: 25px;
165
+ display: inline-block;
166
+ margin: 10px 5px;
167
+ font-weight: bold;
168
+ box-shadow: 0 5px 15px rgba(255, 215, 0, 0.3);
169
+ animation: pulse 2s infinite;
170
+ }
171
+
172
+ @keyframes pulse {
173
+ 0% { transform: scale(1); }
174
+ 50% { transform: scale(1.05); }
175
+ 100% { transform: scale(1); }
176
+ }
177
+
178
+ /* Welcome screen styling */
179
+ .welcome-container {
180
+ text-align: center;
181
+ padding: 50px;
182
+ background: rgba(255,255,255,0.1);
183
+ border-radius: 30px;
184
+ backdrop-filter: blur(10px);
185
+ margin: 20px auto; /* Centered */
186
+ max-width: 900px;
187
+ box-shadow: 0 20px 40px rgba(0,0,0,0.1);
188
+ }
189
+
190
+ .welcome-title {
191
+ font-size: 3.5em;
192
+ font-family: 'Orbitron', monospace;
193
+ font-weight: 900;
194
+ background: linear-gradient(45deg, #FF6B6B, #4ECDC4, #45B7D1);
195
+ -webkit-background-clip: text;
196
+ -webkit-text-fill-color: transparent;
197
+ margin-bottom: 20px;
198
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
199
+ }
200
+
201
+ .welcome-text {
202
+ font-size: 1.3em;
203
+ color: #34495e; /* Darker text for readability */
204
+ margin: 30px 0;
205
+ line-height: 1.6;
206
+ }
207
+
208
+ .welcome-text strong {
209
+ color: #2c3e50;
210
+ }
211
+
212
+ .welcome-image {
213
+ max-width: 100%;
214
+ max-height: 100px; /* ✅ fixed syntax */
215
+ border-radius: 12px; /* slightly softer than 15px */
216
+ margin-top:60px;
217
+ object-fit: contain; /* keeps aspect ratio without distortion */
218
+ display: block;
219
+ margin-left: auto;
220
+ margin-right: auto; /* ✅ centers the image */
221
+ box-shadow: 0 6px 15px rgba(0,0,0,0.15); /* softer shadow for a clean look */
222
+ }
223
+
224
+
225
+ /* Certificate styling */
226
+ .certificate {
227
+ background: linear-gradient(135deg, #667eea, #764ba2);
228
+ color: white;
229
+ padding: 40px;
230
+ border-radius: 20px;
231
+ text-align: center;
232
+ margin: 20px auto;
233
+ max-width: 600px;
234
+ box-shadow: 0 20px 40px rgba(0,0,0,0.3);
235
+ border: 3px solid #FFD700;
236
+ }
237
+
238
+ /* Video container styling */
239
+ .video-container {
240
+ border-radius: 20px;
241
+ overflow: hidden;
242
+ box-shadow: 0 15px 35px rgba(0,0,0,0.2);
243
+ margin: 20px 0;
244
+ width: 100%; /* Ensure video container takes full width */
245
+ }
246
+
247
+ .video-container video {
248
+ width: 100%;
249
+ height: auto;
250
+ display: block;
251
+ }
252
+
253
+ /* Question container styling */
254
+ .question-container {
255
+ background: linear-gradient(135deg, #FF6B6B, #4ECDC4);
256
+ color: white;
257
+ padding: 25px;
258
+ border-radius: 20px;
259
+ margin: 20px 0;
260
+ box-shadow: 0 10px 30px rgba(0,0,0,0.2);
261
+ text-align: center;
262
+ }
263
+
264
+ /* Video watched indicator */
265
+ .video-watched {
266
+ background: rgba(76, 175, 80, 0.1);
267
+ border: 2px solid #4CAF50;
268
+ border-radius: 10px;
269
+ padding: 15px;
270
+ margin: 10px 0;
271
+ color: #2E7D32;
272
+ font-weight: bold;
273
+ text-align: center;
274
+ }
275
+
276
+ /* Feedback messages */
277
+ .feedback.success {
278
+ background-color: #d4edda;
279
+ color: #155724;
280
+ border: 1px solid #badbcc;
281
+ padding: 15px;
282
+ margin: 15px 0;
283
+ border-radius: 8px;
284
+ text-align: center;
285
+ }
286
+
287
+ .feedback.error {
288
+ background-color: #f8d7da;
289
+ color: #721c24;
290
+ border: 1px solid #f5c6cb;
291
+ padding: 15px;
292
+ margin: 15px 0;
293
+ border-radius: 8px;
294
+ text-align: center;
295
+ }
296
+
297
+ .feedback.info {
298
+ background-color: #d1ecf1;
299
+ color: #0c5460;
300
+ border: 1px solid #bee5eb;
301
+ padding: 15px;
302
+ margin: 15px 0;
303
+ border-radius: 8px;
304
+ text-align: center;
305
+ }
306
+
307
+ /* Utility classes for layout */
308
+ .columns-3 {
309
+ display: flex;
310
+ justify-content: center;
311
+ gap: 20px;
312
+ margin-top: 20px;
313
+ flex-wrap: wrap; /* Allow wrapping */
314
+ }
315
+
316
+ .columns-3 > div {
317
+ flex: 1;
318
+ min-width: 250px; /* Ensure columns don't get too small */
319
+ }
320
+
321
+ .columns-5 {
322
+ display: flex;
323
+ justify-content: space-around;
324
+ gap: 10px;
325
+ margin-top: 20px;
326
+ flex-wrap: wrap;
327
+ }
328
+
329
+ .columns-5 > div {
330
+ flex: 1;
331
+ min-width: 150px;
332
+ }
333
+
334
+ h1, h2, h3, h4, h5, h6 {
335
+ font-family: 'Orbitron', monospace;
336
+ color: #34495e;
337
+ }
338
+
339
+ p {
340
+ line-height: 1.6;
341
+ }
342
+ </style>
343
+ </head>
344
+ <body>
345
+ <div class="welcome-container">
346
+ <h1 class="welcome-title">🌧️ RAINWATER QUEST 🌧️</h1>
347
+
348
+ <img src="{{ url_for('static', filename='images/1.png') }}" alt="Rainwater Collection" class="welcome-image">
349
+ <p style="font-size: 0.9em; color: #7f8c8d; margin-top: 10px;">Your adventure in water conservation begins now!</p>
350
+
351
+ <form action="{{ url_for('begin_quest') }}" method="post" style="margin-top: 40px;">
352
+ <button type="submit" class="button-full-width">🚀 BEGIN QUEST</button>
353
+ </form>
354
+ </div>
355
+ </body>
356
+ </html>