.gitattributes CHANGED
@@ -33,7 +33,3 @@ 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
36
- *.png filter=lfs diff=lfs merge=lfs -text
37
- *.mp4 filter=lfs diff=lfs merge=lfs -text
38
- static/images/*.png filter=lfs diff=lfs merge=lfs -text
39
- static/video/*.mp4 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
 
 
 
 
.gitignore CHANGED
@@ -1,70 +1,3 @@
1
- # User Data - DO NOT UPLOAD TO GITHUB
2
  users_data.json
3
-
4
- # Environment Variables - Contains API Keys
5
- .env
6
-
7
- # Firebase Service Account Key - NEVER COMMIT THIS!
8
- serviceAccountKey.json
9
- *serviceAccountKey*.json
10
- firebase-adminsdk-*.json
11
-
12
- # Python Virtual Environment
13
- .venv/
14
- venv/
15
- env/
16
- ENV/
17
-
18
- # Python Cache
19
- __pycache__/
20
- *.py[cod]
21
- *$py.class
22
- *.so
23
-
24
- # Flask
25
- instance/
26
- .webassets-cache
27
-
28
- # IDE/Editor
29
- .vscode/
30
- .idea/
31
- *.swp
32
- *.swo
33
- *~
34
- .DS_Store
35
-
36
- # Logs
37
- *.log
38
-
39
- # Distribution / packaging
40
- .Python
41
- build/
42
- develop-eggs/
43
- dist/
44
- downloads/
45
- eggs/
46
- .eggs/
47
- lib/
48
- lib64/
49
- parts/
50
- sdist/
51
- var/
52
- wheels/
53
- *.egg-info/
54
- .installed.cfg
55
- *.egg
56
-
57
- # Testing
58
- .pytest_cache/
59
- .coverage
60
- htmlcov/
61
-
62
- # RAG test data
63
- rag_data/
64
-
65
- # Other
66
- *.bak
67
- *.tmp
68
-
69
- docs/
70
- etc/
 
1
+ .venv
2
  users_data.json
3
+ .vscode
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Dockerfile CHANGED
@@ -11,8 +11,11 @@ RUN pip install --no-cache-dir --upgrade -r requirements.txt
11
 
12
  COPY --chown=user . /app
13
 
14
- # Expose port 5003 (matches app.py)
15
- EXPOSE 5003
 
 
 
16
 
17
  # Run with Gunicorn
18
- CMD ["gunicorn", "-w", "1", "-b", "0.0.0.0:5003", "app:app"]
 
11
 
12
  COPY --chown=user . /app
13
 
14
+ # Install Flask
15
+ RUN pip install flask gunicorn pymupdf tiktoken
16
+
17
+ # Expose default port
18
+ EXPOSE 7860
19
 
20
  # Run with Gunicorn
21
+ CMD ["gunicorn", "-w", "1", "-b", "0.0.0.0:7860", "app:app"]
app.py CHANGED
@@ -1,5 +1,6 @@
1
  """
2
  Concept: Flask + HTML Integration - Spiritual Path Assessment Tool
 
3
  This app helps users discover which religious or spiritual path aligns with their
4
  beliefs, values, lifestyle, and background through an interactive questionnaire.
5
  """
@@ -9,294 +10,109 @@ from flask import Flask, render_template, request, jsonify, session, redirect, u
9
  from werkzeug.security import generate_password_hash, check_password_hash
10
  import json
11
  import os
12
- import re
13
- import secrets
14
  from dotenv import load_dotenv
15
  from together import Together
16
- from rag_utils import load_religions_from_csv, prepare_religion_rag_context
17
- from openai import OpenAI
18
- import firebase_admin
19
- from firebase_admin import credentials, auth, firestore
20
 
21
  load_dotenv()
22
 
23
  app = Flask(__name__)
24
- app.secret_key = os.getenv('SECRET_KEY', secrets.token_hex(32))
25
-
26
- # Session configuration for production deployment
27
- app.config['SESSION_COOKIE_SECURE'] = os.getenv('FLASK_ENV') == 'production'
28
- app.config['SESSION_COOKIE_HTTPONLY'] = True
29
- app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
30
- app.config['PERMANENT_SESSION_LIFETIME'] = 3600 # 1 hour
31
-
32
- # Initialize Firebase Admin SDK
33
- try:
34
- firebase_cred_path = os.getenv('FIREBASE_CREDENTIALS_PATH', 'serviceAccountKey.json')
35
- if os.path.exists(firebase_cred_path):
36
- cred = credentials.Certificate(firebase_cred_path)
37
- firebase_admin.initialize_app(cred)
38
- db = firestore.client()
39
- print("✅ Firebase initialized successfully")
40
- else:
41
- print(f"⚠️ Firebase credentials not found at {firebase_cred_path}")
42
- db = None
43
- except Exception as e:
44
- print(f"⚠️ Firebase initialization failed: {e}")
45
- db = None
46
-
47
- # Firebase Web Config (for frontend)
48
- FIREBASE_CONFIG = {
49
- 'apiKey': os.getenv('FIREBASE_WEB_API_KEY'),
50
- 'authDomain': os.getenv('FIREBASE_AUTH_DOMAIN'),
51
- 'projectId': os.getenv('FIREBASE_PROJECT_ID'),
52
- 'storageBucket': os.getenv('FIREBASE_STORAGE_BUCKET', f"{os.getenv('FIREBASE_PROJECT_ID')}.appspot.com"),
53
- 'messagingSenderId': os.getenv('FIREBASE_MESSAGING_SENDER_ID'),
54
- 'appId': os.getenv('FIREBASE_APP_ID')
55
- }
56
 
57
  # File to store user data - defaults to current directory (writable in Docker)
58
- # Keep for backward compatibility during transition
59
  USERS_FILE = os.getenv("USERS_FILE", "users_data.json")
60
 
61
  # Together API for chatbot
62
  TOGETHER_API_KEY = os.getenv("TOGETHER_API_KEY")
63
  client = Together(api_key=TOGETHER_API_KEY) if TOGETHER_API_KEY else None
64
 
65
- # OpenAI for Whisper transcription
66
- OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
67
- openai_client = OpenAI(api_key=OPENAI_API_KEY) if OPENAI_API_KEY else None
68
-
69
- # Load detailed religion data at startup
70
- RELIGIONS_CSV = load_religions_from_csv('religions.csv')
71
-
72
-
73
- # Aliases map inconsistent keys to canonical ones
74
- TRADITION_ALIASES = {
75
- "secular": "humanism",
76
- "secularism": "humanism",
77
- "spiritualism": "spiritual_not_religious",
78
- "wicca": "paganism",
79
- "zen": "buddhism",
80
- "pantheism": "paganism",
81
- "unitarian": "spiritual_not_religious",
82
- "deism": "agnosticism"
83
- }
84
-
85
- def canon(key):
86
- """Normalize tradition key to canonical form"""
87
- return TRADITION_ALIASES.get(key, key)
88
-
89
- # Question importance weights (higher = more significant)
90
- QUESTION_WEIGHTS = {
91
- 1: 5, # Nature of the divine (most fundamental)
92
- 2: 3, # Spiritual connection style
93
- 3: 4, # Afterlife beliefs
94
- 4: 4, # Moral/ethical foundation
95
- 5: 3, # Prayer practices
96
- 6: 3, # Ritual/practice style
97
- 7: 3, # Human-nature relationship
98
- 8: 3, # View on suffering
99
- 9: 2 # Community importance
100
- }
101
-
102
- # Assessment Questions ----------------------------
103
  QUESTIONS = [
104
  {
105
  "id": 1,
106
  "question": "What is your view on the nature of the divine?",
107
  "options": {
108
- "One supreme God who created everything": {
109
- "christianity": 5, "islam": 5, "judaism": 5, "sikhism": 4
110
- },
111
- "Multiple gods and goddesses": {
112
- "hinduism": 5, "paganism": 5, "shintoism": 3
113
- },
114
- "A universal energy or force": {
115
- "taoism": 5, "buddhism": 4, "new_age": 4, "spiritual_not_religious": 3
116
- },
117
- "No divine being, focus on human potential": {
118
- "humanism": 5, "atheism": 5, "stoicism": 3, "confucianism": 2
119
- },
120
- "Uncertain or unknowable": {
121
- "agnosticism": 5, "spiritual_not_religious": 2
122
- }
123
  }
124
  },
125
  {
126
  "id": 2,
127
  "question": "How do you prefer to connect with spirituality?",
128
  "options": {
129
- "Through organized worship and community": {
130
- "christianity": 4, "islam": 4, "judaism": 4, "sikhism": 3
131
- },
132
- "Through personal meditation and reflection": {
133
- "buddhism": 5, "hinduism": 4, "taoism": 4, "jainism": 4
134
- },
135
- "Through nature and natural cycles": {
136
- "paganism": 5, "indigenous": 5, "shintoism": 4
137
- },
138
- "Through reason and philosophy": {
139
- "stoicism": 5, "humanism": 4, "confucianism": 3
140
- },
141
- "I don't feel the need for spiritual connection": {
142
- "atheism": 5, "humanism": 2
143
- }
144
  }
145
  },
146
  {
147
  "id": 3,
148
  "question": "What is your belief about the afterlife?",
149
  "options": {
150
- "Heaven or Hell based on faith/deeds": {
151
- "christianity": 5, "islam": 5, "judaism": 3
152
- },
153
- "Reincarnation until enlightenment": {
154
- "hinduism": 5, "buddhism": 5, "jainism": 4, "sikhism": 3
155
- },
156
- "Ancestral realm or spiritual world": {
157
- "indigenous": 4, "paganism": 3, "shintoism": 3, "confucianism": 2
158
- },
159
- "No afterlife, this life is all there is": {
160
- "atheism": 5, "humanism": 4, "stoicism": 2
161
- },
162
- "Unsure or open to possibilities": {
163
- "agnosticism": 4, "new_age": 3, "spiritual_not_religious": 3
164
- }
165
  }
166
  },
167
  {
168
  "id": 4,
169
  "question": "What guides your moral and ethical decisions?",
170
  "options": {
171
- "Sacred texts and religious teachings": {
172
- "islam": 5, "christianity": 5, "judaism": 5, "sikhism": 3
173
- },
174
- "Universal principles of compassion and mindfulness": {
175
- "buddhism": 5, "jainism": 5, "hinduism": 3
176
- },
177
- "Harmony with nature and balance": {
178
- "taoism": 5, "indigenous": 4, "shintoism": 3, "paganism": 3
179
- },
180
- "Reason, empathy, and human rights": {
181
- "humanism": 5, "atheism": 3, "stoicism": 3
182
- },
183
- "Personal intuition and inner wisdom": {
184
- "spiritual_not_religious": 5, "new_age": 4
185
- },
186
- "Virtue, duty, and social harmony": {
187
- "confucianism": 5, "stoicism": 4
188
- }
189
  }
190
  },
191
  {
192
  "id": 5,
193
- "question": "How do you approach prayer or meditation?",
194
  "options": {
195
- "Structured daily prayers at set times (e.g., 5 times daily)": {
196
- "islam": 5, "sikhism": 3
197
- },
198
- "Regular personal prayer and church attendance": {
199
- "christianity": 5, "judaism": 4
200
- },
201
- "Daily meditation or mindfulness practice": {
202
- "buddhism": 5, "hinduism": 4, "jainism": 4
203
- },
204
- "Occasional prayer or reflection when needed": {
205
- "christianity": 2, "judaism": 2, "spiritual_not_religious": 3, "new_age": 2
206
- },
207
- "Meditation for inner peace, not religious practice": {
208
- "stoicism": 3, "humanism": 2, "buddhism": 2
209
- },
210
- "I don't pray or meditate": {
211
- "atheism": 4, "humanism": 3
212
- }
213
  }
214
  },
215
  {
216
  "id": 6,
217
- "question": "What role do rituals and ceremonies play in your life?",
218
- "options": {
219
- "Essential - weekly services and holy day observances": {
220
- "christianity": 4, "islam": 4, "judaism": 5, "hinduism": 3
221
- },
222
- "Important - seasonal celebrations and nature rituals": {
223
- "paganism": 5, "indigenous": 5, "shintoism": 4
224
- },
225
- "Helpful - personal rituals that feel meaningful": {
226
- "new_age": 4, "spiritual_not_religious": 4, "hinduism": 2
227
- },
228
- "Minimal - prefer contemplation and philosophy": {
229
- "stoicism": 4, "humanism": 3, "confucianism": 2, "agnosticism": 2
230
- },
231
- "None - I don't practice rituals": {
232
- "atheism": 4, "humanism": 2
233
- }
234
- }
235
- },
236
- {
237
- "id": 7,
238
  "question": "How do you view the relationship between humans and nature?",
239
  "options": {
240
- "Humans are stewards of God's creation": {
241
- "christianity": 4, "islam": 4, "judaism": 4
242
- },
243
- "All life is interconnected and sacred": {
244
- "buddhism": 5, "hinduism": 4, "jainism": 5, "indigenous": 4
245
- },
246
- "Nature itself is divine and should be revered": {
247
- "paganism": 5, "indigenous": 4, "shintoism": 5, "taoism": 3
248
- },
249
- "Nature follows natural laws we can study": {
250
- "atheism": 4, "humanism": 4, "stoicism": 2
251
- },
252
- "We should live in harmony with natural flow": {
253
- "taoism": 5, "buddhism": 3, "shintoism": 3, "indigenous": 3
254
- }
255
  }
256
  },
257
  {
258
- "id": 8,
259
  "question": "What is your view on suffering and its purpose?",
260
  "options": {
261
- "A test of faith or part of God's plan": {
262
- "christianity": 4, "islam": 4, "judaism": 3
263
- },
264
- "Result of attachment, desire, and ignorance": {
265
- "buddhism": 5, "stoicism": 3, "jainism": 3
266
- },
267
- "Karma from past actions and choices": {
268
- "hinduism": 5, "sikhism": 4, "buddhism": 3, "jainism": 3
269
- },
270
- "Natural part of life without cosmic meaning": {
271
- "atheism": 5, "humanism": 3, "stoicism": 2
272
- },
273
- "An opportunity for spiritual growth": {
274
- "new_age": 4, "spiritual_not_religious": 3, "christianity": 2
275
- },
276
- "To be accepted with virtue and equanimity": {
277
- "stoicism": 5, "buddhism": 2, "confucianism": 2
278
- }
279
  }
280
  },
281
  {
282
- "id": 9,
283
  "question": "How important is community in your spiritual life?",
284
  "options": {
285
- "Very important - prefer group worship and fellowship": {
286
- "christianity": 4, "islam": 4, "sikhism": 5, "judaism": 4
287
- },
288
- "Somewhat important - personal practice matters more": {
289
- "buddhism": 3, "hinduism": 3, "taoism": 2
290
- },
291
- "Community of like-minded spiritual seekers": {
292
- "paganism": 4, "spiritual_not_religious": 4, "new_age": 3
293
- },
294
- "Not important - spirituality is deeply personal": {
295
- "spiritual_not_religious": 3, "agnosticism": 3, "taoism": 2
296
- },
297
- "Prefer secular community over religious": {
298
- "humanism": 4, "atheism": 4, "stoicism": 2
299
- }
300
  }
301
  }
302
  ]
@@ -316,60 +132,11 @@ RELIGIONS = {
316
  "new_age": {"name": "New Age Spirituality", "description": "Eclectic approach emphasizing personal growth.", "practices": "Meditation, energy work, crystals, yoga", "core_beliefs": "Personal transformation, universal consciousness"},
317
  "spiritual_not_religious": {"name": "Spiritual But Not Religious", "description": "Personal spirituality without organized religion.", "practices": "Personal practices, meditation, self-reflection", "core_beliefs": "Individual journey, authenticity, diverse wisdom"},
318
  "sikhism": {"name": "Sikhism", "description": "One God emphasizing service, equality, and meditation.", "practices": "Prayer, meditation, community service, 5 Ks", "core_beliefs": "One God, equality, honest living, sharing"},
319
- "indigenous": {"name": "Indigenous Spirituality", "description": "Traditional practices honoring ancestors and land.", "practices": "Ceremonies, storytelling, seasonal rituals", "core_beliefs": "Land connection, ancestor veneration, reciprocity"},
320
- "jainism": {"name": "Jainism", "description": "Ancient path emphasizing non-violence and spiritual purification.", "practices": "Meditation, fasting, non-violence, self-discipline", "core_beliefs": "Ahimsa (non-violence), karma liberation, asceticism"},
321
- "shintoism": {"name": "Shinto", "description": "Japanese tradition honoring kami spirits and natural harmony.", "practices": "Shrine visits, purification rituals, festivals", "core_beliefs": "Kami reverence, purity, harmony with nature"},
322
- "stoicism": {"name": "Stoicism", "description": "Philosophy emphasizing virtue, reason, and acceptance of fate.", "practices": "Contemplation, virtue practice, rational thinking", "core_beliefs": "Virtue, reason, acceptance, inner peace"},
323
- "confucianism": {"name": "Confucianism", "description": "Philosophy emphasizing moral cultivation and social harmony.", "practices": "Ritual propriety, study, self-cultivation", "core_beliefs": "Filial piety, benevolence, social harmony"}
324
  }
325
 
326
-
327
- # ============================================================================
328
- # FIRESTORE HELPER FUNCTIONS
329
- # ============================================================================
330
-
331
- def get_user_by_uid(uid):
332
- """Get user data from Firestore by Firebase UID"""
333
- if not db:
334
- return None
335
- try:
336
- user_ref = db.collection('users').document(uid)
337
- user_doc = user_ref.get()
338
- if user_doc.exists:
339
- return user_doc.to_dict()
340
- return None
341
- except Exception as e:
342
- print(f"Error getting user {uid}: {e}")
343
- return None
344
-
345
- def create_or_update_user(uid, user_data):
346
- """Create or update user in Firestore"""
347
- if not db:
348
- return False
349
- try:
350
- user_ref = db.collection('users').document(uid)
351
- user_ref.set(user_data, merge=True)
352
- return True
353
- except Exception as e:
354
- print(f"Error saving user {uid}: {e}")
355
- return False
356
-
357
-
358
-
359
- def verify_firebase_token(id_token):
360
- """Verify Firebase ID token and return decoded token"""
361
- try:
362
- decoded_token = auth.verify_id_token(id_token)
363
- return decoded_token
364
- except Exception as e:
365
- print(f"Token verification error: {e}")
366
- return None
367
-
368
- # ============================================================================
369
- # LEGACY JSON FILE FUNCTIONS (for backward compatibility)
370
- # ============================================================================
371
-
372
  def load_users():
 
373
  try:
374
  if os.path.exists(USERS_FILE):
375
  with open(USERS_FILE, 'r') as f:
@@ -396,8 +163,6 @@ def initialize_default_user():
396
  if not users: # Only create if no users exist
397
  users['test'] = {
398
  'password': generate_password_hash('test'),
399
- 'email': '[email protected]',
400
- 'verified': True,
401
  'answers': [],
402
  'results': []
403
  }
@@ -405,115 +170,48 @@ def initialize_default_user():
405
  print("✅ Default test user created (username: test, password: test)")
406
 
407
  def calculate_results(answers):
408
- """
409
- Calculate which spiritual paths align with user's answers
410
- Uses weighted scoring with canonical tradition keys and tie-breakers
411
- """
412
  scores = {}
413
- coverage = {} # Track number of questions contributing to each tradition (for tie-breaking)
414
 
415
- # Calculate weighted scores
416
  for answer in answers:
417
  question = next((q for q in QUESTIONS if q["id"] == answer["question_id"]), None)
418
  if question and answer["answer"] in question["options"]:
419
- points_map = question["options"][answer["answer"]]
420
- question_weight = QUESTION_WEIGHTS.get(answer["question_id"], 1)
421
-
422
- # Track which traditions are scored in this question (for tie-breaker)
423
- touched_this_question = set()
424
-
425
- for tradition_key, base_points in points_map.items():
426
- # Normalize tradition key to canonical form
427
- canonical_key = canon(tradition_key)
428
-
429
- # Calculate weighted score
430
- weighted_score = base_points * question_weight
431
- scores[canonical_key] = scores.get(canonical_key, 0) + weighted_score
432
- touched_this_question.add(canonical_key)
433
-
434
- # Update coverage count for tie-breaking
435
- for key in touched_this_question:
436
- coverage[key] = coverage.get(key, 0) + 1
437
-
438
- # Calculate maximum possible score for percentage calculation
439
- max_possible_score = 0
440
- for answer in answers:
441
- question = next((q for q in QUESTIONS if q["id"] == answer["question_id"]), None)
442
- if question:
443
- # Find the highest point value among all options for this question
444
- max_option_points = 0
445
- for option_scores in question["options"].values():
446
- if option_scores:
447
- max_option_points = max(max_option_points, max(option_scores.values()))
448
-
449
- question_weight = QUESTION_WEIGHTS.get(answer["question_id"], 1)
450
- max_possible_score += max_option_points * question_weight
451
 
452
- # Sort by score (primary) and coverage (tie-breaker)
453
- # Higher coverage means the tradition was scored across more questions
454
- sorted_scores = sorted(
455
- scores.items(),
456
- key=lambda x: (x[1], coverage.get(x[0], 0)),
457
- reverse=True
458
- )
459
 
460
- # Build top 3 recommendations
461
  recommendations = []
462
- for tradition_key, score in sorted_scores:
463
- if tradition_key in RELIGIONS:
464
- tradition_info = RELIGIONS[tradition_key].copy()
465
- tradition_info["score"] = score
466
-
467
- # Calculate percentage based on actual max possible
468
- if max_possible_score > 0:
469
- tradition_info["percentage"] = round((score / max_possible_score) * 100)
470
- else:
471
- tradition_info["percentage"] = 0
472
-
473
- recommendations.append(tradition_info)
474
-
475
- # Stop after top 3
476
- if len(recommendations) == 3:
477
- break
478
 
479
  return recommendations
480
 
481
  @app.route("/")
482
- def landing():
483
- return render_template('landing.html')
484
-
485
- @app.route("/assessment")
486
- def assessment():
487
- # Check for Firebase user first, then legacy username
488
- user_id = session.get('user_id')
489
- username = session.get('username')
490
-
491
- if not user_id and not username:
492
  return redirect(url_for('login'))
493
 
494
- # Get user data from appropriate source
495
- if user_id:
496
- # Firebase user
497
- user_data = get_user_by_uid(user_id) or {}
498
- display_name = session.get('email', 'User')
499
- has_results = 'results' in user_data and user_data['results']
500
- else:
501
- # Legacy user
502
- users = load_users()
503
- user_data = users.get(username, {})
504
- display_name = username
505
- has_results = 'results' in user_data and user_data['results']
506
 
507
  return render_template(
508
  "index.html",
509
  title="Spiritual Path Finder",
510
- message=f"Welcome, {display_name}!",
511
- username=display_name,
512
  logged_in=True,
513
  questions=QUESTIONS,
514
  has_results=has_results,
515
- results=user_data.get('results', []) if has_results else [],
516
- firebase_config=FIREBASE_CONFIG
517
  )
518
 
519
  @app.route("/login", methods=["GET", "POST"])
@@ -523,318 +221,85 @@ def login():
523
  data = request.get_json()
524
  if not data:
525
  return jsonify({"success": False, "message": "Invalid request"}), 400
 
 
 
526
 
527
- # Firebase authentication - verify ID token from frontend
528
- id_token = data.get('idToken')
529
 
530
- if id_token:
531
- # Firebase authentication flow
532
- decoded_token = verify_firebase_token(id_token)
533
- if not decoded_token:
534
- return jsonify({"success": False, "message": "Invalid authentication token"}), 401
535
-
536
- uid = decoded_token['uid']
537
- email = decoded_token.get('email', '')
538
-
539
- # Check if user exists in Firestore, create if not
540
- user_data = get_user_by_uid(uid)
541
- if not user_data:
542
- # Create new user document
543
- create_or_update_user(uid, {
544
- 'email': email,
545
- 'answers': [],
546
- 'results': [],
547
- 'created_at': firestore.SERVER_TIMESTAMP
548
- })
549
-
550
- # Store UID in session
551
- session['user_id'] = uid
552
- session['email'] = email
553
- session.permanent = True
554
- return jsonify({"success": True})
555
- else:
556
- # Legacy username/password flow (backward compatibility)
557
- username = data.get('username', '').strip()
558
- password = data.get('password', '')
559
-
560
- if not username or not password:
561
- return jsonify({"success": False, "message": "Username and password required"}), 400
562
-
563
- users = load_users()
564
- if username not in users:
565
- return jsonify({"success": False, "message": "Invalid credentials"}), 401
566
-
567
- user_data = users[username]
568
-
569
- # Check if email is verified
570
- if not user_data.get('verified', True):
571
- return jsonify({"success": False, "message": "Please verify your email first. Check your inbox."}), 403
572
-
573
- stored = user_data['password']
574
-
575
- # Try hash-based verification first
576
- if stored.startswith(('scrypt:', 'pbkdf2:')):
577
  if check_password_hash(stored, password):
578
  session['username'] = username
579
- session.permanent = True
580
  return jsonify({"success": True})
581
- # Legacy plaintext fallback
582
- elif stored == password:
 
 
 
583
  users[username]['password'] = generate_password_hash(password)
584
  if not save_users(users):
585
  return jsonify({"success": False, "message": "Error saving data"}), 500
586
  session['username'] = username
587
- session.permanent = True
588
  return jsonify({"success": True})
589
-
590
- return jsonify({"success": False, "message": "Invalid credentials"}), 401
591
  except Exception as e:
592
  print(f"Login error: {e}")
593
  return jsonify({"success": False, "message": "Server error"}), 500
594
 
595
- # Pass Firebase config to template
596
- return render_template("index.html", logged_in=False, is_signup=False, firebase_config=FIREBASE_CONFIG)
597
 
598
  @app.route("/signup", methods=["GET", "POST"])
599
  def signup():
600
- if request.method == "POST":
601
- try:
602
- data = request.get_json()
603
- if not data:
604
- return jsonify({"success": False, "message": "Invalid request"}), 400
605
-
606
- # Firebase authentication - verify ID token from frontend
607
- id_token = data.get('idToken')
608
-
609
- if id_token:
610
- # Firebase signup flow (user already created by Firebase Auth on frontend)
611
- decoded_token = verify_firebase_token(id_token)
612
- if not decoded_token:
613
- return jsonify({"success": False, "message": "Invalid authentication token"}), 401
614
-
615
- uid = decoded_token['uid']
616
- email = decoded_token.get('email', '')
617
-
618
- # Create user document in Firestore
619
- user_data = {
620
- 'email': email,
621
- 'answers': [],
622
- 'results': [],
623
- 'created_at': firestore.SERVER_TIMESTAMP
624
- }
625
-
626
- if create_or_update_user(uid, user_data):
627
- session['user_id'] = uid
628
- session['email'] = email
629
- session.permanent = True
630
- return jsonify({
631
- "success": True,
632
- "message": "Account created successfully!"
633
- })
634
- else:
635
- return jsonify({"success": False, "message": "Error creating user profile"}), 500
636
- else:
637
- # Legacy username/password flow (backward compatibility)
638
- username = data.get('username', '').strip()
639
- password = data.get('password', '')
640
- email = data.get('email', '').strip().lower()
641
-
642
- if not username or not password:
643
- return jsonify({"success": False, "message": "Username and password required"}), 400
644
-
645
- if not email:
646
- return jsonify({"success": False, "message": "Email is required"}), 400
647
-
648
- if not validate_email(email):
649
- return jsonify({"success": False, "message": "Invalid email format"}), 400
650
-
651
- users = load_users()
652
-
653
- if username in users:
654
- return jsonify({"success": False, "message": "Username already exists"}), 409
655
-
656
- # Check if email already exists
657
- for user_data in users.values():
658
- if user_data.get('email') == email:
659
- return jsonify({"success": False, "message": "Email already registered"}), 409
660
-
661
- # Generate verification token
662
- token = secrets.token_urlsafe(32)
663
- VERIFICATION_TOKENS[token] = {
664
- 'username': username,
665
- 'email': email,
666
- 'password': password,
667
- 'timestamp': os.path.getmtime(USERS_FILE) if os.path.exists(USERS_FILE) else 0
668
- }
669
-
670
- # Send verification email
671
- send_verification_email(email, token)
672
-
673
- # Create user with verified status (auto-verify in dev mode)
674
- users[username] = {
675
- 'password': generate_password_hash(password),
676
- 'email': email,
677
- 'verified': True, # Auto-verify in dev mode
678
- 'answers': [],
679
- 'results': []
680
- }
681
-
682
- if not save_users(users):
683
- return jsonify({"success": False, "message": "Error saving user data"}), 500
684
-
685
- return jsonify({
686
- "success": True,
687
- "message": "Account created! Please check your email to verify your account.",
688
- "verification_sent": True
689
- })
690
- except Exception as e:
691
- print(f"Signup error: {e}")
692
- return jsonify({"success": False, "message": "Server error"}), 500
693
-
694
- # Pass Firebase config to template
695
- return render_template("index.html", logged_in=False, is_signup=True, firebase_config=FIREBASE_CONFIG)
696
-
697
- @app.route("/forgot-password", methods=["GET", "POST"])
698
- def forgot_password():
699
  if request.method == "POST":
700
  try:
701
  data = request.get_json()
702
  if not data:
703
  return jsonify({"success": False, "message": "Invalid request"}), 400
704
 
705
- email = data.get('email', '').strip().lower()
 
706
 
707
- if not email:
708
- return jsonify({"success": False, "message": "Email is required"}), 400
709
-
710
- if not validate_email(email):
711
- return jsonify({"success": False, "message": "Invalid email format"}), 400
712
 
713
  users = load_users()
714
 
715
- # Find user by email
716
- user_found = None
717
- for username, user_data in users.items():
718
- if user_data.get('email') == email:
719
- user_found = username
720
- break
721
-
722
- if not user_found:
723
- # Don't reveal that email doesn't exist (security best practice)
724
- return jsonify({
725
- "success": True,
726
- "message": "If that email exists, a reset link has been sent."
727
- })
728
 
729
- # Generate reset token
730
- token = secrets.token_urlsafe(32)
731
- VERIFICATION_TOKENS[token] = {
732
- 'username': user_found,
733
- 'email': email,
734
- 'type': 'password_reset',
735
- 'timestamp': os.path.getmtime(USERS_FILE) if os.path.exists(USERS_FILE) else 0
736
  }
737
 
738
- # Send reset email
739
- send_password_reset_email(email, token)
740
-
741
- return jsonify({
742
- "success": True,
743
- "message": "Password reset link sent to your email. Please check your inbox."
744
- })
745
  except Exception as e:
746
- print(f"Password reset error: {e}")
747
  return jsonify({"success": False, "message": "Server error"}), 500
748
 
749
- return render_template("index.html", logged_in=False, is_signup=False, is_forgot_password=True)
750
-
751
- @app.route("/reset-password")
752
- def reset_password_page():
753
- """Handle password reset via token - show form to enter new password"""
754
- token = request.args.get('token')
755
- if not token or token not in VERIFICATION_TOKENS:
756
- return render_template("index.html", logged_in=False, is_signup=False,
757
- reset_error="Invalid or expired reset token"), 400
758
-
759
- token_data = VERIFICATION_TOKENS[token]
760
- if token_data.get('type') != 'password_reset':
761
- return render_template("index.html", logged_in=False, is_signup=False,
762
- reset_error="Invalid token type"), 400
763
-
764
- # Store token in session temporarily for password reset form
765
- session['reset_token'] = token
766
- return render_template("index.html", logged_in=False, is_signup=False,
767
- show_reset_form=True, reset_token=token)
768
-
769
- @app.route("/reset-password-submit", methods=["POST"])
770
- def reset_password_submit():
771
- """Handle password reset submission"""
772
- try:
773
- data = request.get_json()
774
- token = data.get('token')
775
- new_password = data.get('password', '')
776
-
777
- if not token or token not in VERIFICATION_TOKENS:
778
- return jsonify({"success": False, "message": "Invalid or expired token"}), 400
779
-
780
- if not new_password:
781
- return jsonify({"success": False, "message": "Password is required"}), 400
782
-
783
- token_data = VERIFICATION_TOKENS[token]
784
- if token_data.get('type') != 'password_reset':
785
- return jsonify({"success": False, "message": "Invalid token type"}), 400
786
-
787
- # Reset password
788
- users = load_users()
789
- username = token_data['username']
790
- if username in users:
791
- users[username]['password'] = generate_password_hash(new_password)
792
- save_users(users)
793
- del VERIFICATION_TOKENS[token]
794
- session.pop('reset_token', None)
795
- return jsonify({"success": True, "message": "Password reset successfully"})
796
-
797
- return jsonify({"success": False, "message": "User not found"}), 404
798
- except Exception as e:
799
- print(f"Password reset submit error: {e}")
800
- return jsonify({"success": False, "message": "Server error"}), 500
801
-
802
- @app.route("/verify-email")
803
- def verify_email():
804
- """Handle email verification"""
805
- token = request.args.get('token')
806
- if not token or token not in VERIFICATION_TOKENS:
807
- return render_template("index.html", logged_in=False, is_signup=False,
808
- verify_error="Invalid or expired verification token"), 400
809
-
810
- token_data = VERIFICATION_TOKENS[token]
811
- users = load_users()
812
- username = token_data['username']
813
-
814
- if username in users:
815
- users[username]['verified'] = True
816
- save_users(users)
817
- del VERIFICATION_TOKENS[token]
818
- return render_template("index.html", logged_in=False, is_signup=False,
819
- verify_success=True)
820
-
821
- return render_template("index.html", logged_in=False, is_signup=False,
822
- verify_error="User not found"), 404
823
 
824
  @app.route("/logout")
825
  def logout():
826
- # Clear both Firebase and legacy session data
827
- session.pop('user_id', None)
828
- session.pop('email', None)
829
  session.pop('username', None)
830
- return redirect(url_for('landing'))
831
 
832
  @app.route("/submit_assessment", methods=["POST"])
833
  def submit_assessment():
834
- user_id = session.get('user_id')
835
- username = session.get('username')
836
-
837
- if not user_id and not username:
838
  return jsonify({"success": False, "message": "Not logged in"})
839
 
840
  data = request.json
@@ -846,63 +311,34 @@ def submit_assessment():
846
  # Calculate results
847
  results = calculate_results(answers)
848
 
849
- # Save to appropriate storage
850
- if user_id:
851
- # Firebase user - save to Firestore
852
- user_ref = db.collection('users').document(user_id)
853
- user_ref.update({
854
- 'answers': answers,
855
- 'results': results,
856
- 'updated_at': firestore.SERVER_TIMESTAMP
857
- })
858
  return jsonify({"success": True, "results": results})
859
- else:
860
- # Legacy user - save to JSON
861
- users = load_users()
862
- if username in users:
863
- users[username]['answers'] = answers
864
- users[username]['results'] = results
865
- save_users(users)
866
- return jsonify({"success": True, "results": results})
867
 
868
  return jsonify({"success": False, "message": "User not found"})
869
 
870
  @app.route("/reset_assessment", methods=["POST"])
871
  def reset_assessment():
872
- user_id = session.get('user_id')
873
- username = session.get('username')
874
-
875
- if not user_id and not username:
876
  return jsonify({"success": False, "message": "Not logged in"})
877
 
878
- # Reset in appropriate storage
879
- if user_id:
880
- # Firebase user - reset in Firestore
881
- user_ref = db.collection('users').document(user_id)
882
- user_ref.update({
883
- 'answers': [],
884
- 'results': [],
885
- 'updated_at': firestore.SERVER_TIMESTAMP
886
- })
887
  return jsonify({"success": True})
888
- else:
889
- # Legacy user - reset in JSON
890
- users = load_users()
891
- if username in users:
892
- users[username]['answers'] = []
893
- users[username]['results'] = []
894
- save_users(users)
895
- return jsonify({"success": True})
896
 
897
  return jsonify({"success": False, "message": "User not found"})
898
 
899
  @app.route("/chat", methods=["POST"])
900
  def chat():
901
- """
902
- RAG-enhanced chat endpoint for spiritual guidance
903
- Uses retrieval-augmented generation with religion-specific context
904
- """
905
- if 'user_id' not in session and 'username' not in session:
906
  return jsonify({"success": False, "message": "Not logged in"})
907
 
908
  if not client:
@@ -916,65 +352,37 @@ def chat():
916
  if not user_message or not religion_name:
917
  return jsonify({"success": False, "message": "Message and religion required"})
918
 
919
- # Find religion in CSV data first, fallback to basic RELIGIONS
920
  religion_data = None
921
- religion_key = None
922
-
923
  for key, value in RELIGIONS.items():
924
  if value['name'] == religion_name:
925
- religion_key = key
926
- # Use CSV data if available
927
- if key in RELIGIONS_CSV:
928
- religion_data = RELIGIONS_CSV[key]
929
- else:
930
- religion_data = value
931
  break
932
 
933
  if not religion_data:
934
  return jsonify({"success": False, "message": "Religion not found"})
935
 
936
- # Build RAG context using rag_utils
937
- if religion_key in RELIGIONS_CSV:
938
- # Rich context from CSV using RAG utilities
939
- csv_data = RELIGIONS_CSV[religion_key]
940
- context_chunks = prepare_religion_rag_context(csv_data)
941
- context = f"""REFERENCE DATA FOR {csv_data['name']}:
942
-
943
- {context_chunks[0]}"""
944
- else:
945
- # Fallback to basic data
946
- basic_context = prepare_religion_rag_context(religion_data)
947
- context = f"""REFERENCE DATA FOR {religion_data['name']}:
948
-
949
- {basic_context[0]}"""
950
-
951
- system_prompt = f"""You're a knowledgeable spiritual guide. Use the reference data below to answer questions.
952
-
953
- {context}
954
 
955
- INSTRUCTIONS:
956
- - Keep responses concise, minimal. 30-60 words, depending on the context
957
- - ALWAYS complete your sentences - never cut off mid-sentence
958
- - Be respectful and accurate
959
- - If unsure, say so
960
- - Use * for bullet points if listing
961
- - End responses with complete thoughts, not incomplete phrases
962
- - If you need to cut information, end with "..." but complete the current sentence"""
963
-
964
- # Build conversation
965
  messages = [{"role": "system", "content": system_prompt}]
966
 
967
- # Add recent chat history
968
- for msg in chat_history[-4:]:
969
  messages.append({"role": msg["role"], "content": msg["content"]})
970
 
 
971
  messages.append({"role": "user", "content": user_message})
972
 
973
  try:
 
974
  response = client.chat.completions.create(
975
  model="meta-llama/Meta-Llama-3-8B-Instruct-Lite",
976
  messages=messages,
977
- max_tokens=400,
978
  temperature=0.7,
979
  )
980
 
@@ -991,68 +399,8 @@ INSTRUCTIONS:
991
  "message": f"Chat error: {str(e)}"
992
  })
993
 
994
- @app.route("/transcribe", methods=["POST"])
995
- def transcribe():
996
- """Convert audio to text using Whisper"""
997
- if 'username' not in session:
998
- return jsonify({"success": False, "message": "Not logged in"})
999
-
1000
- if not openai_client:
1001
- return jsonify({"success": False, "message": "Whisper not configured"})
1002
-
1003
- audio_file = request.files.get('audio')
1004
- if not audio_file:
1005
- return jsonify({"success": False, "message": "No audio file"})
1006
-
1007
- try:
1008
- # Convert FileStorage to bytes
1009
- audio_bytes = audio_file.read()
1010
-
1011
- transcript = openai_client.audio.transcriptions.create(
1012
- model="whisper-1",
1013
- file=("recording.webm", audio_bytes, "audio/webm")
1014
- )
1015
- return jsonify({"success": True, "text": transcript.text})
1016
- except Exception as e:
1017
- return jsonify({"success": False, "message": str(e)})
1018
-
1019
- @app.route("/debug")
1020
- def debug():
1021
- """
1022
- Debug endpoint to check API configuration and environment
1023
- """
1024
- return jsonify({
1025
- "api_key_set": bool(TOGETHER_API_KEY),
1026
- "client_available": client is not None,
1027
- "environment": os.environ.get("ENVIRONMENT", "unknown"),
1028
- "together_api_key_length": len(TOGETHER_API_KEY) if TOGETHER_API_KEY else 0,
1029
- "flask_debug": app.debug,
1030
- "users_file": USERS_FILE
1031
- })
1032
-
1033
- @app.route("/session-debug")
1034
- def session_debug():
1035
- """
1036
- Debug endpoint to check session and user data
1037
- """
1038
- users = load_users()
1039
- return jsonify({
1040
- "session_data": dict(session),
1041
- "username_in_session": 'username' in session,
1042
- "current_username": session.get('username', 'None'),
1043
- "users_file_exists": os.path.exists(USERS_FILE),
1044
- "users_file_path": os.path.abspath(USERS_FILE),
1045
- "users_count": len(users),
1046
- "user_list": list(users.keys()),
1047
- "session_cookie_config": {
1048
- "secure": app.config.get('SESSION_COOKIE_SECURE'),
1049
- "httponly": app.config.get('SESSION_COOKIE_HTTPONLY'),
1050
- "samesite": app.config.get('SESSION_COOKIE_SAMESITE')
1051
- }
1052
- })
1053
-
1054
  # Initialize default test user on startup
1055
  initialize_default_user()
1056
 
1057
  if __name__ == "__main__":
1058
- app.run(debug=True, port=5003)
 
1
  """
2
  Concept: Flask + HTML Integration - Spiritual Path Assessment Tool
3
+
4
  This app helps users discover which religious or spiritual path aligns with their
5
  beliefs, values, lifestyle, and background through an interactive questionnaire.
6
  """
 
10
  from werkzeug.security import generate_password_hash, check_password_hash
11
  import json
12
  import os
 
 
13
  from dotenv import load_dotenv
14
  from together import Together
 
 
 
 
15
 
16
  load_dotenv()
17
 
18
  app = Flask(__name__)
19
+ app.secret_key = 'spiritual-journey-finder-2024'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
  # File to store user data - defaults to current directory (writable in Docker)
 
22
  USERS_FILE = os.getenv("USERS_FILE", "users_data.json")
23
 
24
  # Together API for chatbot
25
  TOGETHER_API_KEY = os.getenv("TOGETHER_API_KEY")
26
  client = Together(api_key=TOGETHER_API_KEY) if TOGETHER_API_KEY else None
27
 
28
+ # Assessment Questions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  QUESTIONS = [
30
  {
31
  "id": 1,
32
  "question": "What is your view on the nature of the divine?",
33
  "options": {
34
+ "One supreme God who created everything": {"christianity": 3, "islam": 3, "judaism": 3},
35
+ "Multiple gods and goddesses": {"hinduism": 3, "paganism": 3},
36
+ "A universal energy or force": {"buddhism": 2, "taoism": 3, "new_age": 3},
37
+ "No divine being, focus on human potential": {"humanism": 3, "atheism": 3},
38
+ "Uncertain or unknowable": {"agnosticism": 3}
 
 
 
 
 
 
 
 
 
 
39
  }
40
  },
41
  {
42
  "id": 2,
43
  "question": "How do you prefer to connect with spirituality?",
44
  "options": {
45
+ "Through organized worship and community": {"christianity": 2, "islam": 2, "judaism": 2},
46
+ "Through personal meditation and reflection": {"buddhism": 3, "hinduism": 2, "taoism": 2},
47
+ "Through nature and natural cycles": {"paganism": 3, "indigenous": 3},
48
+ "Through reason and philosophy": {"humanism": 2, "stoicism": 3},
49
+ "I don't feel the need for spiritual connection": {"atheism": 3}
 
 
 
 
 
 
 
 
 
 
50
  }
51
  },
52
  {
53
  "id": 3,
54
  "question": "What is your belief about the afterlife?",
55
  "options": {
56
+ "Heaven or Hell based on faith/deeds": {"christianity": 3, "islam": 3},
57
+ "Reincarnation until enlightenment": {"hinduism": 3, "buddhism": 3},
58
+ "Ancestral realm or spiritual world": {"indigenous": 2, "paganism": 2},
59
+ "No afterlife, this life is all there is": {"atheism": 3, "humanism": 2},
60
+ "Unsure or open to possibilities": {"agnosticism": 2, "new_age": 2}
 
 
 
 
 
 
 
 
 
 
61
  }
62
  },
63
  {
64
  "id": 4,
65
  "question": "What guides your moral and ethical decisions?",
66
  "options": {
67
+ "Sacred texts and religious teachings": {"christianity": 3, "islam": 3, "judaism": 3},
68
+ "Universal principles of compassion and mindfulness": {"buddhism": 3, "jainism": 3},
69
+ "Harmony with nature and balance": {"taoism": 3, "indigenous": 2},
70
+ "Reason, empathy, and human rights": {"humanism": 3, "secularism": 3},
71
+ "Personal intuition and inner wisdom": {"new_age": 2, "spiritualism": 3}
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  }
73
  },
74
  {
75
  "id": 5,
76
+ "question": "What role does ritual or practice play in your life?",
77
  "options": {
78
+ "Regular prayer and worship are essential": {"islam": 3, "christianity": 2, "judaism": 2},
79
+ "Daily meditation or mindfulness practice": {"buddhism": 3, "hinduism": 2, "zen": 3},
80
+ "Seasonal celebrations and ceremonies": {"paganism": 3, "wicca": 3},
81
+ "Minimal to no ritual, prefer intellectual engagement": {"humanism": 2, "deism": 2},
82
+ "Flexible, whatever feels meaningful to me": {"new_age": 2, "spiritual_not_religious": 3}
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  }
84
  },
85
  {
86
  "id": 6,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  "question": "How do you view the relationship between humans and nature?",
88
  "options": {
89
+ "Humans are stewards of God's creation": {"christianity": 2, "islam": 2, "judaism": 2},
90
+ "All life is interconnected and sacred": {"buddhism": 2, "hinduism": 2, "jainism": 3},
91
+ "Nature itself is divine": {"paganism": 3, "pantheism": 3, "indigenous": 3},
92
+ "Nature follows natural laws we can understand": {"atheism": 2, "humanism": 2},
93
+ "We should live in harmony with natural flow": {"taoism": 3, "shintoism": 2}
 
 
 
 
 
 
 
 
 
 
94
  }
95
  },
96
  {
97
+ "id": 7,
98
  "question": "What is your view on suffering and its purpose?",
99
  "options": {
100
+ "A test of faith or part of God's plan": {"christianity": 2, "islam": 2},
101
+ "Result of attachment and desire": {"buddhism": 3, "stoicism": 2},
102
+ "Karma from past actions": {"hinduism": 3, "sikhism": 2},
103
+ "Random or result of natural causes": {"atheism": 3, "secular": 2},
104
+ "An opportunity for growth and learning": {"new_age": 2, "spiritualism": 2}
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  }
106
  },
107
  {
108
+ "id": 8,
109
  "question": "How important is community in your spiritual life?",
110
  "options": {
111
+ "Very important, prefer group worship": {"christianity": 2, "islam": 2, "sikhism": 3},
112
+ "Somewhat important, but personal practice matters more": {"buddhism": 2, "hinduism": 2},
113
+ "Community of like-minded seekers": {"paganism": 2, "unitarian": 3},
114
+ "Not important, spirituality is personal": {"spiritual_not_religious": 3, "deism": 2},
115
+ "Prefer secular community over religious": {"humanism": 2, "atheism": 2}
 
 
 
 
 
 
 
 
 
 
116
  }
117
  }
118
  ]
 
132
  "new_age": {"name": "New Age Spirituality", "description": "Eclectic approach emphasizing personal growth.", "practices": "Meditation, energy work, crystals, yoga", "core_beliefs": "Personal transformation, universal consciousness"},
133
  "spiritual_not_religious": {"name": "Spiritual But Not Religious", "description": "Personal spirituality without organized religion.", "practices": "Personal practices, meditation, self-reflection", "core_beliefs": "Individual journey, authenticity, diverse wisdom"},
134
  "sikhism": {"name": "Sikhism", "description": "One God emphasizing service, equality, and meditation.", "practices": "Prayer, meditation, community service, 5 Ks", "core_beliefs": "One God, equality, honest living, sharing"},
135
+ "indigenous": {"name": "Indigenous Spirituality", "description": "Traditional practices honoring ancestors and land.", "practices": "Ceremonies, storytelling, seasonal rituals", "core_beliefs": "Land connection, ancestor veneration, reciprocity"}
 
 
 
 
136
  }
137
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  def load_users():
139
+ """Load users from JSON file"""
140
  try:
141
  if os.path.exists(USERS_FILE):
142
  with open(USERS_FILE, 'r') as f:
 
163
  if not users: # Only create if no users exist
164
  users['test'] = {
165
  'password': generate_password_hash('test'),
 
 
166
  'answers': [],
167
  'results': []
168
  }
 
170
  print("✅ Default test user created (username: test, password: test)")
171
 
172
  def calculate_results(answers):
173
+ """Calculate which spiritual paths align with user's answers"""
 
 
 
174
  scores = {}
 
175
 
 
176
  for answer in answers:
177
  question = next((q for q in QUESTIONS if q["id"] == answer["question_id"]), None)
178
  if question and answer["answer"] in question["options"]:
179
+ points = question["options"][answer["answer"]]
180
+ for religion, score in points.items():
181
+ scores[religion] = scores.get(religion, 0) + score
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
 
183
+ # Sort by score
184
+ sorted_scores = sorted(scores.items(), key=lambda x: x[1], reverse=True)
 
 
 
 
 
185
 
186
+ # Get top 3 recommendations
187
  recommendations = []
188
+ for religion_key, score in sorted_scores[:3]:
189
+ if religion_key in RELIGIONS:
190
+ religion_info = RELIGIONS[religion_key].copy()
191
+ religion_info["score"] = score
192
+ religion_info["percentage"] = round((score / (len(answers) * 3)) * 100)
193
+ recommendations.append(religion_info)
 
 
 
 
 
 
 
 
 
 
194
 
195
  return recommendations
196
 
197
  @app.route("/")
198
+ def home():
199
+ if 'username' not in session:
 
 
 
 
 
 
 
 
200
  return redirect(url_for('login'))
201
 
202
+ users = load_users()
203
+ user_data = users.get(session['username'], {})
204
+ has_results = 'results' in user_data and user_data['results']
 
 
 
 
 
 
 
 
 
205
 
206
  return render_template(
207
  "index.html",
208
  title="Spiritual Path Finder",
209
+ message=f"Welcome, {session['username']}! 🌟",
210
+ username=session['username'],
211
  logged_in=True,
212
  questions=QUESTIONS,
213
  has_results=has_results,
214
+ results=user_data.get('results', []) if has_results else []
 
215
  )
216
 
217
  @app.route("/login", methods=["GET", "POST"])
 
221
  data = request.get_json()
222
  if not data:
223
  return jsonify({"success": False, "message": "Invalid request"}), 400
224
+
225
+ username = data.get('username', '').strip()
226
+ password = data.get('password', '')
227
 
228
+ if not username or not password:
229
+ return jsonify({"success": False, "message": "Username and password required"}), 400
230
 
231
+ users = load_users()
232
+ if username in users:
233
+ stored = users[username]['password']
234
+
235
+ # 1) Try hash-based verification (works for any Werkzeug scheme)
236
+ try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  if check_password_hash(stored, password):
238
  session['username'] = username
 
239
  return jsonify({"success": True})
240
+ except Exception:
241
+ pass # if stored isn't a hash string, we'll try plaintext next
242
+
243
+ # 2) Legacy plaintext fallback → upgrade to a hash
244
+ if stored == password:
245
  users[username]['password'] = generate_password_hash(password)
246
  if not save_users(users):
247
  return jsonify({"success": False, "message": "Error saving data"}), 500
248
  session['username'] = username
 
249
  return jsonify({"success": True})
250
+
251
+ return jsonify({"success": False, "message": "Invalid credentials"})
252
  except Exception as e:
253
  print(f"Login error: {e}")
254
  return jsonify({"success": False, "message": "Server error"}), 500
255
 
256
+ return render_template("index.html", logged_in=False, is_signup=False)
 
257
 
258
  @app.route("/signup", methods=["GET", "POST"])
259
  def signup():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
  if request.method == "POST":
261
  try:
262
  data = request.get_json()
263
  if not data:
264
  return jsonify({"success": False, "message": "Invalid request"}), 400
265
 
266
+ username = data.get('username', '').strip()
267
+ password = data.get('password', '')
268
 
269
+ if not username or not password:
270
+ return jsonify({"success": False, "message": "Username and password required"}), 400
 
 
 
271
 
272
  users = load_users()
273
 
274
+ if username in users:
275
+ return jsonify({"success": False, "message": "Username already exists"})
 
 
 
 
 
 
 
 
 
 
 
276
 
277
+ # Create new user with hashed password
278
+ users[username] = {
279
+ 'password': generate_password_hash(password),
280
+ 'answers': [],
281
+ 'results': []
 
 
282
  }
283
 
284
+ if not save_users(users):
285
+ return jsonify({"success": False, "message": "Error saving user data"}), 500
286
+
287
+ session['username'] = username
288
+ return jsonify({"success": True})
 
 
289
  except Exception as e:
290
+ print(f"Signup error: {e}")
291
  return jsonify({"success": False, "message": "Server error"}), 500
292
 
293
+ return render_template("index.html", logged_in=False, is_signup=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294
 
295
  @app.route("/logout")
296
  def logout():
 
 
 
297
  session.pop('username', None)
298
+ return redirect(url_for('login'))
299
 
300
  @app.route("/submit_assessment", methods=["POST"])
301
  def submit_assessment():
302
+ if 'username' not in session:
 
 
 
303
  return jsonify({"success": False, "message": "Not logged in"})
304
 
305
  data = request.json
 
311
  # Calculate results
312
  results = calculate_results(answers)
313
 
314
+ # Save to user data
315
+ users = load_users()
316
+ if session['username'] in users:
317
+ users[session['username']]['answers'] = answers
318
+ users[session['username']]['results'] = results
319
+ save_users(users)
320
+
 
 
321
  return jsonify({"success": True, "results": results})
 
 
 
 
 
 
 
 
322
 
323
  return jsonify({"success": False, "message": "User not found"})
324
 
325
  @app.route("/reset_assessment", methods=["POST"])
326
  def reset_assessment():
327
+ if 'username' not in session:
 
 
 
328
  return jsonify({"success": False, "message": "Not logged in"})
329
 
330
+ users = load_users()
331
+ if session['username'] in users:
332
+ users[session['username']]['answers'] = []
333
+ users[session['username']]['results'] = []
334
+ save_users(users)
 
 
 
 
335
  return jsonify({"success": True})
 
 
 
 
 
 
 
 
336
 
337
  return jsonify({"success": False, "message": "User not found"})
338
 
339
  @app.route("/chat", methods=["POST"])
340
  def chat():
341
+ if 'username' not in session:
 
 
 
 
342
  return jsonify({"success": False, "message": "Not logged in"})
343
 
344
  if not client:
 
352
  if not user_message or not religion_name:
353
  return jsonify({"success": False, "message": "Message and religion required"})
354
 
355
+ # Find religion details
356
  religion_data = None
 
 
357
  for key, value in RELIGIONS.items():
358
  if value['name'] == religion_name:
359
+ religion_data = value
 
 
 
 
 
360
  break
361
 
362
  if not religion_data:
363
  return jsonify({"success": False, "message": "Religion not found"})
364
 
365
+ # Create context-aware system prompt
366
+ system_prompt = f"""You're a spiritual guide for {religion_data['name']}.
367
+ Info: {religion_data['description']} | Practices: {religion_data['practices']} | Beliefs: {religion_data['core_beliefs']}
368
+ Rules: Keep 30-50 words, be respectful, use * for bullet points (format: "Text: * item * item"), answer directly."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
 
370
+ # Build conversation history
 
 
 
 
 
 
 
 
 
371
  messages = [{"role": "system", "content": system_prompt}]
372
 
373
+ # Add chat history (last 5 messages for context)
374
+ for msg in chat_history[-5:]:
375
  messages.append({"role": msg["role"], "content": msg["content"]})
376
 
377
+ # Add current user message
378
  messages.append({"role": "user", "content": user_message})
379
 
380
  try:
381
+ # Call Together API with limited tokens for concise responses
382
  response = client.chat.completions.create(
383
  model="meta-llama/Meta-Llama-3-8B-Instruct-Lite",
384
  messages=messages,
385
+ max_tokens=80, # Roughly 50-60 words maximum
386
  temperature=0.7,
387
  )
388
 
 
399
  "message": f"Chat error: {str(e)}"
400
  })
401
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402
  # Initialize default test user on startup
403
  initialize_default_user()
404
 
405
  if __name__ == "__main__":
406
+ app.run(debug=True, port=5001)
rag_utils.py DELETED
@@ -1,31 +0,0 @@
1
- """Simple RAG utilities for loading religion data"""
2
- import csv
3
-
4
- def load_religions_from_csv(csv_path):
5
- """Load religion data from CSV file"""
6
- try:
7
- religions = {}
8
- with open(csv_path, 'r', encoding='utf-8') as f:
9
- reader = csv.DictReader(f)
10
- for row in reader:
11
- religions[row['key']] = row
12
- print(f"✅ Loaded {len(religions)} religions from CSV")
13
- return religions
14
- except Exception as e:
15
- print(f"⚠️ Error loading religions CSV: {e}")
16
- return {}
17
-
18
- def prepare_religion_rag_context(religion_data):
19
- """Prepare context string from religion data"""
20
- parts = []
21
-
22
- if 'description' in religion_data:
23
- parts.append(f"Description: {religion_data['description']}")
24
- if 'practices' in religion_data:
25
- parts.append(f"Practices: {religion_data['practices']}")
26
- if 'core_beliefs' in religion_data:
27
- parts.append(f"Core Beliefs: {religion_data['core_beliefs']}")
28
- if 'common_curiosities' in religion_data:
29
- parts.append(f"Common Questions: {religion_data['common_curiosities']}")
30
-
31
- return ['\n\n'.join(parts)]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
religions.csv DELETED
@@ -1,109 +0,0 @@
1
- id,religion,category,topic,content,keywords,question_relevance
2
- 1,christianity,core_belief,nature_of_divine,"Christianity is a monotheistic religion centered on belief in one God who exists as three persons: Father, Son (Jesus Christ), and Holy Spirit. Christians believe God is loving, just, and personal, actively involved in human affairs.","monotheism,trinity,personal god,jesus christ",q1_q4
3
- 2,christianity,practice,spiritual_connection,"Christians connect with God through personal prayer, communal worship, reading the Bible, and participating in sacraments like communion and baptism. Church community is central to faith practice.","prayer,worship,bible,communion,church",q2_q5
4
- 3,christianity,belief,afterlife,Christianity teaches that faith in Jesus Christ leads to eternal life in Heaven with God. Those who reject Christ face separation from God (Hell). Salvation is through grace and faith.,"heaven,hell,salvation,eternal life,grace",q3
5
- 4,christianity,ethics,moral_guidance,"Christian ethics are based on Biblical teachings, especially Jesus' commands to love God and love others. The Ten Commandments and Jesus' Sermon on the Mount provide moral framework.","bible,ten commandments,love,scripture,jesus teaching",q4
6
- 5,christianity,practice,nature_relationship,"Christians view humans as stewards of God's creation, called to care for the Earth responsibly. Nature reveals God's glory but is distinct from God.","stewardship,creation care,dominion",q6
7
- 6,christianity,belief,suffering,"Suffering is often seen as a test of faith, opportunity for spiritual growth, or part of God's mysterious plan. Jesus' suffering on the cross gives meaning to human suffering.","test of faith,cross,redemptive suffering,gods plan",q7
8
- 7,islam,core_belief,nature_of_divine,"Islam is strictly monotheistic, believing in one God (Allah) who is merciful, compassionate, and transcendent. Allah is the creator and sustainer of all existence, revealed through Prophet Muhammad.","monotheism,allah,tawhid,prophet muhammad",q1_q4
9
- 8,islam,practice,spiritual_connection,"Muslims connect with Allah through five daily prayers (salat), reciting Quran, and following the Five Pillars. Prayer involves physical prostration showing submission to Allah.","five pillars,salat,prayer,quran,submission",q2_q5
10
- 9,islam,belief,afterlife,Muslims believe in Day of Judgment where all are resurrected and judged by Allah. Paradise (Jannah) awaits the righteous; Hell (Jahannam) for those who reject faith and do evil.,"judgment day,jannah,paradise,hell,resurrection",q3
11
- 10,islam,ethics,moral_guidance,"Islamic ethics come from Quran and Hadith (Prophet's teachings). Core values include justice, compassion, honesty, modesty, and following Shariah (Islamic law).","quran,hadith,shariah,justice,compassion",q4
12
- 11,islam,practice,nature_relationship,"Muslims see humans as Allah's khalifah (stewards) on Earth, responsible for protecting and using resources justly. Nature is a sign of Allah's power and mercy.","khalifah,stewardship,creation,signs of allah",q6
13
- 12,islam,belief,suffering,Suffering is a test from Allah to strengthen faith and purify the soul. Patience (sabr) during hardship is highly valued and rewarded by Allah.,"test,sabr,patience,purification,trial",q7
14
- 13,buddhism,core_belief,nature_of_divine,"Buddhism generally doesn't focus on a creator god. Instead, it teaches about the nature of reality, suffering, and the path to enlightenment (Nirvana). Some schools venerate Buddha and bodhisattvas.","non-theistic,enlightenment,nirvana,buddha,no creator",q1
15
- 14,buddhism,practice,spiritual_connection,"Buddhists practice meditation (especially mindfulness and concentration), follow the Eightfold Path, and cultivate wisdom and compassion. Personal practice is emphasized over communal worship.","meditation,mindfulness,eightfold path,personal practice",q2_q5
16
- 15,buddhism,belief,afterlife,Buddhism teaches rebirth (samsara) driven by karma. The goal is to achieve Nirvana - liberation from the cycle of rebirth and suffering. Rebirth isn't eternal soul but continuation of consciousness.,"rebirth,samsara,karma,nirvana,liberation",q3
17
- 16,buddhism,ethics,moral_guidance,"Buddhist ethics center on the Four Noble Truths and Eightfold Path. Core principles include non-harm (ahimsa), compassion (karuna), and mindful awareness. Actions create karma.","four noble truths,ahimsa,compassion,karma,mindfulness",q4
18
- 17,buddhism,belief,interconnection,Buddhism teaches dependent origination - all phenomena are interconnected. Harming nature harms oneself. All sentient beings deserve compassion and should be treated with respect.,"interconnection,dependent origination,sentient beings,compassion",q6
19
- 18,buddhism,belief,suffering,"Suffering (dukkha) is caused by attachment, craving, and ignorance of reality's true nature. The path to end suffering is through understanding, letting go of attachment, and following the Eightfold Path.","dukkha,attachment,craving,four noble truths,cessation",q7
20
- 19,hinduism,core_belief,nature_of_divine,"Hinduism embraces diverse views: one supreme reality (Brahman) manifesting as many deities, or devotion to personal gods like Vishnu, Shiva, or Devi. Ultimate reality is beyond form.","brahman,polytheism,monotheism,vishnu,shiva,multiple paths",q1
21
- 20,hinduism,practice,spiritual_connection,"Hindus practice yoga, meditation, puja (worship), temple visits, and festivals. Multiple paths exist: devotion (bhakti), knowledge (jnana), action (karma yoga), meditation (raja yoga).","yoga,meditation,puja,bhakti,multiple paths",q2_q5
22
- 21,hinduism,belief,afterlife,"Hindus believe in reincarnation (samsara) based on karma. The soul (atman) is reborn until achieving moksha (liberation) - union with Brahman, ending the cycle of rebirth.","reincarnation,karma,moksha,atman,samsara,liberation",q3
23
- 22,hinduism,ethics,moral_guidance,"Hindu ethics center on dharma (righteous duty), which varies by role, age, and circumstance. Universal principles include non-violence (ahimsa), truthfulness, and compassion.","dharma,ahimsa,karma,righteous duty",q4
24
- 23,hinduism,belief,interconnection,"Hinduism teaches all life contains the divine essence (atman/Brahman). Ahimsa (non-violence) extends to all creatures. Many Hindus are vegetarian, viewing cows and other animals as sacred.","atman,brahman,sacred,ahimsa,interconnected",q6
25
- 24,hinduism,belief,suffering,Suffering results from karma - consequences of past actions in this or previous lives. It's an opportunity to work through karmic debt and progress spiritually toward moksha.,"karma,past lives,spiritual progress,moksha",q7
26
- 25,judaism,core_belief,nature_of_divine,"Judaism is strictly monotheistic, believing in one God (YHWH/Adonai) who is eternal, creator, and enters into covenant with the Jewish people. God is both transcendent and involved in history.","monotheism,yhwh,covenant,ethical monotheism",q1_q4
27
- 26,judaism,practice,spiritual_connection,"Jews connect with God through prayer (especially Shabbat and holidays), Torah study, following mitzvot (commandments), and community participation in synagogue.","prayer,torah,mitzvot,shabbat,synagogue",q2_q5
28
- 27,judaism,belief,afterlife,"Jewish views on afterlife vary. Traditional belief includes resurrection of the dead in messianic age. Some emphasize this-world focus, others believe in Olam Ha-Ba (World to Come).","resurrection,messianic age,olam haba,varied views",q3
29
- 28,judaism,ethics,moral_guidance,"Jewish ethics come from Torah and Talmud, emphasizing justice (tzedakah), ethical behavior, study, and repair of the world (tikkun olam). The 613 mitzvot guide daily life.","torah,talmud,tzedakah,tikkun olam,justice",q4
30
- 29,judaism,practice,nature_relationship,Jews are stewards (bal tashchit - don't destroy) of God's creation. Environmental care is a religious duty. Laws like kosher and sabbatical years reflect respect for creation.,"stewardship,bal tashchit,creation care",q6
31
- 30,judaism,belief,suffering,"Suffering raises theological questions addressed differently across Jewish thought. Some see it as test, punishment, mystery, or call to action for justice. Holocaust deeply shaped modern Jewish theology.","test,mystery,justice,theological question",q7
32
- 31,taoism,core_belief,nature_of_divine,"Taoism centers on the Tao - the ineffable, eternal source and pattern of all existence. It's not a personal god but the natural way of the universe, beyond words or concepts.","tao,natural way,non-theistic,universal force",q1
33
- 32,taoism,practice,spiritual_connection,"Taoists practice meditation, tai chi, qigong, and wu wei (effortless action). The focus is aligning with natural rhythms and simplicity rather than forceful striving.","meditation,tai chi,wu wei,simplicity,natural flow",q2_q5
34
- 33,taoism,belief,afterlife,Traditional Taoism includes beliefs in immortality through spiritual cultivation and alchemy. Philosophical Taoism focuses more on living well in harmony with Tao than afterlife concerns.,"immortality,spiritual cultivation,harmony,this-life focus",q3
35
- 34,taoism,ethics,moral_guidance,"Taoist ethics emphasize naturalness, spontaneity, simplicity, and compassion. Rather than rigid rules, guidance comes from understanding natural balance (yin-yang) and wu wei.","naturalness,wu wei,yin-yang,balance,spontaneity",q4
36
- 35,taoism,belief,interconnection,"Taoism sees humans as integral part of nature, not separate or superior. Living in harmony with natural cycles and respecting all life reflects the Tao. Nature is the primary teacher.","harmony with nature,natural cycles,interconnection,nature teacher",q6
37
- 36,taoism,belief,suffering,"Suffering arises from resistance to natural flow and clinging to desires. By accepting change, practicing wu wei, and aligning with Tao, one reduces suffering and finds peace.","resistance,acceptance,wu wei,natural flow",q7
38
- 37,paganism,core_belief,nature_of_divine,Modern Paganism typically honors multiple deities (polytheism) or sees divinity in nature itself (pantheism). Gods/goddesses often represent natural forces and archetypal energies.,"polytheism,pantheism,nature deities,goddess,god",q1
39
- 38,paganism,practice,spiritual_connection,"Pagans celebrate seasonal festivals (solstices, equinoxes), perform rituals, work with natural elements, and often practice magic. Connection with nature is central to practice.","sabbats,rituals,nature connection,magic,seasonal celebrations",q2_q5
40
- 39,paganism,belief,afterlife,"Pagan views vary widely: some believe in reincarnation, others in ancestral realms or otherworlds, some in merging with nature. Many focus on honoring this life fully.","reincarnation,otherworld,ancestral realm,varied views",q3
41
- 40,paganism,ethics,moral_guidance,"Pagan ethics often center on personal responsibility and the Wiccan Rede ('harm none'). Values include balance, reciprocity with nature, and authentic personal experience.","harm none,reciprocity,personal responsibility,balance",q4
42
- 41,paganism,belief,interconnection,"Pagans view nature as sacred and divine. All life is interconnected. Humans are part of nature's web, not separate or superior. Environmental stewardship is spiritual duty.","nature sacred,divine nature,interconnected,sacred earth",q6
43
- 42,paganism,belief,suffering,Suffering is part of natural cycles of death and rebirth. It can be transformative and teach important lessons. Balance between light and dark is natural and necessary.,"cycles,transformation,balance,death and rebirth",q7
44
- 43,humanism,core_belief,nature_of_divine,"Humanism is non-religious, rejecting supernatural beliefs. Focus is on human potential, reason, ethics, and science without gods or divine intervention.","atheistic,secular,non-religious,reason,human potential",q1
45
- 44,humanism,practice,spiritual_connection,"Humanists connect through reason, critical thinking, community service, art, philosophy, and building meaningful relationships. Fulfillment comes from human connection and contribution.","reason,community,philosophy,critical thinking,service",q2_q5
46
- 45,humanism,belief,afterlife,"Humanists generally don't believe in afterlife. This life is all there is, making it precious. Legacy lives through impact on others and contributions to human progress.","no afterlife,this life only,legacy,naturalistic",q3
47
- 46,humanism,ethics,moral_guidance,"Humanist ethics are based on reason, empathy, human rights, and wellbeing. Morality comes from human needs and social context, not divine command. Science informs ethical decisions.","reason,empathy,human rights,secular ethics,evidence-based",q4
48
- 47,humanism,belief,interconnection,"Humanists view humans as part of natural world, subject to natural laws. Environmental protection is important for human wellbeing and future generations, based on scientific understanding.","naturalism,science,environmentalism,evolution",q6
49
- 48,humanism,belief,suffering,"Suffering has natural causes, not divine purpose. Humans can reduce suffering through science, medicine, social progress, and compassionate action. Focus on solving problems, not accepting fate.","natural causes,problem-solving,progress,compassion",q7
50
- 49,atheism,core_belief,nature_of_divine,"Atheism is lack of belief in gods or deities. Atheists rely on naturalistic explanations, viewing universe as product of natural processes without supernatural intervention.","no god,naturalistic,non-theistic,skepticism",q1
51
- 50,atheism,practice,spiritual_connection,"Atheists typically don't seek spiritual connection in traditional sense. Meaning comes from relationships, experiences, learning, creativity, and contributing to society. Some find awe in nature/science.","secular,community,science,meaning-making,non-spiritual",q2_q5
52
- 51,atheism,belief,afterlife,Atheists don't believe in afterlife. Consciousness ends at death. This makes life precious and motivates living fully and ethically now.,"no afterlife,materialism,this life only,consciousness ends",q3
53
- 52,atheism,ethics,moral_guidance,"Atheist morality is based on empathy, reason, wellbeing, and social cooperation. Ethics evolved to help humans live together successfully, not from divine command.","secular ethics,empathy,reason,evolution,wellbeing",q4
54
- 53,atheism,belief,interconnection,"Atheists understand humans as evolved animals, part of natural world. Environmental concern comes from understanding ecology and protecting our only home, based on scientific evidence.","naturalism,evolution,science,ecology,evidence-based",q6
55
- 54,atheism,belief,suffering,"Suffering has natural causes - disease, accidents, natural disasters, human actions. No divine purpose or test. We reduce suffering through medicine, technology, social justice, and compassion.","natural causes,randomness,problem-solving,no divine plan",q7
56
- 55,agnosticism,core_belief,nature_of_divine,Agnostics hold that existence/nature of divine is unknown or unknowable. Some lack belief but remain open; others actively believe we cannot know. Focus on uncertainty rather than conviction.,"unknowable,uncertainty,open-minded,skeptical inquiry",q1
57
- 56,agnosticism,practice,spiritual_connection,"Agnostics may explore various practices or none at all. Often value philosophical inquiry, ethical living, and intellectual honesty over religious ritual. Open to diverse perspectives.","inquiry,philosophical,eclectic,ethical living,exploration",q2_q5
58
- 57,agnosticism,belief,afterlife,"Agnostics are uncertain about afterlife. Some lean toward no afterlife, others remain completely open. Emphasis on living well now given the uncertainty about what follows.","uncertain,unknown,open possibilities,this-life focus",q3
59
- 58,agnosticism,ethics,moral_guidance,"Agnostic ethics often based on reason, empathy, and human wellbeing without certainty about divine command. Questions and inquiry are valued over absolute answers.","reason,empathy,inquiry,questioning,open ethics",q4
60
- 59,agnosticism,belief,interconnection,Agnostics may appreciate scientific understanding of interconnection or remain open to spiritual interpretations. Often value environmental care based on practical concerns.,"scientific understanding,open interpretation,practical ethics",q6
61
- 60,agnosticism,belief,suffering,Agnostics are uncertain whether suffering has purpose or is random. May see it as natural phenomenon while remaining open to other interpretations. Focus on responding compassionately.,"uncertainty,natural and/or meaningful,compassionate response",q7
62
- 61,new_age,core_belief,nature_of_divine,"New Age spirituality sees divine as universal consciousness or energy pervading all existence. Often blends ideas from Eastern religions, Western esotericism, and modern psychology.","universal consciousness,divine energy,holistic,eclectic",q1
63
- 62,new_age,practice,spiritual_connection,"New Age practitioners use meditation, energy healing, crystals, yoga, tarot, astrology, and visualization. Emphasis on personal experience and whatever resonates individually.","meditation,crystals,energy work,personal practice,eclectic",q2_q5
64
- 63,new_age,belief,afterlife,"New Age views often include reincarnation, spiritual evolution across lifetimes, and eventual unity with higher consciousness. Some believe in creating own reality even after death.","reincarnation,spiritual evolution,higher consciousness,varied beliefs",q3
65
- 64,new_age,ethics,moral_guidance,"New Age ethics emphasize personal growth, positive thinking, manifesting desires, and universal love. 'What you put out returns to you' (Law of Attraction). Follow inner guidance.","law of attraction,personal growth,inner guidance,positive energy",q4
66
- 65,new_age,belief,interconnection,"New Age teaches all is one - humans, nature, cosmos connected through universal energy. Harming environment harms collective consciousness. Earth (Gaia) may be seen as living being.","oneness,universal energy,gaia,interconnected consciousness",q6
67
- 66,new_age,belief,suffering,Suffering provides growth opportunities and spiritual lessons. Often seen as self-created for soul evolution. Positive thinking and raising vibration can transform suffering.,"growth opportunity,spiritual lesson,soul evolution,transformation",q7
68
- 67,spiritual_not_religious,core_belief,nature_of_divine,"SBNR individuals have personal spiritual beliefs outside organized religion. Views vary widely - some believe in higher power, universal energy, or remain undefined. Value personal experience over doctrine.","personal spirituality,no organized religion,individual path,authentic",q1
69
- 68,spiritual_not_religious,practice,spiritual_connection,"SBNR people create personal practices - may include meditation, nature connection, journaling, yoga, or borrowing from various traditions. Emphasis on what feels authentic and meaningful.","personal practice,eclectic,authentic,self-directed,meaningful",q2_q5
70
- 69,spiritual_not_religious,belief,afterlife,"SBNR views on afterlife vary widely. Some believe in reincarnation, others in energy continuation, some remain open and uncertain. Focus on present spiritual experience over afterlife doctrine.","varied beliefs,open-minded,present-focused,personal interpretation",q3
71
- 70,spiritual_not_religious,ethics,moral_guidance,"SBNR ethics based on personal intuition, inner wisdom, and values like compassion and authenticity. Draw from various sources - philosophy, psychology, wisdom traditions - without dogma.","intuition,inner wisdom,authenticity,compassion,non-dogmatic",q4
72
- 71,spiritual_not_religious,belief,interconnection,SBNR individuals often feel deep connection to nature and all life. May see nature as sacred teacher without formal religious framework. Environmental care is spiritual practice.,"nature connection,sacred nature,interconnection,personal meaning",q6
73
- 72,spiritual_not_religious,belief,suffering,SBNR views on suffering are personal and varied. Many see it as growth opportunity or natural part of life's journey. Focus on finding personal meaning rather than accepting religious explanation.,"personal meaning,growth,varied interpretation,authentic response",q7
74
- 73,sikhism,core_belief,nature_of_divine,"Sikhism is monotheistic, believing in one formless God (Waheguru) who is creator, sustainer, and destroyer. God is timeless, without gender, and accessible to all through devotion and service.","monotheism,waheguru,one god,formless,universal access",q1_q4
75
- 74,sikhism,practice,spiritual_connection,"Sikhs connect with God through meditation on God's name (Naam Japna), selfless service (Seva), honest living (Kirat Karni), and sharing (Vand Chakna). Community worship at Gurdwara is important.","naam japna,seva,kirat karni,gurdwara,community",q2_q5_q8
76
- 75,sikhism,belief,afterlife,Sikhs believe in reincarnation based on karma until union with God is achieved. The goal is to escape reincarnation cycle and merge with divine light through righteous living.,"reincarnation,karma,union with god,liberation,mukti",q3
77
- 76,sikhism,ethics,moral_guidance,"Sikh ethics emphasize equality, justice, service, honest work, and devotion. The five virtues are truth, contentment, compassion, humility, and love. Fight against injustice is duty.","equality,justice,five virtues,honest living,service",q4
78
- 77,sikhism,belief,interconnection,"Sikhs see all creation as manifestation of one God. All humans are equal regardless of caste, gender, or religion. Environmental care is duty as God's creation must be respected.","equality,gods creation,stewardship,respect for all",q6
79
- 78,sikhism,belief,suffering,"Suffering is result of separation from God and ego (Haumai). It's also karma from past lives. Through devotion, service, and God's grace, one can transcend suffering.","karma,separation from god,ego,grace,transcendence",q7
80
- 79,indigenous,core_belief,nature_of_divine,"Indigenous spiritualities vary greatly but often include Great Spirit, creator beings, and/or spirits in all things (animism). Divine is immanent in nature, ancestors, and the land itself.","great spirit,animism,spirits,ancestors,varied traditions",q1
81
- 80,indigenous,practice,spiritual_connection,"Indigenous practices include ceremonies, storytelling, seasonal rituals, vision quests, and maintaining relationship with land and ancestors. Community and tradition are central.","ceremonies,storytelling,seasonal rituals,land connection,ancestral",q2_q5
82
- 81,indigenous,belief,afterlife,"Indigenous beliefs often include journey to spirit world or ancestral realm after death. Ancestors remain present and active. Death is transformation, not end. Varies by tradition.","spirit world,ancestors,transformation,varied beliefs,continuity",q3
83
- 82,indigenous,ethics,moral_guidance,"Indigenous ethics center on reciprocity with nature and community, respect for elders and ancestors, seven generations thinking, and living in balance. Wisdom comes from tradition and land.","reciprocity,respect,balance,traditional wisdom,seven generations",q4
84
- 83,indigenous,belief,interconnection,"Indigenous worldviews see all beings as relatives - plants, animals, rocks, rivers all have spirit. Humans are part of nature's web with responsibilities. Land is sacred, not property.","all my relations,sacred land,interconnection,reciprocity,kinship",q6
85
- 84,indigenous,belief,suffering,"Suffering may be seen as imbalance, result of not honoring relationships, or spiritual lesson. Healing comes through ceremony, community support, and restoring harmony.","imbalance,ceremony,healing,restoration,community",q7
86
- 85,jainism,core_belief,nature_of_divine,"Jainism is non-theistic, focusing on individual spiritual liberation rather than god-worship. The universe is eternal, uncreated. Tirthankaras (enlightened teachers) show the path but aren't gods.","non-theistic,tirthankaras,eternal universe,self-liberation",q1
87
- 86,jainism,practice,spiritual_connection,"Jains practice strict non-violence (ahimsa), meditation, fasting, self-discipline, and study. Monks/nuns follow intense ascetic practices. Lay people support monastics and follow modified vows.","ahimsa,meditation,fasting,asceticism,non-violence",q2_q5
88
- 87,jainism,belief,afterlife,"Jains believe in reincarnation based on karma. Liberation (moksha) comes through eliminating all karma through right conduct, non-violence, and asceticism, freeing soul from rebirth cycle.","reincarnation,karma,moksha,liberation,soul purification",q3
89
- 88,jainism,ethics,moral_guidance,"Jain ethics center on ahimsa (non-violence) to all living beings. Five main vows: non-violence, truth, non-stealing, celibacy/chastity, non-attachment. Compassion extends to smallest creatures.","ahimsa,non-violence,five vows,compassion,universal",q4
90
- 89,jainism,belief,interconnection,"Jainism teaches all beings have souls (jiva) and deserve absolute respect. Even plants, water, and air contain souls. Strict vegetarianism and extreme care not to harm any life.","jiva,all beings sacred,extreme non-violence,vegetarianism,reverence for life",q6
91
- 90,jainism,belief,suffering,"Suffering results from karma accumulated through violence and attachment. Liberation comes through eliminating karma via strict non-violence, asceticism, and detachment from worldly concerns.","karma,non-violence,asceticism,detachment,purification",q7
92
- 91,shintoism,core_belief,nature_of_divine,"Shinto honors kami - spirits in nature, ancestors, and sacred places. Kami are not all-powerful gods but spiritual essences deserving respect. No single creator deity or canonical texts.","kami,nature spirits,ancestors,polytheistic,japanese tradition",q1
93
- 92,shintoism,practice,spiritual_connection,"Shinto practices include shrine visits, purification rituals (misogi), festivals (matsuri), offerings to kami, and maintaining harmony with nature. Ritual purity is important.","shrines,purification,matsuri,offerings,rituals",q2_q5
94
- 93,shintoism,belief,afterlife,"Shinto has no clear afterlife doctrine. Ancestors become kami and are venerated. Focus is on this life, purity, and harmony rather than afterlife rewards/punishments.","ancestors become kami,this-life focus,veneration,unclear afterlife",q3
95
- 94,shintoism,ethics,moral_guidance,"Shinto ethics emphasize purity, harmony with nature and community, gratitude, and living naturally. Morality comes from innate goodness and maintaining balance, not divine commandments.","purity,harmony,gratitude,natural living,balance",q4
96
- 95,shintoism,belief,interconnection,"Shinto sees nature as sacred - mountains, rivers, trees, rocks can all house kami. Humans are part of nature, not separate. Living in harmony with natural world is spiritual imperative.","nature sacred,kami in nature,harmony,sacred places,reverence",q6
97
- 96,shintoism,belief,suffering,Shinto doesn't have elaborate suffering theology. Misfortune may come from spiritual impurity or disharmony. Purification rituals and harmonious living restore balance and wellbeing.,"purification,impurity,balance,harmony,restoration",q7
98
- 97,stoicism,core_belief,nature_of_divine,"Stoicism is philosophical tradition, not religion. Some ancient Stoics believed in divine reason (logos) pervading cosmos; modern Stoics may be theistic, atheistic, or agnostic. Focus is on virtue.","philosophy,logos,reason,virtue,varied divine views",q1
99
- 98,stoicism,practice,spiritual_connection,"Stoics practice contemplation, journaling, virtue cultivation, and accepting what can't be controlled. Philosophical study and rational reflection replace religious ritual.","contemplation,journaling,virtue practice,rational reflection,philosophy",q2_q5
100
- 99,stoicism,belief,afterlife,Ancient Stoics had varied views; many modern Stoics don't focus on afterlife. Emphasis is on living virtuously now. Some believed in soul's continuation; others focused solely on this life.,"varied views,this-life focus,virtue,unclear afterlife",q3
101
- 100,stoicism,ethics,moral_guidance,"Stoic ethics center on four cardinal virtues: wisdom, justice, courage, temperance. Live according to reason and nature. Focus on what you can control; accept what you cannot.","four virtues,reason,acceptance,control,wisdom",q4
102
- 101,stoicism,belief,interconnection,"Stoics see humans as part of rational, ordered cosmos. We're social beings with duties to others. Living according to nature means recognizing our place in larger whole.","cosmic order,social duty,rational nature,interconnection",q6
103
- 102,stoicism,belief,suffering,"Suffering comes from mistaken judgments and desire for control over externals. By accepting fate (amor fati), focusing on virtue, and distinguishing what we control, we achieve tranquility.","acceptance,amor fati,control,judgment,tranquility",q7
104
- 103,confucianism,core_belief,nature_of_divine,"Confucianism is philosophical/ethical system more than religion. Focus is on human relationships and moral cultivation, not supernatural. Some reverence for Heaven (Tian) as moral force.","philosophy,ethics,tian,human relationships,moral cultivation",q1
105
- 104,confucianism,practice,spiritual_connection,"Confucian practice emphasizes ritual propriety (li), ancestor veneration, study of classics, self-cultivation, and fulfilling social roles properly. Education and moral development are central.","ritual propriety,ancestor veneration,study,self-cultivation,li",q2_q5
106
- 105,confucianism,belief,afterlife,"Confucianism focuses on this life and proper conduct, not afterlife speculation. Ancestors are honored and remembered, continuing influence through descendants.","this-life focus,ancestor honor,unclear afterlife,legacy",q3
107
- 106,confucianism,ethics,moral_guidance,"Confucian ethics emphasize five relationships, filial piety, benevolence (ren), righteousness (yi), and proper ritual conduct. Moral cultivation through education and self-reflection.","five relationships,filial piety,ren,yi,moral cultivation",q4
108
- 107,confucianism,belief,interconnection,Confucianism sees humans as inherently social beings in web of relationships. Harmony in family and society reflects cosmic order. Proper human conduct maintains balance.,"social harmony,relationships,cosmic order,balance,interconnection",q6
109
- 108,confucianism,belief,suffering,"Suffering often results from social disorder, improper relationships, or lack of virtue. Remedy through moral cultivation, proper conduct, education, and restoring harmonious relationships.","moral cultivation,harmony,proper conduct,social order",q7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,7 +1,4 @@
1
- # Flask + HTML Integration with Chatbot
2
  Flask>=2.0.0
3
  python-dotenv>=0.19.0
4
- together>=0.2.0
5
- gunicorn>=21.0.0
6
- openai>=1.0.0
7
- firebase-admin>=6.0.0
 
1
+ # Task 10 - Flask + HTML Integration with Chatbot
2
  Flask>=2.0.0
3
  python-dotenv>=0.19.0
4
+ together>=0.2.0
 
 
 
static/design-tokens.css DELETED
@@ -1,79 +0,0 @@
1
- :root {
2
- /* 🎨 Primary Colors */
3
- --primary-dark: #3D3D3D;
4
- --primary-black: #1A1A1A;
5
- --primary-light: #6B7280;
6
-
7
- /* Neutral Colors */
8
- --bg-gray: #F5F5F5;
9
- --bg-white: #FFFFFF;
10
- --bg-surface-alt: #FAFAFA;
11
- --border-divider: #E9ECEF;
12
-
13
- /* Semantic Colors */
14
- --background-color-1:rgb(84, 96, 235);
15
- --background-color-2:rgb(151, 119, 249);
16
- --success: #10B981;
17
- --warning: #F59E0B;
18
- --error: #EF4444;
19
- --info: #6366F1;
20
-
21
- /* Result Ranking Colors */
22
- --rank-1: #FEF3C7;
23
- --rank-2: #DBEAFE;
24
- --rank-3: #D1FAE5;
25
-
26
-
27
- /* Text Colors */
28
- --text-primary: #3D3D3D;
29
- --text-secondary: #6B7280;
30
- --text-tertiary: #9CA3AF;
31
- --text-inverse: #FFFFFF;
32
-
33
- /* 📏 Typography */
34
- --font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
35
-
36
- --font-size-sm: 0.875rem; /* 14px */
37
- --font-size-base: 1rem; /* 16px */
38
- --font-size-lg: 1.125rem; /* 18px */
39
- --font-size-xl: 1.25rem; /* 20px */
40
- --font-size-2xl: 1.375rem; /* 22px */
41
- --font-size-3xl: 2rem; /* 24px */
42
- --font-size-4xl: 3rem; /* 28px */
43
-
44
- --font-weight-regular: 400;
45
- --font-weight-medium: 600;
46
- --font-weight-bold: 700;
47
- --font-weight-extrabold: 800;
48
-
49
- /* 📐 Spacing System (4px base unit) */
50
- --space-xs: 0.5rem; /* 8px */
51
- --space-sm: 0.75rem; /* 12px */
52
- --space-md: 1rem; /* 16px */
53
- --space-lg: 1.25rem; /* 20px */
54
- --space-xl: 1.5rem; /* 24px */
55
- --space-2xl: 2rem; /* 32px */
56
- --space-3xl: 2.5rem; /* 40px */
57
- --space-4xl: 3rem; /* 48px */
58
-
59
- /* 🔲 Border Radius */
60
- --radius-sm: 8px;
61
- --radius-md: 10px;
62
- --radius-lg: 12px;
63
- --radius-xl: 16px;
64
- --radius-full: 50%;
65
-
66
- /* 🎭 Shadows */
67
- --shadow-sm: 0 2px 6px rgba(0, 0, 0, 0.04);
68
- --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.06);
69
- --shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.08);
70
- --shadow-xl: 0 4px 20px rgba(0, 0, 0, 0.08);
71
-
72
- /* 🔄 Transitions */
73
- --transition-fast: 0.2s;
74
- --transition-base: 0.3s;
75
- --transition-slow: 0.4s;
76
-
77
- /* 📱 Breakpoints */
78
- --breakpoint-mobile: 768px;
79
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
static/images/Abstract Color Gradient.png DELETED

Git LFS Details

  • SHA256: cee66aec6f50a8eded3bdc4d334153f3274ddc3a67b76681c8a9bdfd04d2d201
  • Pointer size: 132 Bytes
  • Size of remote file: 1.09 MB
static/images/icon1.png DELETED

Git LFS Details

  • SHA256: a2e71f46fcefeb534d17602344529a7ce1530b0f5eff3193d2a97d81b262b9cd
  • Pointer size: 131 Bytes
  • Size of remote file: 130 kB
static/images/icon2.png DELETED

Git LFS Details

  • SHA256: a25325726de414f7de147a7e87b19a853b18ed3f14bdf1993811d215f9655470
  • Pointer size: 131 Bytes
  • Size of remote file: 147 kB
static/images/icon3.png DELETED

Git LFS Details

  • SHA256: 05033cf70fd5db198d73544eb46990d8bce1fd325f73d3ed86c4bc7fc2702a84
  • Pointer size: 131 Bytes
  • Size of remote file: 141 kB
static/landing.css DELETED
@@ -1,524 +0,0 @@
1
- @import url('design-tokens.css');
2
-
3
- /* ==========================================================================
4
- RESET & BASE STYLES
5
- ========================================================================== */
6
-
7
- * {
8
- margin: 0;
9
- padding: 0;
10
- box-sizing: border-box;
11
- }
12
-
13
- body {
14
- font-family: var(--font-family);
15
- line-height: 1.6;
16
- color: var(--text-primary);
17
- background: #ffffff;
18
- overflow-x: hidden;
19
- }
20
-
21
- /* ==========================================================================
22
- ANIMATIONS
23
- ========================================================================== */
24
-
25
- @keyframes gradientShift {
26
- 0%, 100% { transform: scale(1) rotate(0deg); }
27
- 50% { transform: scale(1.1) rotate(5deg); }
28
- }
29
-
30
- @keyframes fadeInUp {
31
- from {
32
- opacity: 0;
33
- transform: translateY(30px);
34
- }
35
- to {
36
- opacity: 1;
37
- transform: translateY(0);
38
- }
39
- }
40
-
41
- /* ==========================================================================
42
- NAVIGATION
43
- ========================================================================== */
44
-
45
- .navbar {
46
- position: fixed;
47
- top: 0;
48
- left: 0;
49
- right: 0;
50
- background: rgba(255, 255, 255, 0);
51
- backdrop-filter: blur(10px);
52
- border-bottom: 1px solid var(--border-divider);
53
- z-index: 1000;
54
- padding: var(--space-md) 0;
55
- }
56
-
57
- .nav-container {
58
- max-width: 1200px;
59
- margin: 0 auto;
60
- padding: 0 var(--space-xl);
61
- display: flex;
62
- justify-content: space-between;
63
- align-items: center;
64
- }
65
-
66
- .logo {
67
- font-size: var(--font-size-xl);
68
- font-weight: var(--font-weight-bold);
69
- color: var(--text-primary);
70
- }
71
-
72
- .nav-cta {
73
- padding: var(--space-sm) var(--space-xl);
74
- background: var(--primary-dark);
75
- color: var(--text-inverse);
76
- text-decoration: none;
77
- border-radius: var(--radius-sm);
78
- font-weight: var(--font-weight-medium);
79
- font-size: var(--font-size-sm);
80
- transition: all var(--transition-base);
81
- }
82
-
83
- .nav-cta:hover {
84
- background: var(--primary-dark);
85
- transform: translateY(-2px);
86
- box-shadow: var(--shadow-lg);
87
- }
88
-
89
- /* ==========================================================================
90
- HERO SECTION
91
- ========================================================================== */
92
-
93
- .hero-section {
94
- padding: 120px var(--space-2xl) var(--space-3xl);
95
- text-align: center;
96
- position: relative;
97
- min-height: 100vh;
98
- display: flex;
99
- align-items: center;
100
- overflow: hidden;
101
- background: transparent;
102
- }
103
-
104
- .hero-container {
105
- max-width: 1000px;
106
- margin: 0 auto;
107
- position: relative;
108
- z-index: 1;
109
- }
110
-
111
- .hero-video {
112
- position: fixed;
113
- top: 0;
114
- left: 0;
115
- width: 100vw;
116
- height: 100vh;
117
- object-fit: cover;
118
- z-index: -1;
119
- background: #fff;
120
- opacity: .4;
121
- visibility: visible;
122
- }
123
-
124
- .badge {
125
- display: inline-block;
126
- padding: var(--space-xs) var(--space-md);
127
- font-size: var(--font-size-sm);
128
- font-weight: var(--font-weight-medium);
129
- color: var(--info);
130
- margin-bottom: var(--space-md);
131
- animation: fadeInUp 0.6s ease;
132
- }
133
-
134
- .hero-title {
135
- font-size: 4.5rem;
136
- font-weight: var(--font-weight-bold);
137
- line-height: 1.1;
138
- margin-bottom: var(--space-xl);
139
- color: var(--text-primary);
140
- animation: fadeInUp 0.6s ease 0.1s both;
141
- }
142
-
143
- .hero-subtitle {
144
- font-size: var(--font-size-xl);
145
- color: var(--text-primary);
146
- max-width: 600px;
147
- margin: 0 auto 6rem;
148
- line-height: 1.8;
149
- animation: fadeInUp 0.6s ease 0.2s both;
150
- }
151
-
152
- .cta-buttons {
153
- animation: fadeInUp 0.6s ease 0.3s both;
154
- }
155
-
156
- .assessment-meta {
157
- display: flex;
158
- justify-content: center;
159
- gap: var(--space-2xl);
160
- margin-top: var(--space-2xl);
161
- font-size: var(--font-size-sm);
162
- color: var(--text-secondary);
163
- }
164
-
165
- /* ==========================================================================
166
- BUTTONS
167
- ========================================================================== */
168
-
169
- .btn-primary {
170
- display: inline-flex;
171
- align-items: center;
172
- gap: var(--space-sm);
173
- padding: var(--space-md) var(--space-2xl);
174
- font-size: var(--font-size-lg);
175
- font-weight: var(--font-weight-medium);
176
- text-decoration: none;
177
- border-radius: var(--radius-md);
178
- transition: all var(--transition-base);
179
- cursor: pointer;
180
- background: var(--primary-dark);
181
- color: var(--text-inverse);
182
- box-shadow: var(--shadow-xl);
183
- }
184
-
185
- .btn-primary:hover {
186
- background: linear-gradient(135deg, var(--background-color-2), var(--background-color-1));
187
- box-shadow: var(--shadow-xl);
188
- }
189
-
190
- .btn-primary .arrow {
191
- transition: transform var(--transition-base);
192
- }
193
-
194
- .btn-primary:hover .arrow {
195
- transform: translateX(5px);
196
- }
197
-
198
- /* ==========================================================================
199
- FEATURES SECTION
200
- ========================================================================== */
201
-
202
- /* .features-section {
203
- width: 100vw;
204
- min-height: 100vh;
205
- background: url("images/Abstract%20Color%20Gradient.png") center/cover no-repeat;
206
- display: flex;
207
- align-items: center;
208
- justify-content: center;
209
- z-index: 2;
210
- } */
211
-
212
- /* landing.css */
213
- .features-section {
214
- width: 100vw;
215
- min-height: 100vh;
216
- display: flex;
217
- align-items: center;
218
- justify-content: center;
219
- position: relative;
220
- overflow: hidden;
221
- z-index: 2;
222
-
223
- background: radial-gradient(
224
- 160% 130% at 50% -20%,
225
- rgba(219, 239, 253, 1) 0%,
226
- rgba(219, 239, 253, 0.65) 45%,
227
- rgba(255, 255, 255, 0.75) 65%,
228
- rgba(255, 255, 255, 1) 82%,
229
- rgba(255, 255, 255, 1) 100%
230
- ),
231
- radial-gradient(
232
- 170% 170% at 50% 140%,
233
- rgba(246, 228, 255, 0.95) 0%,
234
- rgba(246, 228, 255, 0.55) 55%,
235
- rgba(246, 228, 255, 0.15) 78%,
236
- rgba(255, 255, 255, 1) 100%
237
- );
238
- }
239
-
240
- .section-container {
241
- max-width: 1200px;
242
- margin: 0 auto;
243
- padding: 0 var(--space-xl);
244
- }
245
-
246
- .section-header {
247
- text-align: center;
248
- margin-bottom: var(--space-2xl);
249
- }
250
-
251
- .section-header h2 {
252
- font-size: var(--font-size-4xl);
253
- font-weight: var(--font-weight-extrabold);
254
- color: var(--text-primary);
255
- margin-bottom: var(--space-md);
256
- }
257
-
258
- .section-subtitle {
259
- font-size: var(--font-size-xl); /* same as hero-subtitle */
260
- color: var(--text-primary); /* hero uses primary */
261
- max-width: 600px; /* hero-subtitle width */
262
- margin: 0 auto var(--space-4xl); /* centered, comfortable bottom space */
263
- line-height: 1.8; /* easier reading like hero */
264
- animation: fadeInUp 0.6s ease 0.2s both; /* subtle entrance like hero */
265
- }
266
-
267
- .section-header p {
268
- font-size: var(--font-size-xl);
269
- color: var(--text-secondary);
270
- }
271
-
272
- .features-grid {
273
- display: grid;
274
- grid-template-columns: repeat(3, 1fr);
275
- gap: var(--space-xl);
276
- justify-items: center;
277
- }
278
-
279
- .feature-card {
280
- background: var(--bg-white);
281
- padding: var(--space-3xl);
282
- border-radius: var(--radius-lg);
283
- box-shadow: var(--shadow-md);
284
- transition: all var(--transition-base);
285
- }
286
-
287
- .feature-card:hover {
288
- transform: translateY(-8px);
289
- box-shadow: var(--shadow-xl);
290
- }
291
-
292
- .feature-icon-img {
293
- width: 68px;
294
- height: 68px;
295
- object-fit: contain;
296
- }
297
-
298
- .feature-card h3 {
299
- font-size: var(--font-size-2xl);
300
- font-weight: var(--font-weight-bold);
301
- color: var(--text-primary);
302
- margin-bottom: var(--space-md);
303
- }
304
-
305
- .feature-card p {
306
- color: var(--text-secondary);
307
- line-height: 1.8;
308
- }
309
-
310
- /* ==========================================================================
311
- CTA SECTION
312
- ========================================================================== */
313
-
314
- .cta-section {
315
- height: 100vh;
316
- display: flex;
317
- align-items: center;
318
- justify-content: center;
319
- text-align: center;
320
- color: var(--text-inverse);
321
- }
322
-
323
- .cta-container h2 {
324
- font-size: var(--font-size-4xl);
325
- font-weight: var(--font-weight-bold);
326
- margin-bottom: var(--space-md);
327
- color: var(--text-primary);
328
- }
329
-
330
- .cta-container p {
331
- font-size: var(--font-size-xl);
332
- margin-bottom: var(--space-3xl);
333
- color: var(--text-primary);
334
- }
335
-
336
- /* ==========================================================================
337
- FOOTER
338
- ========================================================================== */
339
-
340
- .footer {
341
- background: var(--primary-dark);
342
- color: var(--text-inverse);
343
- padding: var(--space-md) var(--space-md);
344
- text-align: center;
345
- }
346
-
347
- .footer-container {
348
- max-width: 1200px;
349
- margin: 0 auto;
350
- }
351
-
352
- .footer p {
353
- opacity: 0.7;
354
- }
355
-
356
- /* ==========================================================================
357
- RESPONSIVE DESIGN
358
- ========================================================================== */
359
-
360
- /* Tablet and smaller desktop */
361
- @media (max-width: 1024px) {
362
- .hero-title {
363
- font-size: 3.5rem;
364
- }
365
-
366
- .features-grid {
367
- grid-template-columns: repeat(2, 1fr);
368
- gap: var(--space-lg);
369
- }
370
-
371
- .feature-card {
372
- padding: var(--space-xl);
373
- }
374
- }
375
-
376
- /* Mobile landscape and smaller tablets */
377
- @media (max-width: 768px) {
378
- .nav-container {
379
- padding: 0 var(--space-md);
380
- }
381
-
382
- .hero-section {
383
- padding: 100px var(--space-md) var(--space-3xl);
384
- min-height: 100vh;
385
- }
386
-
387
- .hero-title {
388
- font-size: 2.5rem;
389
- line-height: 1.2;
390
- }
391
-
392
- .hero-subtitle {
393
- font-size: var(--font-size-lg);
394
- margin: 0 auto var(--space-2xl);
395
- }
396
-
397
- .assessment-meta {
398
- flex-direction: column;
399
- gap: var(--space-sm);
400
- margin-top: var(--space-xl);
401
- }
402
-
403
- .btn-primary {
404
- padding: var(--space-md) var(--space-xl);
405
- font-size: var(--font-size-base);
406
- width: 100%;
407
- max-width: 300px;
408
- justify-content: center;
409
- }
410
-
411
- .features-section {
412
- height: auto;
413
- min-height: 100vh;
414
- padding: var(--space-3xl) 0;
415
- }
416
-
417
- .section-container {
418
- padding: 0 var(--space-md);
419
- }
420
-
421
- .section-header h2 {
422
- font-size: var(--font-size-2xl);
423
- }
424
-
425
- .section-header p {
426
- font-size: var(--font-size-lg);
427
- }
428
-
429
- .features-grid {
430
- grid-template-columns: 1fr;
431
- gap: var(--space-lg);
432
- }
433
-
434
- .feature-card {
435
- padding: var(--space-xl);
436
- margin: 0 var(--space-sm);
437
- }
438
-
439
- .cta-section {
440
- height: auto;
441
- min-height: 100vh;
442
- padding: var(--space-3xl) var(--space-md);
443
- }
444
-
445
- .cta-container h2 {
446
- font-size: var(--font-size-2xl);
447
- margin-bottom: var(--space-lg);
448
- }
449
-
450
- .cta-container p {
451
- font-size: var(--font-size-lg);
452
- margin-bottom: var(--space-2xl);
453
- }
454
-
455
- .nav-cta {
456
- padding: var(--space-sm) var(--space-lg);
457
- font-size: var(--font-size-sm);
458
- }
459
- }
460
-
461
- /* Small mobile devices */
462
- @media (max-width: 480px) {
463
- .hero-section {
464
- padding: 80px var(--space-sm) var(--space-2xl);
465
- }
466
-
467
- .hero-title {
468
- font-size: 2rem;
469
- }
470
-
471
- .hero-subtitle {
472
- font-size: var(--font-size-base);
473
- }
474
-
475
- .section-container {
476
- padding: 0 var(--space-sm);
477
- }
478
-
479
- .feature-card {
480
- padding: var(--space-lg);
481
- margin: 0;
482
- }
483
-
484
- .cta-section {
485
- padding: var(--space-2xl) var(--space-sm);
486
- }
487
-
488
- .cta-container h2 {
489
- font-size: var(--font-size-xl);
490
- }
491
-
492
- .cta-container p {
493
- font-size: var(--font-size-base);
494
- }
495
-
496
- .btn-primary {
497
- padding: var(--space-sm) var(--space-lg);
498
- font-size: var(--font-size-sm);
499
- }
500
-
501
- .nav-cta {
502
- padding: var(--space-xs) var(--space-md);
503
- font-size: var(--font-size-xs);
504
- }
505
- }
506
-
507
- /* Very small mobile devices */
508
- @media (max-width: 320px) {
509
- .hero-title {
510
- font-size: 1.8rem;
511
- }
512
-
513
- .hero-subtitle {
514
- font-size: var(--font-size-sm);
515
- }
516
-
517
- .feature-card {
518
- padding: var(--space-md);
519
- }
520
-
521
- .cta-container h2 {
522
- font-size: var(--font-size-lg);
523
- }
524
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
static/script.js CHANGED
@@ -3,214 +3,33 @@ function sanitizeReligionName(name) {
3
  return name.replace(/\s+/g, '-');
4
  }
5
 
6
- // ==================== FIREBASE AUTHENTICATION ====================
7
-
8
- // Google Sign-In with Firebase
9
- async function signInWithGoogle() {
10
- if (!window.firebaseEnabled || !window.firebaseAuth) {
11
- document.getElementById('result').innerHTML =
12
- '<p class="error-msg">⚠️ Firebase not configured</p>';
13
- return;
14
- }
15
-
16
- const provider = new firebase.auth.GoogleAuthProvider();
17
-
18
- // Configure provider for better popup behavior
19
- provider.setCustomParameters({
20
- prompt: 'select_account' // Always show account selection
21
- });
22
-
23
- try {
24
- // Show loading state
25
- const resultDiv = document.getElementById('result');
26
- if (resultDiv) {
27
- resultDiv.innerHTML = '<p style="color: #666;">Opening sign-in window...</p>';
28
- }
29
-
30
- const result = await window.firebaseAuth.signInWithPopup(provider);
31
- const user = result.user;
32
-
33
- if (resultDiv) {
34
- resultDiv.innerHTML = '<p style="color: #666;">Signing in...</p>';
35
- }
36
-
37
- // Get ID token to send to backend
38
- const idToken = await user.getIdToken();
39
-
40
- // Send to backend for session creation
41
- const endpoint = window.location.pathname === '/signup' ? '/signup' : '/login';
42
- const response = await fetch(endpoint, {
43
- method: 'POST',
44
- headers: {'Content-Type': 'application/json'},
45
- body: JSON.stringify({idToken}),
46
- credentials: 'same-origin'
47
- });
48
-
49
- const data = await response.json();
50
-
51
- if (data.success) {
52
- window.location.href = '/assessment';
53
- } else {
54
- document.getElementById('result').innerHTML =
55
- `<p class="error-msg">${data.message || 'Authentication failed'}</p>`;
56
- }
57
- } catch (error) {
58
- console.error('Google Sign-In Error:', error);
59
- console.error('Error code:', error.code);
60
- console.error('Error details:', error);
61
-
62
- const resultDiv = document.getElementById('result');
63
- let errorMsg = '';
64
-
65
- // Handle specific error cases
66
- if (error.code === 'auth/popup-closed-by-user') {
67
- errorMsg = '❌ Sign-in window was closed. Please try again and complete the sign-in process.';
68
- } else if (error.code === 'auth/popup-blocked') {
69
- errorMsg = '❌ Pop-up was blocked by your browser. Please allow pop-ups for this site and try again.';
70
- } else if (error.code === 'auth/cancelled-popup-request') {
71
- errorMsg = '❌ Another sign-in is in progress. Please wait a moment and try again.';
72
- } else if (error.code === 'auth/unauthorized-domain') {
73
- errorMsg = '❌ This domain is not authorized for Google Sign-In. Please contact the administrator.';
74
- } else if (error.code === 'auth/operation-not-allowed') {
75
- errorMsg = '❌ Google Sign-In is not enabled. Please contact the administrator.';
76
- } else if (error.code === 'auth/network-request-failed') {
77
- errorMsg = '❌ Network error. Please check your internet connection and try again.';
78
- } else {
79
- errorMsg = `❌ ${error.message || 'Google Sign-In failed. Please try again.'}`;
80
- }
81
-
82
- if (resultDiv) {
83
- resultDiv.innerHTML = `<p class="error-msg">${errorMsg}</p>`;
84
- }
85
- }
86
- }
87
-
88
- // Firebase Email/Password Authentication
89
- async function authenticateWithFirebase(email, password, isSignup) {
90
- if (!window.firebaseEnabled || !window.firebaseAuth) {
91
- return null; // Fall back to legacy auth
92
- }
93
-
94
- try {
95
- let userCredential;
96
-
97
- if (isSignup) {
98
- // Create new user with Firebase
99
- userCredential = await window.firebaseAuth.createUserWithEmailAndPassword(email, password);
100
-
101
- // Send email verification
102
- await userCredential.user.sendEmailVerification();
103
- } else {
104
- // Sign in existing user
105
- userCredential = await window.firebaseAuth.signInWithEmailAndPassword(email, password);
106
-
107
- // Check if email is verified
108
- if (!userCredential.user.emailVerified) {
109
- document.getElementById('result').innerHTML =
110
- '<p class="error-msg">⚠️ Please verify your email first. Check your inbox.</p>';
111
- await window.firebaseAuth.signOut();
112
- return null;
113
- }
114
- }
115
-
116
- // Get ID token
117
- const idToken = await userCredential.user.getIdToken();
118
- return idToken;
119
-
120
- } catch (error) {
121
- console.error('Firebase Auth Error:', error);
122
- throw error;
123
- }
124
- }
125
-
126
  // ==================== AUTHENTICATION ====================
127
 
128
- async function authenticate() {
129
- const email = document.getElementById('authEmail').value.trim();
130
- const username = document.getElementById('authUsername') ? document.getElementById('authUsername').value.trim() : '';
131
  const password = document.getElementById('authPassword').value;
132
 
133
- if (!email || !password) {
134
  document.getElementById('result').innerHTML =
135
  '<p class="error-msg">⚠️ Please fill in all fields</p>';
136
  return;
137
  }
138
 
139
- const isSignup = window.location.pathname === '/signup';
140
- const endpoint = isSignup ? '/signup' : '/login';
141
-
142
- // Try Firebase authentication first if available
143
- if (window.firebaseEnabled) {
144
- try {
145
- const idToken = await authenticateWithFirebase(email, password, isSignup);
146
-
147
- if (!idToken) {
148
- return; // Error already displayed
149
- }
150
-
151
- // Send token to backend
152
- const response = await fetch(endpoint, {
153
- method: 'POST',
154
- headers: {'Content-Type': 'application/json'},
155
- body: JSON.stringify({idToken}),
156
- credentials: 'same-origin'
157
- });
158
-
159
- const data = await response.json();
160
-
161
- if (data.success) {
162
- if (isSignup) {
163
- document.getElementById('result').innerHTML =
164
- '<p class="success-msg">✅ Account created! Please check your email to verify.</p>';
165
- } else {
166
- window.location.href = '/assessment';
167
- }
168
- } else {
169
- document.getElementById('result').innerHTML =
170
- `<p class="error-msg">${data.message || 'Authentication failed'}</p>`;
171
- }
172
- return;
173
- } catch (error) {
174
- console.error('Firebase auth error:', error);
175
- document.getElementById('result').innerHTML =
176
- `<p class="error-msg">${error.message || 'Authentication failed'}</p>`;
177
- return;
178
- }
179
- }
180
-
181
- // Legacy authentication fallback (if Firebase is disabled)
182
- const body = isSignup
183
- ? {username, password, email}
184
- : {username, password};
185
 
186
  fetch(endpoint, {
187
  method: 'POST',
188
  headers: {'Content-Type': 'application/json'},
189
- body: JSON.stringify(body),
190
- credentials: 'same-origin'
191
- })
192
- .then(response => {
193
- if (!response.ok) {
194
- return response.json().then(err => Promise.reject(err));
195
- }
196
- return response.json();
197
  })
 
198
  .then(data => {
199
  if (data.success) {
200
- if (data.verification_sent) {
201
- document.getElementById('result').innerHTML =
202
- '<p class="success-msg">✅ ' + (data.message || 'Account created! Please check your email to verify.') + '</p>';
203
- } else {
204
- window.location.href = '/assessment';
205
- }
206
  } else {
207
  document.getElementById('result').innerHTML =
208
- `<p class="error-msg">${data.message || 'Authentication failed'}</p>`;
209
  }
210
- })
211
- .catch(error => {
212
- document.getElementById('result').innerHTML =
213
- `<p class="error-msg">${error.message || 'Network error. Please try again.'}</p>`;
214
  });
215
  }
216
 
@@ -219,80 +38,6 @@ function switchAuth() {
219
  window.location.href = newPath;
220
  }
221
 
222
- function resetPassword() {
223
- const email = document.getElementById('resetEmail').value.trim();
224
-
225
- if (!email) {
226
- document.getElementById('result').innerHTML =
227
- '<p class="error-msg">⚠️ Please enter your email</p>';
228
- return;
229
- }
230
-
231
- fetch('/forgot-password', {
232
- method: 'POST',
233
- headers: {'Content-Type': 'application/json'},
234
- body: JSON.stringify({email}),
235
- credentials: 'same-origin'
236
- })
237
- .then(response => {
238
- if (!response.ok) {
239
- return response.json().then(err => Promise.reject(err));
240
- }
241
- return response.json();
242
- })
243
- .then(data => {
244
- if (data.success) {
245
- document.getElementById('result').innerHTML =
246
- '<p class="success-msg">✅ Password reset link sent! Check your email (or server console in dev mode).</p>';
247
- } else {
248
- document.getElementById('result').innerHTML =
249
- `<p class="error-msg">${data.message || 'Failed to send reset link'}</p>`;
250
- }
251
- })
252
- .catch(error => {
253
- document.getElementById('result').innerHTML =
254
- `<p class="error-msg">${error.message || 'Network error. Please try again.'}</p>`;
255
- });
256
- }
257
-
258
- function submitPasswordReset() {
259
- const token = document.getElementById('resetToken').value;
260
- const password = document.getElementById('resetPassword').value;
261
-
262
- if (!password) {
263
- document.getElementById('result').innerHTML =
264
- '<p class="error-msg">⚠️ Please enter a new password</p>';
265
- return;
266
- }
267
-
268
- fetch('/reset-password-submit', {
269
- method: 'POST',
270
- headers: {'Content-Type': 'application/json'},
271
- body: JSON.stringify({token, password}),
272
- credentials: 'same-origin'
273
- })
274
- .then(response => {
275
- if (!response.ok) {
276
- return response.json().then(err => Promise.reject(err));
277
- }
278
- return response.json();
279
- })
280
- .then(data => {
281
- if (data.success) {
282
- document.getElementById('result').innerHTML =
283
- '<p class="success-msg">✅ Password reset successfully! Redirecting to login...</p>';
284
- setTimeout(() => window.location.href = '/login', 1500);
285
- } else {
286
- document.getElementById('result').innerHTML =
287
- `<p class="error-msg">${data.message || 'Failed to reset password'}</p>`;
288
- }
289
- })
290
- .catch(error => {
291
- document.getElementById('result').innerHTML =
292
- `<p class="error-msg">${error.message || 'Network error. Please try again.'}</p>`;
293
- });
294
- }
295
-
296
  // ==================== ASSESSMENT ====================
297
 
298
  var currentQuestion = 1;
@@ -324,22 +69,34 @@ function showQuestion(questionIndex) {
324
  block.classList.remove('active');
325
  var blockIndex = parseInt(block.getAttribute('data-question-index'));
326
  if (blockIndex === questionIndex) block.classList.add('active');
 
 
 
 
 
327
  });
328
 
329
  currentQuestion = questionIndex;
330
  document.getElementById('questionCounter').textContent = 'Question ' + questionIndex + ' of ' + totalQuestions;
331
- document.getElementById('progressBar').style.width = ((questionIndex - 1) / (totalQuestions - 1)) * 100 + '%';
332
  updateNavigationButtons();
333
  }
334
 
335
  function updateNavigationButtons() {
336
- // Check if we're on the last question and it's answered
337
- var currentQuestionId = questionIds[currentQuestion - 1];
338
- var radioName = 'q' + currentQuestionId;
339
- var isAnswered = document.querySelector('input[name="' + radioName + '"]:checked') !== null;
340
 
341
- if (currentQuestion === totalQuestions && isAnswered) {
342
- document.getElementById('submitBtn').style.display = 'block';
 
 
 
 
 
 
 
 
 
 
343
  }
344
  }
345
 
@@ -351,12 +108,18 @@ function handleAnswer(radioElement) {
351
  return;
352
  }
353
 
 
 
354
  // Auto-advance to next question after selection
355
  setTimeout(function() {
356
  if (questionIndex < totalQuestions) {
357
  showQuestion(questionIndex + 1);
358
  } else {
359
  // Last question - show submit button
 
 
 
 
360
  document.getElementById('submitBtn').style.display = 'block';
361
  }
362
  }, 400);
@@ -368,12 +131,6 @@ function goToNext() {
368
  }
369
  }
370
 
371
- function goToPrev() {
372
- if (currentQuestion > 1) {
373
- showQuestion(currentQuestion - 1);
374
- }
375
- }
376
-
377
  function submitAssessment() {
378
  var form = document.getElementById('assessmentForm');
379
  var answers = [];
@@ -433,30 +190,6 @@ function resetAssessment() {
433
  });
434
  }
435
 
436
- // ==================== SHARE RESULTS ====================
437
-
438
- function shareResults() {
439
- const results = getResultsSummary();
440
- const text = `My Spiritual Path Results:\n\n${results}\n\nTake the assessment: ${window.location.origin}`;
441
-
442
- if (navigator.share) {
443
- navigator.share({ title: 'Spiritual Path Results', text: text });
444
- } else {
445
- navigator.clipboard.writeText(text);
446
- alert('Results copied to clipboard!');
447
- }
448
- }
449
-
450
- function getResultsSummary() {
451
- const cards = document.querySelectorAll('.result-card');
452
- return Array.from(cards).map((card, i) => {
453
- const title = card.querySelector('h3').textContent;
454
- const percentage = card.querySelector('.result-percentage').textContent;
455
- return `${i+1}. ${title} (${percentage})`;
456
- }).join('\n');
457
- }
458
-
459
-
460
  // ==================== CHAT FUNCTIONALITY ====================
461
 
462
  var chatHistories = {};
@@ -593,118 +326,3 @@ function sendMessage(religionName) {
593
  });
594
  }
595
 
596
- // ==================== VOICE CHAT ====================
597
-
598
- var recognition = null;
599
- var currentReligion = null;
600
- var isRecording = false;
601
-
602
- function initializeSpeechRecognition() {
603
- if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
604
- recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
605
- recognition.continuous = true;
606
- recognition.interimResults = true;
607
- recognition.lang = 'en-US';
608
-
609
- recognition.onresult = function(event) {
610
- var interimTranscript = '';
611
- var finalTranscript = '';
612
-
613
- for (var i = event.resultIndex; i < event.results.length; i++) {
614
- var transcript = event.results[i][0].transcript;
615
- if (event.results[i].isFinal) {
616
- finalTranscript += transcript + ' ';
617
- } else {
618
- interimTranscript += transcript;
619
- }
620
- }
621
-
622
- // Update input field with live transcription
623
- if (currentReligion) {
624
- var inputId = 'input-' + sanitizeReligionName(currentReligion);
625
- var inputEl = document.getElementById(inputId);
626
- if (inputEl) {
627
- inputEl.value = finalTranscript + interimTranscript;
628
- }
629
- }
630
- };
631
-
632
- recognition.onerror = function(event) {
633
- console.error('Speech recognition error:', event.error);
634
- if (event.error === 'no-speech') {
635
- stopVoiceInput();
636
- }
637
- };
638
-
639
- recognition.onend = function() {
640
- if (isRecording) {
641
- // Restart if it stopped unexpectedly
642
- try {
643
- recognition.start();
644
- } catch(e) {
645
- stopVoiceInput();
646
- }
647
- }
648
- };
649
- }
650
- }
651
-
652
- function startVoiceInput(religionName) {
653
- if (!recognition) {
654
- alert('Speech recognition not supported in your browser');
655
- return;
656
- }
657
-
658
- currentReligion = religionName;
659
- isRecording = true;
660
-
661
- try {
662
- recognition.start();
663
- document.getElementById('voice-' + sanitizeReligionName(religionName)).classList.add('recording');
664
- } catch(e) {
665
- console.log('Already started or error:', e);
666
- }
667
- }
668
-
669
- function stopVoiceInput(religionName) {
670
- if (recognition && isRecording) {
671
- isRecording = false;
672
- recognition.stop();
673
- document.getElementById('voice-' + sanitizeReligionName(religionName)).classList.remove('recording');
674
- }
675
- }
676
-
677
- // Initialize on page load
678
- if (document.readyState === 'loading') {
679
- document.addEventListener('DOMContentLoaded', initializeSpeechRecognition);
680
- } else {
681
- initializeSpeechRecognition();
682
- }
683
-
684
- // Make tooltips appear instantly on hover
685
- document.addEventListener('DOMContentLoaded', function() {
686
- const micButtons = document.querySelectorAll('button[title]');
687
-
688
- micButtons.forEach(button => {
689
- const tooltipText = button.getAttribute('title');
690
- button.removeAttribute('title'); // Remove native tooltip
691
-
692
- // Create custom tooltip element
693
- const tooltip = document.createElement('div');
694
- tooltip.className = 'custom-tooltip';
695
- tooltip.textContent = tooltipText;
696
-
697
- // Add tooltip to button
698
- button.style.position = 'relative';
699
- button.appendChild(tooltip);
700
-
701
- button.addEventListener('mouseenter', function() {
702
- tooltip.style.opacity = '1';
703
- });
704
-
705
- button.addEventListener('mouseleave', function() {
706
- tooltip.style.opacity = '0';
707
- });
708
- });
709
- });
710
-
 
3
  return name.replace(/\s+/g, '-');
4
  }
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  // ==================== AUTHENTICATION ====================
7
 
8
+ function authenticate() {
9
+ const username = document.getElementById('authUsername').value.trim();
 
10
  const password = document.getElementById('authPassword').value;
11
 
12
+ if (!username || !password) {
13
  document.getElementById('result').innerHTML =
14
  '<p class="error-msg">⚠️ Please fill in all fields</p>';
15
  return;
16
  }
17
 
18
+ const endpoint = window.location.pathname === '/signup' ? '/signup' : '/login';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
  fetch(endpoint, {
21
  method: 'POST',
22
  headers: {'Content-Type': 'application/json'},
23
+ body: JSON.stringify({username, password})
 
 
 
 
 
 
 
24
  })
25
+ .then(response => response.json())
26
  .then(data => {
27
  if (data.success) {
28
+ window.location.href = '/';
 
 
 
 
 
29
  } else {
30
  document.getElementById('result').innerHTML =
31
+ `<p class="error-msg">${data.message}</p>`;
32
  }
 
 
 
 
33
  });
34
  }
35
 
 
38
  window.location.href = newPath;
39
  }
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  // ==================== ASSESSMENT ====================
42
 
43
  var currentQuestion = 1;
 
69
  block.classList.remove('active');
70
  var blockIndex = parseInt(block.getAttribute('data-question-index'));
71
  if (blockIndex === questionIndex) block.classList.add('active');
72
+ if (blockIndex < questionIndex) {
73
+ block.querySelectorAll('input[type="radio"]').forEach(function(radio) {
74
+ radio.disabled = true;
75
+ });
76
+ }
77
  });
78
 
79
  currentQuestion = questionIndex;
80
  document.getElementById('questionCounter').textContent = 'Question ' + questionIndex + ' of ' + totalQuestions;
81
+ document.getElementById('progressBar').style.width = ((questionIndex - 1) / totalQuestions) * 100 + '%';
82
  updateNavigationButtons();
83
  }
84
 
85
  function updateNavigationButtons() {
86
+ var nextBtn = document.getElementById('nextBtn' + currentQuestion);
 
 
 
87
 
88
+ if (nextBtn) {
89
+ var currentQuestionId = questionIds[currentQuestion - 1];
90
+ var radioName = 'q' + currentQuestionId;
91
+ var isAnswered = document.querySelector('input[name="' + radioName + '"]:checked') !== null;
92
+
93
+ if (currentQuestion === totalQuestions && isAnswered) {
94
+ nextBtn.style.display = 'none';
95
+ document.getElementById('submitBtn').style.display = 'block';
96
+ } else {
97
+ nextBtn.disabled = !isAnswered;
98
+ nextBtn.style.display = 'inline-block';
99
+ }
100
  }
101
  }
102
 
 
108
  return;
109
  }
110
 
111
+ updateNavigationButtons();
112
+
113
  // Auto-advance to next question after selection
114
  setTimeout(function() {
115
  if (questionIndex < totalQuestions) {
116
  showQuestion(questionIndex + 1);
117
  } else {
118
  // Last question - show submit button
119
+ var nextBtn = document.getElementById('nextBtn' + questionIndex);
120
+ if (nextBtn) {
121
+ nextBtn.style.display = 'none';
122
+ }
123
  document.getElementById('submitBtn').style.display = 'block';
124
  }
125
  }, 400);
 
131
  }
132
  }
133
 
 
 
 
 
 
 
134
  function submitAssessment() {
135
  var form = document.getElementById('assessmentForm');
136
  var answers = [];
 
190
  });
191
  }
192
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  // ==================== CHAT FUNCTIONALITY ====================
194
 
195
  var chatHistories = {};
 
326
  });
327
  }
328
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
static/style.css CHANGED
@@ -1,6 +1,3 @@
1
- @import url('design-tokens.css');
2
-
3
- /* ===== RESET & BASE ===== */
4
  * {
5
  margin: 0;
6
  padding: 0;
@@ -8,34 +5,24 @@
8
  }
9
 
10
  body {
11
- font-family: var(--font-family);
12
- background: linear-gradient(to bottom,
13
- rgb(255, 255, 255) 0%,
14
- rgb(255, 255, 255) 5%,
15
- rgb(226, 218, 250) 30%,
16
- rgb(219, 239, 253) 80%,
17
- rgb(255, 255, 255) 95%,
18
- rgb(255, 255, 255) 100%);
19
  min-height: 100vh;
20
  display: flex;
21
  justify-content: center;
22
  align-items: center;
23
- padding: var(--space-sm);
24
  }
25
 
26
-
27
- /* ===== LAYOUT ===== */
28
-
29
- /* Assessment container - keep original width for assessment/results */
30
- .assessment-container {
31
- background: var(--bg-white);
32
- border-radius: var(--radius-xl);
33
- padding: var(--space-lg);
34
- box-shadow: var(--shadow-xl);
35
  max-width: 700px;
36
  width: 100%;
37
- animation: slideIn var(--transition-slow) ease;
38
- max-height: 95vh;
39
  overflow-y: auto;
40
  }
41
 
@@ -44,58 +31,43 @@ body {
44
  to { opacity: 1; transform: translateY(0); }
45
  }
46
 
47
- /* ===== TYPOGRAPHY ===== */
48
  h1 {
49
- color: var(--text-primary);
50
- font-size: var(--font-size-4xl);
51
- margin-bottom: var(--space-xs);
52
- text-align: center;
53
- font-weight: var(--font-weight-bold);
54
- }
55
-
56
- h2 {
57
- font-size: var(--font-size-2xl); /* or var(--font-size-2xl) for even smaller */
58
  text-align: center;
59
- margin-bottom: var(--space-xs);
60
  }
61
 
62
  h3 {
63
  text-align: center;
64
- color: var(--text-primary);
65
- }
66
-
67
- /* ===== ICON STYLING ===== */
68
- .fa-heart {
69
- color: var(--error); /* Red color for heart icons */
70
  }
71
 
72
  p {
73
- color: var(--text-secondary);
74
  text-align: center;
75
- margin-bottom: var(--space-2xl);
76
- font-size: var(--font-size-base);
77
  }
78
 
79
  .subtitle {
80
- color: var(--text-secondary);
81
- font-size: var(--font-size-base);
82
  text-align: center;
83
- margin-bottom: var(--space-xl);
84
  line-height: 1.6;
85
- max-width: 540px;
86
- margin-left: auto;
87
- margin-right: auto;
88
  }
89
 
90
- /* ===== FORM ELEMENTS ===== */
91
- input[type="text"], input[type="password"], input[type="email"] {
92
  width: 100%;
93
- padding: var(--space-sm) var(--space-lg);
94
- font-size: var(--font-size-base);
95
  border: none;
96
- background: var(--bg-gray);
97
- border-radius: var(--radius-md);
98
- transition: all var(--transition-fast);
99
  outline: none;
100
  }
101
 
@@ -104,25 +76,25 @@ input:focus {
104
  box-shadow: 0 0 0 2px #E5E5E5;
105
  }
106
 
107
- /* ===== BUTTONS ===== */
108
  button, .btn, .nav-btn, .submit-btn {
109
- padding: var(--space-sm) var(--space-2xl);
110
- font-size: var(--font-size-base);
111
- background: var(--primary-dark);
112
- color: var(--text-inverse);
113
  border: none;
114
- border-radius: var(--radius-md);
115
  cursor: pointer;
116
- transition: all var(--transition-fast);
117
- font-weight: var(--font-weight-medium);
118
  text-decoration: none;
119
  display: inline-block;
120
  }
121
 
122
  button:hover, .btn:hover, .nav-btn:hover, .submit-btn:hover {
123
- background: var(--primary-black);
124
  transform: translateY(-1px);
125
- box-shadow: var(--shadow-lg);
126
  }
127
 
128
  button:active, .btn:active {
@@ -142,133 +114,58 @@ button:disabled {
142
  transform: none;
143
  }
144
 
145
- /* Secondary Buttons */
146
- .logout-btn, .reset-btn, .share-btn {
147
- background: var(--primary-light);
148
- padding: var(--space-xs) var(--space-md);
149
- font-size: var(--font-size-sm);
150
- font-weight: var(--font-weight-regular);
151
- margin-top: var(--space-sm);
152
  }
153
 
154
- .logout-btn:hover, .reset-btn:hover, .share-btn:hover {
155
  background: #4B5563;
156
- box-shadow: var(--shadow-lg);
157
  }
158
 
159
  /* Submit Button */
160
  .submit-btn {
161
  width: 100%;
162
- padding: var(--space-md);
163
- font-size: var(--font-size-base);
164
- margin-top: var(--space-sm);
165
  }
166
 
167
  /* Auth Form */
168
- /* Auth-specific container - narrower for login/signup/forgot-password */
169
- .auth-container {
170
- background: var(--bg-white);
171
- border-radius: var(--radius-xl);
172
- padding: var(--space-lg);
173
- box-shadow: var(--shadow-xl);
174
- max-width: 400px; /* Narrower for auth forms */
175
- width: 100%;
176
- animation: slideIn var(--transition-slow) ease;
177
- max-height: 95vh;
178
- overflow-y: auto;
179
- text-align: center;
180
- }
181
-
182
  .auth-form input {
183
  width: 100%;
184
- margin-bottom: var(--space-sm);
185
  }
186
 
187
  .auth-form button {
188
  width: 100%;
189
  }
190
 
191
- /* Google Sign-In Button */
192
- .google-signin-btn {
193
- width: 100%;
194
- padding: var(--space-sm) var(--space-lg);
195
- background: white;
196
- color: #444;
197
- border: 1px solid #ddd;
198
- border-radius: var(--radius-md);
199
- font-size: var(--font-size-base);
200
- font-weight: 500;
201
- cursor: pointer;
202
- display: flex;
203
- align-items: center;
204
- justify-content: center;
205
- gap: 12px;
206
- transition: all var(--transition-fast);
207
- margin-bottom: var(--space-md);
208
- }
209
-
210
- .google-signin-btn:hover {
211
- background: #f8f8f8;
212
- border-color: #ccc;
213
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
214
- }
215
-
216
- .google-signin-btn svg {
217
- flex-shrink: 0;
218
- }
219
-
220
- /* Divider for "or" between Google and email/password */
221
- .divider {
222
- display: flex;
223
- align-items: center;
224
- text-align: center;
225
- margin: var(--space-md) 0;
226
- color: var(--text-secondary);
227
- font-size: var(--font-size-sm);
228
- }
229
-
230
- .divider::before,
231
- .divider::after {
232
- content: '';
233
- flex: 1;
234
- border-bottom: 1px solid #ddd;
235
- }
236
-
237
- .divider span {
238
- padding: 0 var(--space-sm);
239
- }
240
-
241
- #result {
242
- margin-bottom: var(--space-md); /* 16px bottom margin only */
243
- margin-top: 0;
244
- padding: 0;
245
- }
246
-
247
  .switch-link {
248
- color: var(--text-primary);
249
  text-decoration: none;
250
  cursor: pointer;
251
- font-weight: var(--font-weight-regular);
252
- /* margin-top: 2px; */
253
- display: block;
254
- text-align: center;
255
- transition: color var(--transition-fast);
256
- font-size: var(--font-size-sm);
257
  }
258
 
259
  .switch-link:hover {
260
- color: var(--primary-black);
261
  text-decoration: underline;
262
  }
263
 
264
-
265
-
266
- /* ===== QUESTION BLOCKS ===== */
267
  .question-block {
268
- background: var(--bg-surface-alt);
269
- padding: var(--space-xl);
270
- border-radius: var(--radius-lg);
271
- margin-bottom: var(--space-lg);
272
  border: none;
273
  display: none;
274
  min-height: 400px;
@@ -276,7 +173,7 @@ button:disabled {
276
 
277
  .question-block.active {
278
  display: block;
279
- animation: fadeIn var(--transition-base) ease;
280
  }
281
 
282
  @keyframes fadeIn {
@@ -285,53 +182,52 @@ button:disabled {
285
  }
286
 
287
  .question-block h4 {
288
- color: var(--text-primary);
289
- margin-bottom: var(--space-xl);
290
- font-weight: var(--font-weight-bold);
291
- font-size: var(--font-size-xl);
292
  line-height: 1.6;
293
  }
294
 
295
  .question-number {
296
  display: inline-block;
297
- background: var(--primary-dark);
298
- color: var(--text-inverse);
299
  width: 32px;
300
  height: 32px;
301
- border-radius: var(--radius-full);
302
  text-align: center;
303
  line-height: 32px;
304
- margin-right: var(--space-sm);
305
- font-size: var(--font-size-sm);
306
  }
307
 
308
- /* ===== OPTIONS ===== */
309
  .option {
310
- display: flex;
311
- align-items: center;
312
- padding: var(--space-lg) var(--space-xl);
313
- margin-bottom: var(--space-sm);
314
- background: var(--bg-white);
315
  border: none;
316
- border-radius: var(--radius-md);
317
  cursor: pointer;
318
- transition: all var(--transition-fast);
319
- font-size: var(--font-size-base);
320
- color: var(--text-primary);
321
- box-shadow: var(--shadow-sm);
322
  }
323
 
324
  .option:hover {
325
- background: var(--bg-gray);
326
  transform: translateX(5px);
327
- box-shadow: var(--shadow-lg);
328
  }
329
 
330
  .option input[type="radio"] {
331
- margin-right: var(--space-sm);
332
  cursor: pointer;
333
- width: 16px;
334
- height: 16px;
335
  }
336
 
337
  .option input[type="radio"]:disabled {
@@ -345,222 +241,187 @@ button:disabled {
345
  }
346
 
347
  .option:has(input[type="radio"]:disabled):hover {
348
- background: var(--bg-white);
349
  transform: none;
350
  }
351
 
352
- /* ===== PROGRESS & NAVIGATION ===== */
353
  .question-counter {
354
  text-align: center;
355
  color: #999;
356
- font-size: var(--font-size-sm);
357
- margin-bottom: var(--space-sm);
358
- font-weight: var(--font-weight-medium);
359
  }
360
 
361
  .progress-bar {
362
- background: var(--bg-gray);
363
- height: 6px;
364
  border-radius: 4px;
365
  overflow: hidden;
366
- margin-bottom: var(--space-lg);
367
  }
368
 
369
  .progress-fill {
370
- background: var(--primary-dark);
371
  height: 100%;
372
- transition: width var(--transition-base);
373
  }
374
 
 
375
  .nav-buttons {
376
  display: flex;
377
- gap: var(--space-sm);
378
- justify-content: flex-start;
379
- margin-top: var(--space-lg);
380
- }
381
-
382
- .nav-btn.prev {
383
- background: transparent;
384
- color: #999;
385
- border: none;
386
- padding: 0;
387
- font-weight: var(--font-weight-medium);
388
- font-size: var(--font-size-sm);
389
- text-decoration: none;
390
- display: inline-flex;
391
- align-items: center;
392
- gap: 4px;
393
- transition: all var(--transition-base) ease;
394
- box-shadow: none;
395
- }
396
-
397
- .nav-btn.prev:hover {
398
- background: transparent;
399
- color: #666;
400
- transform: translateX(-3px);
401
- box-shadow: none;
402
  }
403
 
404
  .buttons-row {
405
  display: flex;
406
- gap: var(--space-sm);
407
  justify-content: center;
408
- margin-top: var(--space-lg);
409
- }
410
-
411
- /* ===== RESULTS ===== */
412
- .results-explanation {
413
- max-width: 400px; /* Adjust this value as needed */
414
- margin: 0 auto var(--space-sm);
415
- text-align: center;
416
- color: var(--text-secondary);
417
- font-size: var(--font-size-sm);
418
- line-height: 1.5;
419
  }
420
 
 
421
  .result-card {
422
- padding: var(--space-xl);
423
- border-radius: var(--radius-lg);
424
- margin-bottom: var(--space-3xl);
425
  border: none;
426
- box-shadow: var(--shadow-md);
427
  }
428
 
429
  .result-card.rank-1 {
430
- background: var(--rank-1);
431
  }
432
 
433
  .result-card.rank-2 {
434
- background: var(--rank-2);
435
  }
436
 
437
  .result-card.rank-3 {
438
- background: var(--rank-3);
439
  }
440
 
441
  .result-header {
442
  display: flex;
443
  align-items: center;
444
  justify-content: space-between;
445
- margin-bottom: var(--space-sm);
446
  }
447
 
448
  .result-rank {
449
- color: var(--text-inverse);
450
- width: 32px;
451
- height: 32px;
452
- border-radius: var(--radius-full);
453
  display: flex;
454
  align-items: center;
455
  justify-content: center;
456
- font-size: var(--font-size-xl);
457
- font-weight: var(--font-weight-extrabold);
458
- background: var(--primary-dark);
459
  }
460
 
461
  .result-title {
462
  flex: 1;
463
- margin-left: var(--space-sm);
464
  }
465
 
466
  .result-title h3 {
467
- font-size: var(--font-size-3xl);
468
  margin-bottom: 5px;
469
- color: var(--text-primary);
470
- }
471
-
472
- .result-details h5 i {
473
- font-size: var(--font-size-sm);
474
- margin-right: var(--space-xs);
475
  }
476
 
477
  .result-percentage {
478
- font-size: var(--font-size-xl);
479
- font-weight: var(--font-weight-bold);
480
- color: var(--text-secondary);
481
  }
482
 
483
  .result-description {
484
  color: #666;
485
  line-height: 1.6;
486
- margin-bottom: var(--space-sm);
487
- font-size: var(--font-size-base);
488
  }
489
 
490
  .result-details {
491
  background: rgba(255, 255, 255, 0.6);
492
  border: none;
493
- padding: var(--space-sm);
494
- border-radius: var(--radius-sm);
495
- margin-top: var(--space-sm);
496
  }
497
 
498
  .result-details h5 {
499
- color: var(--text-primary);
500
- font-size: var(--font-size-base);
501
- margin-bottom: var(--space-xs);
502
- font-weight: var(--font-weight-medium);
503
  }
504
 
505
  .result-details p {
506
  color: #666;
507
- font-size: var(--font-size-sm);
508
- margin-bottom: var(--space-sm);
509
  text-align: left;
510
  }
511
 
512
- /* ===== MESSAGES & ICONS ===== */
513
  .icon {
514
- font-size: var(--font-size-3xl);
515
- margin-right: var(--space-xs);
516
  }
517
 
518
  .success-msg {
519
- color: var(--text-primary);
520
- font-weight: var(--font-weight-medium);
521
  }
522
 
523
  .error-msg {
524
  color: #666;
525
- font-weight: var(--font-weight-medium);
526
  text-align: center;
527
- padding: var(--space-sm);
528
  }
529
 
530
- /* ===== CHAT INTERFACE ===== */
531
  .chat-toggle-btn {
532
  background: rgba(255, 255, 255, 0.8);
533
- color: var(--text-primary);
534
  border: none;
535
- padding: var(--space-sm) var(--space-lg);
536
- border-radius: var(--radius-sm);
537
  cursor: pointer;
538
- font-size: var(--font-size-sm);
539
- font-weight: var(--font-weight-medium);
540
- margin-top: var(--space-sm);
541
  width: 100%;
542
- transition: background var(--transition-fast), color var(--transition-fast);
543
- box-shadow: var(--shadow-md);
544
  }
545
 
546
  .chat-toggle-btn:hover {
547
- background: var(--primary-dark);
548
- color: var(--text-inverse);
549
  }
550
 
551
  .chat-window {
552
  display: none;
553
  background: rgba(255, 255, 255, 0.9);
554
  border: none;
555
- border-radius: var(--radius-md);
556
- margin-top: var(--space-sm);
557
  overflow: hidden;
558
- box-shadow: var(--shadow-lg);
559
  }
560
 
561
  .chat-window.open {
562
  display: block;
563
- animation: slideDown var(--transition-base) ease;
564
  }
565
 
566
  @keyframes slideDown {
@@ -571,37 +432,37 @@ button:disabled {
571
  .chat-messages {
572
  height: 250px;
573
  overflow-y: auto;
574
- padding: var(--space-sm);
575
  background: transparent;
576
  }
577
 
578
  .chat-message {
579
- margin-bottom: var(--space-sm);
580
- padding: var(--space-sm) var(--space-sm);
581
- border-radius: var(--radius-sm);
582
  max-width: 85%;
583
  line-height: 1.5;
584
- font-size: var(--font-size-base);
585
  }
586
 
587
  .chat-message.user {
588
- background: var(--primary-dark);
589
- color: var(--text-inverse);
590
  margin-left: auto;
591
  text-align: right;
592
  }
593
 
594
  .chat-message.bot {
595
- background: var(--bg-white);
596
- color: var(--text-primary);
597
  border: none;
598
  text-align: left;
599
- box-shadow: var(--shadow-sm);
600
  }
601
 
602
  .chat-message.bot ul {
603
- margin: var(--space-sm) 0 0 0;
604
- padding-left: var(--space-lg);
605
  }
606
 
607
  .chat-message.bot li {
@@ -611,180 +472,55 @@ button:disabled {
611
 
612
  .chat-input-area {
613
  display: flex;
614
- gap: var(--space-xs);
615
- padding: var(--space-sm);
616
  background: transparent;
617
  border-top: none;
618
  }
619
 
620
  .chat-input {
621
  flex: 1;
622
- padding: var(--space-sm) var(--space-sm);
623
  border: none;
624
- background: var(--bg-white);
625
- border-radius: var(--radius-sm);
626
- font-size: var(--font-size-sm);
627
  outline: none;
628
- color: var(--text-primary);
629
- box-shadow: var(--shadow-sm);
630
  }
631
 
632
  .chat-input:focus {
633
- box-shadow: var(--shadow-md);
634
  }
635
 
636
  .chat-send-btn {
637
- padding: var(--space-sm) var(--space-lg);
638
- background: var(--primary-dark);
639
- color: var(--text-inverse);
640
  border: none;
641
- border-radius: var(--radius-sm);
642
  cursor: pointer;
643
- font-weight: var(--font-weight-medium);
644
- font-size: var(--font-size-sm);
645
- transition: background var(--transition-fast);
646
  }
647
 
648
  .chat-send-btn:hover {
649
- background: var(--primary-black);
650
- color: var(--text-inverse);
651
  }
652
 
653
  .chat-send-btn:disabled {
654
- background: var(--bg-gray);
655
  color: #999;
656
- }
657
-
658
- .voice-btn {
659
- padding: var(--space-sm) var(--space-sm);
660
- background: var(--primary-dark);
661
- color: var(--text-inverse);
662
- border: none;
663
- border-radius: var(--radius-sm);
664
- cursor: pointer;
665
- font-size: var(--font-size-lg);
666
- transition: all var(--transition-fast);
667
- }
668
-
669
- .voice-btn:hover {
670
- background: var(--info);
671
- }
672
-
673
- .voice-btn.recording {
674
- background: var(--error);
675
- animation: pulse 1s infinite;
676
- }
677
-
678
- @keyframes pulse {
679
- 0%, 100% { opacity: 1; }
680
- 50% { opacity: 0.7; }
681
  }
682
 
683
  .chat-typing {
684
  color: #999;
685
  font-style: italic;
686
- font-size: var(--font-size-sm);
687
- padding: var(--space-sm) var(--space-sm);
688
- }
689
-
690
- /* ===== NAVIGATION & UTILITIES ===== */
691
- .nav-header {
692
- padding: 0 0 var(--space-lg) 0;
693
- margin-bottom: var(--space-lg);
694
- border-bottom: 1px solid var(--border-divider);
695
  }
696
 
697
- .back-link {
698
- color: #667eea;
699
- text-decoration: none;
700
- font-weight: var(--font-weight-medium);
701
- font-size: var(--font-size-sm);
702
- transition: all var(--transition-base) ease;
703
- display: inline-flex;
704
- align-items: center;
705
- gap: 4px;
706
-
707
- }
708
-
709
- .back-link:hover {
710
- color: #5a6fd8;
711
- transform: translateX(-3px);
712
- }
713
-
714
- .results-spacing {
715
- margin-top: var(--space-3xl);
716
- }
717
-
718
- /* ===== MOBILE RESPONSIVE ===== */
719
- @media (max-width: 768px) {
720
- .container {
721
- padding: var(--space-sm);
722
- border-radius: var(--radius-lg);
723
- margin: 5px;
724
- }
725
-
726
- h1 {
727
- font-size: var(--font-size-3xl);
728
- }
729
-
730
- p {
731
- font-size: var(--font-size-lg);
732
- }
733
-
734
- .subtitle {
735
- font-size: var(--font-size-base);
736
- }
737
-
738
- input[type="text"], input[type="password"], input[type="email"] {
739
- padding: var(--space-md) var(--space-lg);
740
- font-size: var(--font-size-lg);
741
- }
742
-
743
- button, .btn, .nav-btn, .submit-btn {
744
- padding: var(--space-md) var(--space-2xl);
745
- font-size: var(--font-size-lg);
746
- }
747
-
748
- .question-block {
749
- padding: var(--space-lg);
750
- min-height: 350px;
751
- }
752
-
753
- .question-block h4 {
754
- font-size: var(--font-size-2xl);
755
- }
756
-
757
- .option {
758
- padding: var(--space-lg) var(--space-xl);
759
- margin-bottom: var(--space-md);
760
- font-size: var(--font-size-lg);
761
- }
762
-
763
- .option input[type="radio"] {
764
- width: 22px;
765
- height: 22px;
766
- margin-right: var(--space-md);
767
- }
768
-
769
- }
770
-
771
-
772
- /* Custom tooltip styles */
773
- .custom-tooltip {
774
- position: absolute;
775
- background: var(--primary-dark);
776
- color: var(--text-inverse);
777
- padding: var(--space-xs) var(--space-sm);
778
- border-radius: var(--radius-sm);
779
- font-family: var(--font-family);
780
- font-size: 13px;
781
- font-weight: var(--font-weight-regular);
782
- white-space: nowrap;
783
- z-index: 10000;
784
- pointer-events: none;
785
- opacity: 0;
786
- transition: opacity var(--transition-fast) ease-in-out;
787
- top: -50px;
788
- left: 50%;
789
- transform: translateX(-50%);
790
- }
 
 
 
 
1
  * {
2
  margin: 0;
3
  padding: 0;
 
5
  }
6
 
7
  body {
8
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
9
+ background: #F5F5F5;
 
 
 
 
 
 
10
  min-height: 100vh;
11
  display: flex;
12
  justify-content: center;
13
  align-items: center;
14
+ padding: 20px;
15
  }
16
 
17
+ .container {
18
+ background: white;
19
+ border-radius: 16px;
20
+ padding: 40px;
21
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
 
 
 
 
22
  max-width: 700px;
23
  width: 100%;
24
+ animation: slideIn 0.4s ease;
25
+ max-height: 90vh;
26
  overflow-y: auto;
27
  }
28
 
 
31
  to { opacity: 1; transform: translateY(0); }
32
  }
33
 
 
34
  h1 {
35
+ color: #3D3D3D;
36
+ font-size: 32px;
37
+ margin-bottom: 8px;
 
 
 
 
 
 
38
  text-align: center;
39
+ font-weight: 800;
40
  }
41
 
42
  h3 {
43
  text-align: center;
44
+ color: #3D3D3D;
 
 
 
 
 
45
  }
46
 
47
  p {
48
+ color: #6B7280;
49
  text-align: center;
50
+ margin-bottom: 30px;
51
+ font-size: 15px;
52
  }
53
 
54
  .subtitle {
55
+ color: #9CA3AF;
56
+ font-size: 14px;
57
  text-align: center;
58
+ margin-bottom: 25px;
59
  line-height: 1.6;
 
 
 
60
  }
61
 
62
+ /* Form Inputs */
63
+ input[type="text"], input[type="password"] {
64
  width: 100%;
65
+ padding: 12px 16px;
66
+ font-size: 15px;
67
  border: none;
68
+ background: #F5F5F5;
69
+ border-radius: 10px;
70
+ transition: all 0.2s;
71
  outline: none;
72
  }
73
 
 
76
  box-shadow: 0 0 0 2px #E5E5E5;
77
  }
78
 
79
+ /* Buttons - Consolidated styles */
80
  button, .btn, .nav-btn, .submit-btn {
81
+ padding: 12px 24px;
82
+ font-size: 15px;
83
+ background: #3D3D3D;
84
+ color: white;
85
  border: none;
86
+ border-radius: 10px;
87
  cursor: pointer;
88
+ transition: all 0.2s;
89
+ font-weight: 600;
90
  text-decoration: none;
91
  display: inline-block;
92
  }
93
 
94
  button:hover, .btn:hover, .nav-btn:hover, .submit-btn:hover {
95
+ background: #1A1A1A;
96
  transform: translateY(-1px);
97
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
98
  }
99
 
100
  button:active, .btn:active {
 
114
  transform: none;
115
  }
116
 
117
+ /* Secondary Buttons - Consolidated */
118
+ .logout-btn, .reset-btn {
119
+ background: #6B7280;
120
+ padding: 8px 16px;
121
+ font-size: 13px;
122
+ margin-top: 15px;
 
123
  }
124
 
125
+ .logout-btn:hover, .reset-btn:hover {
126
  background: #4B5563;
127
+ box-shadow: 0 4px 12px rgba(107, 114, 128, 0.3);
128
  }
129
 
130
  /* Submit Button */
131
  .submit-btn {
132
  width: 100%;
133
+ padding: 16px;
134
+ font-size: 16px;
135
+ margin-top: 10px;
136
  }
137
 
138
  /* Auth Form */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  .auth-form input {
140
  width: 100%;
141
+ margin-bottom: 15px;
142
  }
143
 
144
  .auth-form button {
145
  width: 100%;
146
  }
147
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  .switch-link {
149
+ color: #3D3D3D;
150
  text-decoration: none;
151
  cursor: pointer;
152
+ font-weight: 600;
153
+ margin-top: 15px;
154
+ display: inline-block;
155
+ transition: color 0.2s;
 
 
156
  }
157
 
158
  .switch-link:hover {
159
+ color: #1A1A1A;
160
  text-decoration: underline;
161
  }
162
 
163
+ /* Question Blocks */
 
 
164
  .question-block {
165
+ background: #FAFAFA;
166
+ padding: 30px;
167
+ border-radius: 12px;
168
+ margin-bottom: 20px;
169
  border: none;
170
  display: none;
171
  min-height: 400px;
 
173
 
174
  .question-block.active {
175
  display: block;
176
+ animation: fadeIn 0.3s ease;
177
  }
178
 
179
  @keyframes fadeIn {
 
182
  }
183
 
184
  .question-block h4 {
185
+ color: #3D3D3D;
186
+ margin-bottom: 25px;
187
+ font-weight: 700;
188
+ font-size: 18px;
189
  line-height: 1.6;
190
  }
191
 
192
  .question-number {
193
  display: inline-block;
194
+ background: #3D3D3D;
195
+ color: #fff;
196
  width: 32px;
197
  height: 32px;
198
+ border-radius: 50%;
199
  text-align: center;
200
  line-height: 32px;
201
+ margin-right: 12px;
202
+ font-size: 16px;
203
  }
204
 
205
+ /* Options */
206
  .option {
207
+ display: block;
208
+ padding: 16px 20px;
209
+ margin-bottom: 12px;
210
+ background: white;
 
211
  border: none;
212
+ border-radius: 10px;
213
  cursor: pointer;
214
+ transition: all 0.2s;
215
+ font-size: 15px;
216
+ color: #3D3D3D;
217
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
218
  }
219
 
220
  .option:hover {
221
+ background: #F5F5F5;
222
  transform: translateX(5px);
223
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
224
  }
225
 
226
  .option input[type="radio"] {
227
+ margin-right: 12px;
228
  cursor: pointer;
229
+ width: 18px;
230
+ height: 18px;
231
  }
232
 
233
  .option input[type="radio"]:disabled {
 
241
  }
242
 
243
  .option:has(input[type="radio"]:disabled):hover {
244
+ background: white;
245
  transform: none;
246
  }
247
 
248
+ /* Question Counter & Progress */
249
  .question-counter {
250
  text-align: center;
251
  color: #999;
252
+ font-size: 14px;
253
+ margin-bottom: 15px;
254
+ font-weight: 600;
255
  }
256
 
257
  .progress-bar {
258
+ background: #F5F5F5;
259
+ height: 8px;
260
  border-radius: 4px;
261
  overflow: hidden;
262
+ margin-bottom: 20px;
263
  }
264
 
265
  .progress-fill {
266
+ background: #3D3D3D;
267
  height: 100%;
268
+ transition: width 0.3s;
269
  }
270
 
271
+ /* Navigation */
272
  .nav-buttons {
273
  display: flex;
274
+ gap: 12px;
275
+ justify-content: space-between;
276
+ margin-top: 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
  }
278
 
279
  .buttons-row {
280
  display: flex;
281
+ gap: 10px;
282
  justify-content: center;
283
+ margin-top: 20px;
 
 
 
 
 
 
 
 
 
 
284
  }
285
 
286
+ /* Results */
287
  .result-card {
288
+ padding: 25px;
289
+ border-radius: 12px;
290
+ margin-bottom: 20px;
291
  border: none;
292
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
293
  }
294
 
295
  .result-card.rank-1 {
296
+ background: #FFFACD;
297
  }
298
 
299
  .result-card.rank-2 {
300
+ background: #B4C7E7;
301
  }
302
 
303
  .result-card.rank-3 {
304
+ background: #90EE90;
305
  }
306
 
307
  .result-header {
308
  display: flex;
309
  align-items: center;
310
  justify-content: space-between;
311
+ margin-bottom: 15px;
312
  }
313
 
314
  .result-rank {
315
+ color: #fff;
316
+ width: 40px;
317
+ height: 40px;
318
+ border-radius: 50%;
319
  display: flex;
320
  align-items: center;
321
  justify-content: center;
322
+ font-size: 20px;
323
+ font-weight: 800;
324
+ background: #3D3D3D;
325
  }
326
 
327
  .result-title {
328
  flex: 1;
329
+ margin-left: 15px;
330
  }
331
 
332
  .result-title h3 {
333
+ font-size: 22px;
334
  margin-bottom: 5px;
335
+ color: #3D3D3D;
 
 
 
 
 
336
  }
337
 
338
  .result-percentage {
339
+ font-size: 24px;
340
+ font-weight: 800;
341
+ color: #3D3D3D;
342
  }
343
 
344
  .result-description {
345
  color: #666;
346
  line-height: 1.6;
347
+ margin-bottom: 12px;
348
+ font-size: 14px;
349
  }
350
 
351
  .result-details {
352
  background: rgba(255, 255, 255, 0.6);
353
  border: none;
354
+ padding: 15px;
355
+ border-radius: 8px;
356
+ margin-top: 15px;
357
  }
358
 
359
  .result-details h5 {
360
+ color: #3D3D3D;
361
+ font-size: 13px;
362
+ margin-bottom: 8px;
363
+ font-weight: 700;
364
  }
365
 
366
  .result-details p {
367
  color: #666;
368
+ font-size: 13px;
369
+ margin-bottom: 10px;
370
  text-align: left;
371
  }
372
 
373
+ /* Icons & Messages */
374
  .icon {
375
+ font-size: 24px;
376
+ margin-right: 8px;
377
  }
378
 
379
  .success-msg {
380
+ color: #3D3D3D;
381
+ font-weight: 600;
382
  }
383
 
384
  .error-msg {
385
  color: #666;
386
+ font-weight: 600;
387
  text-align: center;
388
+ padding: 10px;
389
  }
390
 
391
+ /* Chat Interface */
392
  .chat-toggle-btn {
393
  background: rgba(255, 255, 255, 0.8);
394
+ color: #3D3D3D;
395
  border: none;
396
+ padding: 10px 20px;
397
+ border-radius: 8px;
398
  cursor: pointer;
399
+ font-size: 14px;
400
+ font-weight: 600;
401
+ margin-top: 15px;
402
  width: 100%;
403
+ transition: background 0.2s, color 0.2s;
404
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
405
  }
406
 
407
  .chat-toggle-btn:hover {
408
+ background: #3D3D3D;
409
+ color: #fff;
410
  }
411
 
412
  .chat-window {
413
  display: none;
414
  background: rgba(255, 255, 255, 0.9);
415
  border: none;
416
+ border-radius: 10px;
417
+ margin-top: 15px;
418
  overflow: hidden;
419
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
420
  }
421
 
422
  .chat-window.open {
423
  display: block;
424
+ animation: slideDown 0.3s ease;
425
  }
426
 
427
  @keyframes slideDown {
 
432
  .chat-messages {
433
  height: 250px;
434
  overflow-y: auto;
435
+ padding: 15px;
436
  background: transparent;
437
  }
438
 
439
  .chat-message {
440
+ margin-bottom: 12px;
441
+ padding: 10px 14px;
442
+ border-radius: 8px;
443
  max-width: 85%;
444
  line-height: 1.5;
445
+ font-size: 14px;
446
  }
447
 
448
  .chat-message.user {
449
+ background: #3D3D3D;
450
+ color: #fff;
451
  margin-left: auto;
452
  text-align: right;
453
  }
454
 
455
  .chat-message.bot {
456
+ background: white;
457
+ color: #3D3D3D;
458
  border: none;
459
  text-align: left;
460
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04);
461
  }
462
 
463
  .chat-message.bot ul {
464
+ margin: 8px 0 0 0;
465
+ padding-left: 20px;
466
  }
467
 
468
  .chat-message.bot li {
 
472
 
473
  .chat-input-area {
474
  display: flex;
475
+ gap: 8px;
476
+ padding: 12px;
477
  background: transparent;
478
  border-top: none;
479
  }
480
 
481
  .chat-input {
482
  flex: 1;
483
+ padding: 10px 14px;
484
  border: none;
485
+ background: white;
486
+ border-radius: 8px;
487
+ font-size: 14px;
488
  outline: none;
489
+ color: #3D3D3D;
490
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04);
491
  }
492
 
493
  .chat-input:focus {
494
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
495
  }
496
 
497
  .chat-send-btn {
498
+ padding: 10px 20px;
499
+ background: #3D3D3D;
500
+ color: #fff;
501
  border: none;
502
+ border-radius: 8px;
503
  cursor: pointer;
504
+ font-weight: 600;
505
+ font-size: 14px;
506
+ transition: background 0.2s;
507
  }
508
 
509
  .chat-send-btn:hover {
510
+ background: #1A1A1A;
511
+ color: #fff;
512
  }
513
 
514
  .chat-send-btn:disabled {
515
+ background: #F5F5F5;
516
  color: #999;
517
+ cursor: not-allowed;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
518
  }
519
 
520
  .chat-typing {
521
  color: #999;
522
  font-style: italic;
523
+ font-size: 13px;
524
+ padding: 10px 14px;
 
 
 
 
 
 
 
525
  }
526
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
static/video/Animation (Light theme).mp4 DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:c53cd9d87f17d8c3e0500572a448609819d7bb1f44d61e19727c65ed15977ac1
3
- size 6026968
 
 
 
 
templates/index.html CHANGED
@@ -1,132 +1,35 @@
1
  <!DOCTYPE html>
2
  <html>
3
  <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>{{ title if logged_in else 'Spiritual Path Finder - Login' }}</title>
7
  <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
8
- <!-- Add Font Awesome here -->
9
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
10
-
11
- <!-- Firebase SDK -->
12
- <script src="https://www.gstatic.com/firebasejs/10.7.1/firebase-app-compat.js"></script>
13
- <script src="https://www.gstatic.com/firebasejs/10.7.1/firebase-auth-compat.js"></script>
14
-
15
- {% if firebase_config and firebase_config.apiKey %}
16
- <script>
17
- // Initialize Firebase
18
- const firebaseConfig = {
19
- apiKey: "{{ firebase_config.apiKey }}",
20
- authDomain: "{{ firebase_config.authDomain }}",
21
- projectId: "{{ firebase_config.projectId }}",
22
- storageBucket: "{{ firebase_config.storageBucket }}",
23
- messagingSenderId: "{{ firebase_config.messagingSenderId }}",
24
- appId: "{{ firebase_config.appId }}"
25
- };
26
- firebase.initializeApp(firebaseConfig);
27
- window.firebaseAuth = firebase.auth();
28
- window.firebaseEnabled = true;
29
- </script>
30
- {% else %}
31
- <script>
32
- window.firebaseEnabled = false;
33
- </script>
34
- {% endif %}
35
  </head>
36
  <body>
37
- <div class="{% if not logged_in %}auth-container{% else %}assessment-container{% endif %}">
38
- <div class="nav-header">
39
- <a href="/" class="back-link">← Back to Home</a>
40
- </div>
41
  {% if not logged_in %}
42
  <!-- Login/Signup Form -->
43
- <h2><span class="icon"></span>Spiritual Path Finder</h2>
44
  <p>{{ 'Begin your journey of self-discovery' if is_signup else 'Welcome back, seeker!' }}</p>
45
 
46
- {% if verify_success %}
47
- <div class="success-msg" style="padding: 15px; margin-bottom: 20px; border-radius: 8px;">
48
- ✅ Email verified successfully! You can now sign in.
49
- </div>
50
- {% endif %}
51
-
52
- {% if verify_error %}
53
- <div class="error-msg" style="padding: 15px; margin-bottom: 20px; border-radius: 8px;">
54
- ⚠️ {{ verify_error }}
55
- </div>
56
- {% endif %}
57
-
58
- {% if reset_error %}
59
- <div class="error-msg" style="padding: 15px; margin-bottom: 20px; border-radius: 8px;">
60
- ⚠️ {{ reset_error }}
61
- </div>
62
- {% endif %}
63
-
64
  <div class="auth-form">
65
- {% if not is_forgot_password %}
66
- <!-- Firebase Google Sign-In Button -->
67
- {% if firebase_config and firebase_config.apiKey %}
68
- <button class="google-signin-btn" onclick="signInWithGoogle()">
69
- <svg width="18" height="18" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
70
- <path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"/>
71
- <path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"/>
72
- <path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"/>
73
- <path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"/>
74
- <path fill="none" d="M0 0h48v48H0z"/>
75
- </svg>
76
- Continue with Google
77
- </button>
78
-
79
- <div class="divider">
80
- <span>or</span>
81
- </div>
82
- {% endif %}
83
-
84
- <!-- Around line 84-88 -->
85
- {% if firebase_config and firebase_config.apiKey %}
86
- <input type="email" id="authEmail" placeholder="Email">
87
- {% else %}
88
- <input type="text" id="authUsername" placeholder="User name">
89
- {% endif %}
90
- {% if is_signup and firebase_config and firebase_config.apiKey %}
91
- <input type="text" id="authUsername" placeholder="Display Name (optional)">
92
- {% elif not firebase_config or not firebase_config.apiKey %}
93
- {% if is_signup %}
94
- <input type="email" id="authEmail" placeholder="Email">
95
- {% endif %}
96
- {% endif %}
97
-
98
  <button onclick="authenticate()">{{ 'Sign Up' if is_signup else 'Sign In' }}</button>
99
  <div id="result"></div>
100
- {% if not is_signup %}
101
- <p class="switch-link" onclick="window.location.href='/forgot-password'">
102
- Forgot Password?
103
- </p>
104
- {% endif %}
105
  <p class="switch-link" onclick="switchAuth()">
106
  {{ 'Already have an account? Sign In' if is_signup else 'New here? Sign Up' }}
107
  </p>
108
- {% else %}
109
- {% if show_reset_form %}
110
- <input type="password" id="resetPassword" placeholder="New Password">
111
- <input type="hidden" id="resetToken" value="{{ reset_token }}">
112
- <button onclick="submitPasswordReset()">Reset Password</button>
113
- <div id="result"></div>
114
- {% else %}
115
- <input type="email" id="resetEmail" placeholder="Email">
116
- <button onclick="resetPassword()">Send Reset Link</button>
117
- <div id="result"></div>
118
- {% endif %}
119
- <p class="switch-link" onclick="window.location.href='/login'">
120
- Back to Sign In
121
- </p>
122
- {% endif %}
123
  </div>
124
  {% else %}
125
  <!-- Assessment Interface -->
126
- <h2>{{ title }}</h2>
127
- <p>{{ message }} <i class="fas fa-heart"></i> </p>
128
 
129
  {% if not has_results %}
 
 
 
 
130
 
131
  <div class="question-counter" id="questionCounter">Question 1 of {{ questions|length }}</div>
132
 
@@ -158,9 +61,9 @@
158
  </label>
159
  {% endfor %}
160
 
161
- <div class="nav-buttons">
162
- <button type="button" class="nav-btn prev" id="prevBtn{{ q_index }}" onclick="goToPrev()" {% if loop.first %}style="visibility: hidden;"{% endif %}>
163
- Previous
164
  </button>
165
  </div>
166
  </div>
@@ -177,11 +80,8 @@
177
  <p class="subtitle">
178
  Based on your responses, here are the spiritual paths that align most closely with your values and beliefs:
179
  </p>
180
- <div class="results-explanation">
181
- * Percentages reflect how strongly your answers align with each path, using weighted scoring where more fundamental beliefs count more.
182
- </div>
183
 
184
- <div id="resultsContainer" class="results-spacing">
185
  {% for result in results %}
186
  <div class="result-card rank-{{ loop.index }}">
187
  <div class="result-header">
@@ -193,21 +93,22 @@
193
  </div>
194
  <p class="result-description">{{ result.description }}</p>
195
  <div class="result-details">
196
- <h5><i class="fa-solid fa-grip"></i> Common Practices:</h5>
197
  <p>{{ result.practices }}</p>
198
- <h5><i class="fa-solid fa-rainbow"></i> Core Beliefs:</h5>
199
  <p>{{ result.core_beliefs }}</p>
200
  </div>
201
 
202
  <button class="chat-toggle-btn" onclick="toggleChat('{{ result.name }}')">
203
- <i class="fa-solid fa-comment-dots"></i> Ask Questions About {{ result.name }}
204
  </button>
205
 
206
  <div class="chat-window" id="chat-{{ result.name|replace(' ', '-') }}">
207
  <div class="chat-messages" id="messages-{{ result.name|replace(' ', '-') }}">
208
  <div class="chat-message bot">
209
  Hi! Ask me anything about {{ result.name }}.<br>
210
- <span style="color:#666; font-size:14px;">
 
211
  Example: "How do I get started with this path?"
212
  </span>
213
  </div>
@@ -218,15 +119,6 @@
218
  id="input-{{ result.name|replace(' ', '-') }}"
219
  placeholder="Ask about {{ result.name }}..."
220
  onkeypress="if(event.key==='Enter') sendMessage('{{ result.name }}')">
221
- <button class="voice-btn"
222
- id="voice-{{ result.name|replace(' ', '-') }}"
223
- onmousedown="startVoiceInput('{{ result.name }}')"
224
- onmouseup="stopVoiceInput('{{ result.name }}')"
225
- ontouchstart="startVoiceInput('{{ result.name }}')"
226
- ontouchend="stopVoiceInput('{{ result.name }}')"
227
- title="Hold to record with live transcription">
228
- <i class="fa-solid fa-microphone"></i>
229
- </button>
230
  <button class="chat-send-btn"
231
  id="send-{{ result.name|replace(' ', '-') }}"
232
  onclick="sendMessage('{{ result.name }}')">
@@ -239,17 +131,13 @@
239
  </div>
240
 
241
  <div class="buttons-row">
242
- <button class="btn reset-btn" onclick="resetAssessment()">
243
- <i class="fa-solid fa-arrow-rotate-right"></i> Retake Assessment
244
- </button>
245
- <button class="btn share-btn" onclick="shareResults()">
246
- <i class="fa-solid fa-share-nodes"></i> Share Results
247
- </button>
248
- <a href="/logout" class="btn logout-btn">
249
- <i class="fa-solid fa-arrow-right-from-bracket"></i> Sign Out
250
- </a>
251
  </div>
252
  {% endif %}
 
 
 
 
253
  {% endif %}
254
  </div>
255
  <script src="{{ url_for('static', filename='script.js') }}"></script>
 
1
  <!DOCTYPE html>
2
  <html>
3
  <head>
 
 
4
  <title>{{ title if logged_in else 'Spiritual Path Finder - Login' }}</title>
5
  <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  </head>
7
  <body>
8
+ <div class="container">
 
 
 
9
  {% if not logged_in %}
10
  <!-- Login/Signup Form -->
11
+ <h1><span class="icon">🌟</span>Spiritual Path Finder</h1>
12
  <p>{{ 'Begin your journey of self-discovery' if is_signup else 'Welcome back, seeker!' }}</p>
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  <div class="auth-form">
15
+ <input type="text" id="authUsername" placeholder="Username">
16
+ <input type="password" id="authPassword" placeholder="Password">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  <button onclick="authenticate()">{{ 'Sign Up' if is_signup else 'Sign In' }}</button>
18
  <div id="result"></div>
 
 
 
 
 
19
  <p class="switch-link" onclick="switchAuth()">
20
  {{ 'Already have an account? Sign In' if is_signup else 'New here? Sign Up' }}
21
  </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  </div>
23
  {% else %}
24
  <!-- Assessment Interface -->
25
+ <h1>{{ title }}</h1>
26
+ <p>{{ message }}</p>
27
 
28
  {% if not has_results %}
29
+ <p class="subtitle">
30
+ Discover which spiritual or religious path aligns with your beliefs, values, and lifestyle.
31
+ Answer 8 thoughtful questions about your worldview.
32
+ </p>
33
 
34
  <div class="question-counter" id="questionCounter">Question 1 of {{ questions|length }}</div>
35
 
 
61
  </label>
62
  {% endfor %}
63
 
64
+ <div class="nav-buttons" style="justify-content: flex-end;">
65
+ <button type="button" class="nav-btn next" id="nextBtn{{ q_index }}" onclick="goToNext()" disabled>
66
+ Next
67
  </button>
68
  </div>
69
  </div>
 
80
  <p class="subtitle">
81
  Based on your responses, here are the spiritual paths that align most closely with your values and beliefs:
82
  </p>
 
 
 
83
 
84
+ <div id="resultsContainer">
85
  {% for result in results %}
86
  <div class="result-card rank-{{ loop.index }}">
87
  <div class="result-header">
 
93
  </div>
94
  <p class="result-description">{{ result.description }}</p>
95
  <div class="result-details">
96
+ <h5>📿 Common Practices:</h5>
97
  <p>{{ result.practices }}</p>
98
+ <h5>💭 Core Beliefs:</h5>
99
  <p>{{ result.core_beliefs }}</p>
100
  </div>
101
 
102
  <button class="chat-toggle-btn" onclick="toggleChat('{{ result.name }}')">
103
+ 💬 Ask Questions About {{ result.name }}
104
  </button>
105
 
106
  <div class="chat-window" id="chat-{{ result.name|replace(' ', '-') }}">
107
  <div class="chat-messages" id="messages-{{ result.name|replace(' ', '-') }}">
108
  <div class="chat-message bot">
109
  Hi! Ask me anything about {{ result.name }}.<br>
110
+ <span style="color:#7C3AED; font-size:13px;">
111
+ Example: "What are the daily practices?"<br>
112
  Example: "How do I get started with this path?"
113
  </span>
114
  </div>
 
119
  id="input-{{ result.name|replace(' ', '-') }}"
120
  placeholder="Ask about {{ result.name }}..."
121
  onkeypress="if(event.key==='Enter') sendMessage('{{ result.name }}')">
 
 
 
 
 
 
 
 
 
122
  <button class="chat-send-btn"
123
  id="send-{{ result.name|replace(' ', '-') }}"
124
  onclick="sendMessage('{{ result.name }}')">
 
131
  </div>
132
 
133
  <div class="buttons-row">
134
+ <button class="btn reset-btn" onclick="resetAssessment()">🔄 Retake Assessment</button>
 
 
 
 
 
 
 
 
135
  </div>
136
  {% endif %}
137
+
138
+ <div style="text-align: center;">
139
+ <a href="/logout" class="btn logout-btn">Logout</a>
140
+ </div>
141
  {% endif %}
142
  </div>
143
  <script src="{{ url_for('static', filename='script.js') }}"></script>
templates/landing.html DELETED
@@ -1,109 +0,0 @@
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>Spiritual Path Assessment</title>
7
- <link rel="stylesheet" href="{{ url_for('static', filename='landing.css') }}">
8
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
9
- <!-- Add Font Awesome here -->
10
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
11
- </head>
12
- <body>
13
- <div class="gradient-bg"></div>
14
-
15
- <nav class="navbar">
16
- <div class="nav-container">
17
- <div class="logo"> <i class="fa-solid fa-compass"></i> Spiritual Path</div>
18
- <!-- <a href="/login" class="nav-cta">Start Assessment</a> -->
19
- </div>
20
- </nav>
21
-
22
- <main class="main-content">
23
- <section class="hero-section">
24
- <!-- Video Background -->
25
- <video class="hero-video" autoplay muted loop playsinline preload="auto">
26
- <source src="{{ url_for('static', filename='video/Animation (Light theme).mp4') }}" type="video/mp4">
27
- Your browser does not support the video tag.
28
- </video>
29
-
30
- <div class="hero-container">
31
- <div class="badge"> <i class="fa-solid fa-compass"></i> Discover Your Path</div>
32
- <h1 class="hero-title">
33
- Find Your Spiritual Journey
34
- </h1>
35
- <p class="hero-subtitle">
36
- Discover the spiritual or religious path that resonates with your unique beliefs, values, and lifestyle.
37
- Answer 9 thoughtful questions to gain deep insights into your worldview.
38
- </p>
39
-
40
- <div class="cta-buttons">
41
- <a href="/login" class="btn-primary">
42
- Begin Your Assessment
43
- <span class="arrow">→</span>
44
- </a>
45
- <div class="assessment-meta">
46
- <span><i class="fa-solid fa-clock"></i> 5-10 minutes</span>
47
- <span><i class="fa-solid fa-heart"></i> Completely free</span>
48
- <span><i class="fa-solid fa-file"></i> Personalized insights</span>
49
- </div>
50
- </div>
51
- </div>
52
- </section>
53
-
54
- <section class="features-section">
55
- <div class="section-container">
56
- <div class="section-header">
57
- <h2>What You'll Discover</h2>
58
- <p class="section-subtitle">
59
- After completing the assessment, explore your top 3 spiritual paths and ask questions through our interactive chat feature.
60
- </p>
61
- </div>
62
-
63
- <div class="features-grid">
64
- <div class="feature-card">
65
- <div class="feature-icon-wrapper">
66
- <img src="{{ url_for('static', filename='images/icon3.png') }}" alt="ring icon" class="feature-icon-img">
67
- </div>
68
- <h3>Your Spiritual Dimensions</h3>
69
- <p>Understand your current spiritual development across multiple dimensions and uncover hidden aspects of your journey.</p>
70
- </div>
71
-
72
- <div class="feature-card">
73
- <div class="feature-icon-wrapper">
74
- <img src="{{ url_for('static', filename='images/icon2.png') }}" alt="round abstract icon" class="feature-icon-img">
75
- </div>
76
- <h3>Personalized Recommendations</h3>
77
- <p>Get specific guidance tailored to your unique spiritual path, beliefs, and goals for meaningful growth.</p>
78
- </div>
79
-
80
- <div class="feature-card">
81
- <div class="feature-icon-wrapper">
82
- <img src="{{ url_for('static', filename='images/icon1.png') }}" alt="star shaped icon" class="feature-icon-img">
83
- </div>
84
- <h3>Growth Opportunities</h3>
85
- <p>Identify areas for spiritual growth and receive practical steps to deepen your practice and understanding.</p>
86
- </div>
87
- </div>
88
- </div>
89
- </section>
90
-
91
- <section class="cta-section">
92
- <div class="cta-container">
93
- <h2>Ready to begin your journey?</h2>
94
- <p>Start your spiritual path assessment today</p>
95
- <a href="/login" class="btn-primary">
96
- Get Started Now
97
- <span class="arrow">→</span>
98
- </a>
99
- </div>
100
- </section>
101
- </main>
102
-
103
- <footer class="footer">
104
- <div class="footer-container">
105
- <p>© 2025 Spiritual Path Assessment. Empowering your spiritual journey.</p>
106
- </div>
107
- </footer>
108
- </body>
109
- </html>