ayush-thakur02 commited on
Commit
30fe542
·
verified ·
1 Parent(s): c271583

Upload 8 files

Browse files
Files changed (9) hide show
  1. .gitattributes +1 -0
  2. Dockerfile +11 -0
  3. app.py +370 -0
  4. requirements.txt +4 -0
  5. static/icon.png +3 -0
  6. static/script.js +1348 -0
  7. static/styles.css +978 -0
  8. templates/auth.html +362 -0
  9. templates/index.html +346 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ static/icon.png filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+
7
+ RUN pip install --no-cache-dir -r requirements.txt
8
+
9
+ COPY . .
10
+
11
+ CMD ["gunicorn", "-b", "0.0.0.0:7860", "app:app"]
app.py ADDED
@@ -0,0 +1,370 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify, session, redirect, url_for
2
+ from pymongo import MongoClient
3
+ from werkzeug.security import generate_password_hash, check_password_hash
4
+ from datetime import datetime, timedelta
5
+ import os
6
+ from functools import wraps
7
+ import uuid
8
+ import base64
9
+ import random
10
+ from dotenv import load_dotenv
11
+ load_dotenv()
12
+
13
+ app = Flask(__name__)
14
+ app.secret_key = os.urandom(24)
15
+
16
+ # MongoDB connection
17
+ MONGO_URI = os.getenv('MONGO_URI')
18
+ client = MongoClient(MONGO_URI)
19
+ db = client.startpage
20
+
21
+ # Collections
22
+ users = db.users
23
+ user_bookmarks = db.bookmarks
24
+ user_notes = db.notes
25
+ site_stats = db.site_stats
26
+ vault_passwords = db.vault_passwords
27
+
28
+ def login_required(f):
29
+ @wraps(f)
30
+ def decorated_function(*args, **kwargs):
31
+ if 'user_id' not in session:
32
+ return jsonify({'error': 'Authentication required'}), 401
33
+ return f(*args, **kwargs)
34
+ return decorated_function
35
+
36
+ @app.route('/')
37
+ def index():
38
+ # Track page visit
39
+ today = datetime.now().strftime('%Y-%m-%d')
40
+ site_stats.update_one(
41
+ {'date': today},
42
+ {'$inc': {'page_visits': 1}},
43
+ upsert=True
44
+ )
45
+
46
+ # Check if user is logged in
47
+ if 'user_id' not in session:
48
+ return render_template('auth.html')
49
+
50
+ return render_template('index.html', username=session.get('username', 'User'))
51
+
52
+ @app.route('/register', methods=['POST'])
53
+ def register():
54
+ data = request.json
55
+ username = data.get('username')
56
+ password = data.get('password')
57
+
58
+ if not username or not password:
59
+ return jsonify({'error': 'Username and password required'}), 400
60
+
61
+ if users.find_one({'username': username}):
62
+ return jsonify({'error': 'Username already exists'}), 400
63
+
64
+ user_id = str(uuid.uuid4())
65
+ hashed_password = generate_password_hash(password)
66
+
67
+ users.insert_one({
68
+ 'user_id': user_id,
69
+ 'username': username,
70
+ 'password': hashed_password,
71
+ 'created_at': datetime.now()
72
+ })
73
+
74
+ # Add default bookmarks for new user
75
+ default_bookmarks = [
76
+ { 'name': 'YouTube', 'url': 'https://youtube.com', 'icon': 'play_circle', 'user_id': user_id, 'created_at': datetime.now() },
77
+ { 'name': 'GitHub', 'url': 'https://github.com', 'icon': 'code', 'user_id': user_id, 'created_at': datetime.now() },
78
+ { 'name': 'Gmail', 'url': 'https://gmail.com', 'icon': 'mail', 'user_id': user_id, 'created_at': datetime.now() },
79
+ { 'name': 'Google Drive', 'url': 'https://drive.google.com', 'icon': 'cloud', 'user_id': user_id, 'created_at': datetime.now() },
80
+ { 'name': 'Netflix', 'url': 'https://netflix.com', 'icon': 'movie', 'user_id': user_id, 'created_at': datetime.now() },
81
+ { 'name': 'Reddit', 'url': 'https://reddit.com', 'icon': 'forum', 'user_id': user_id, 'created_at': datetime.now() },
82
+ { 'name': 'Twitter', 'url': 'https://twitter.com', 'icon': 'alternate_email', 'user_id': user_id, 'created_at': datetime.now() },
83
+ { 'name': 'LinkedIn', 'url': 'https://linkedin.com', 'icon': 'work', 'user_id': user_id, 'created_at': datetime.now() }
84
+ ]
85
+
86
+ user_bookmarks.insert_many(default_bookmarks)
87
+
88
+ session['user_id'] = user_id
89
+ session['username'] = username
90
+
91
+ return jsonify({'message': 'Registration successful'})
92
+
93
+ @app.route('/login', methods=['POST'])
94
+ def login():
95
+ data = request.json
96
+ username = data.get('username')
97
+ password = data.get('password')
98
+
99
+ if not username or not password:
100
+ return jsonify({'error': 'Username and password required'}), 400
101
+
102
+ user = users.find_one({'username': username})
103
+
104
+ if not user or not check_password_hash(user['password'], password):
105
+ return jsonify({'error': 'Invalid credentials'}), 401
106
+
107
+ session['user_id'] = user['user_id']
108
+ session['username'] = username
109
+
110
+ return jsonify({'message': 'Login successful'})
111
+
112
+ @app.route('/logout', methods=['POST'])
113
+ def logout():
114
+ session.clear()
115
+ return jsonify({'message': 'Logout successful'})
116
+
117
+ @app.route('/api/bookmarks', methods=['GET'])
118
+ @login_required
119
+ def get_bookmarks():
120
+ user_id = session['user_id']
121
+ bookmarks = list(user_bookmarks.find({'user_id': user_id}, {'_id': 0}))
122
+ return jsonify(bookmarks)
123
+
124
+ @app.route('/api/bookmarks', methods=['POST'])
125
+ @login_required
126
+ def save_bookmarks():
127
+ user_id = session['user_id']
128
+ data = request.json
129
+
130
+ user_bookmarks.delete_many({'user_id': user_id})
131
+
132
+ for bookmark in data.get('bookmarks', []):
133
+ bookmark['user_id'] = user_id
134
+ bookmark['created_at'] = datetime.now()
135
+ user_bookmarks.insert_one(bookmark)
136
+
137
+ return jsonify({'message': 'Bookmarks saved'})
138
+
139
+ @app.route('/api/notes', methods=['GET'])
140
+ @login_required
141
+ def get_notes():
142
+ user_id = session['user_id']
143
+ notes = list(user_notes.find({'user_id': user_id}, {'_id': 0}))
144
+ return jsonify(notes)
145
+
146
+ @app.route('/api/notes', methods=['POST'])
147
+ @login_required
148
+ def save_notes():
149
+ user_id = session['user_id']
150
+ data = request.json
151
+
152
+ user_notes.delete_many({'user_id': user_id})
153
+
154
+ for note in data.get('notes', []):
155
+ note['user_id'] = user_id
156
+ note['updated_at'] = datetime.now()
157
+ user_notes.insert_one(note)
158
+
159
+ return jsonify({'message': 'Notes saved'})
160
+
161
+ @app.route('/api/search', methods=['POST'])
162
+ @login_required
163
+ def track_search():
164
+ today = datetime.now().strftime('%Y-%m-%d')
165
+ site_stats.update_one(
166
+ {'date': today},
167
+ {'$inc': {'search_count': 1}},
168
+ upsert=True
169
+ )
170
+ return jsonify({'message': 'Search tracked'})
171
+
172
+ @app.route('/api/stats', methods=['GET'])
173
+ def get_stats():
174
+ today = datetime.now().strftime('%Y-%m-%d')
175
+ stats = site_stats.find_one({'date': today})
176
+
177
+ if not stats:
178
+ return jsonify({'page_visits': 0, 'search_count': 0})
179
+
180
+ return jsonify({
181
+ 'page_visits': stats.get('page_visits', 0),
182
+ 'search_count': stats.get('search_count', 0)
183
+ })
184
+
185
+ @app.route('/api/change-password', methods=['POST'])
186
+ @login_required
187
+ def change_password():
188
+ data = request.json
189
+ current_password = data.get('currentPassword')
190
+ new_password = data.get('newPassword')
191
+
192
+ if not current_password or not new_password:
193
+ return jsonify({'error': 'Current password and new password required'}), 400
194
+
195
+ user_id = session['user_id']
196
+ user = users.find_one({'user_id': user_id})
197
+
198
+ if not user or not check_password_hash(user['password'], current_password):
199
+ return jsonify({'error': 'Current password is incorrect'}), 400
200
+
201
+ if len(new_password) < 6:
202
+ return jsonify({'error': 'Password must be at least 6 characters long'}), 400
203
+
204
+ # Update password
205
+ hashed_password = generate_password_hash(new_password)
206
+ users.update_one(
207
+ {'user_id': user_id},
208
+ {'$set': {'password': hashed_password, 'updated_at': datetime.now()}}
209
+ )
210
+
211
+ return jsonify({'message': 'Password changed successfully'})
212
+
213
+ @app.route('/api/developer-stats', methods=['GET'])
214
+ @login_required
215
+ def get_developer_stats():
216
+ # Total users count
217
+ total_users = users.count_documents({})
218
+
219
+ # Total visits and searches across all time
220
+ all_stats = list(site_stats.find({}))
221
+ total_visits = sum(stat.get('page_visits', 0) for stat in all_stats)
222
+ total_searches = sum(stat.get('search_count', 0) for stat in all_stats)
223
+
224
+ # Monthly data (last 12 months)
225
+ monthly_data = []
226
+ current_date = datetime.now()
227
+
228
+ for i in range(12):
229
+ # Calculate the first day of each month
230
+ target_date = datetime(current_date.year, current_date.month - i, 1) if current_date.month - i > 0 else datetime(current_date.year - 1, current_date.month - i + 12, 1)
231
+
232
+ # Find all stats for that month
233
+ start_of_month = target_date
234
+ if target_date.month == 12:
235
+ end_of_month = datetime(target_date.year + 1, 1, 1)
236
+ else:
237
+ end_of_month = datetime(target_date.year, target_date.month + 1, 1)
238
+
239
+ # Query stats for the month
240
+ month_stats = site_stats.aggregate([
241
+ {
242
+ '$match': {
243
+ 'date': {
244
+ '$gte': start_of_month.strftime('%Y-%m-%d'),
245
+ '$lt': end_of_month.strftime('%Y-%m-%d')
246
+ }
247
+ }
248
+ },
249
+ {
250
+ '$group': {
251
+ '_id': None,
252
+ 'total_visits': {'$sum': '$page_visits'},
253
+ 'total_searches': {'$sum': '$search_count'}
254
+ }
255
+ }
256
+ ])
257
+
258
+ month_result = list(month_stats)
259
+ month_name = target_date.strftime('%b')
260
+
261
+ if month_result:
262
+ monthly_data.append({
263
+ 'month': month_name,
264
+ 'visits': month_result[0]['total_visits'],
265
+ 'searches': month_result[0]['total_searches']
266
+ })
267
+ else:
268
+ monthly_data.append({
269
+ 'month': month_name,
270
+ 'visits': 0,
271
+ 'searches': 0
272
+ })
273
+
274
+ # Reverse to get chronological order
275
+ monthly_data.reverse()
276
+
277
+ return jsonify({
278
+ 'totalUsers': total_users,
279
+ 'totalVisits': total_visits,
280
+ 'totalSearches': total_searches,
281
+ 'monthlyData': monthly_data
282
+ })
283
+
284
+ # Vault API endpoints
285
+ @app.route('/api/vault/authenticate', methods=['POST'])
286
+ @login_required
287
+ def authenticate_vault():
288
+ data = request.json
289
+ password = data.get('password')
290
+
291
+ if not password:
292
+ return jsonify({'error': 'Password required'}), 400
293
+
294
+ user_id = session['user_id']
295
+ user = users.find_one({'user_id': user_id})
296
+
297
+ if not user or not check_password_hash(user['password'], password):
298
+ return jsonify({'error': 'Invalid password'}), 401
299
+
300
+ return jsonify({'message': 'Authentication successful'})
301
+
302
+ @app.route('/api/vault/passwords', methods=['GET'])
303
+ @login_required
304
+ def get_vault_passwords():
305
+ user_id = session['user_id']
306
+ passwords = list(vault_passwords.find({'user_id': user_id}, {'_id': 0}))
307
+
308
+ # Decode passwords for display (simple base64 encoding for demo)
309
+ for password in passwords:
310
+ try:
311
+ password['password'] = base64.b64decode(password['password'].encode()).decode()
312
+ except:
313
+ pass # Keep original if decoding fails
314
+
315
+ return jsonify(passwords)
316
+
317
+ @app.route('/api/vault/passwords', methods=['POST'])
318
+ @login_required
319
+ def save_vault_password():
320
+ user_id = session['user_id']
321
+ data = request.json
322
+
323
+ # Validate required fields
324
+ if not data.get('title') or not data.get('password'):
325
+ return jsonify({'error': 'Title and password are required'}), 400
326
+
327
+ # Simple base64 encoding for password storage (demo purposes)
328
+ encoded_password = base64.b64encode(data['password'].encode()).decode()
329
+
330
+ password_entry = {
331
+ 'user_id': user_id,
332
+ 'title': data.get('title'),
333
+ 'username': data.get('username', ''),
334
+ 'password': encoded_password,
335
+ 'website': data.get('website', ''),
336
+ 'notes': data.get('notes', ''),
337
+ 'created_at': datetime.now(),
338
+ 'updated_at': datetime.now()
339
+ }
340
+
341
+ # Check if updating existing entry
342
+ if data.get('id'):
343
+ password_entry['id'] = data['id']
344
+ vault_passwords.update_one(
345
+ {'user_id': user_id, 'id': data['id']},
346
+ {'$set': password_entry}
347
+ )
348
+ else:
349
+ password_entry['id'] = str(uuid.uuid4())
350
+ vault_passwords.insert_one(password_entry)
351
+
352
+ return jsonify({'message': 'Password saved successfully'})
353
+
354
+ @app.route('/api/vault/passwords/<password_id>', methods=['DELETE'])
355
+ @login_required
356
+ def delete_vault_password(password_id):
357
+ user_id = session['user_id']
358
+
359
+ result = vault_passwords.delete_one({
360
+ 'user_id': user_id,
361
+ 'id': password_id
362
+ })
363
+
364
+ if result.deleted_count > 0:
365
+ return jsonify({'message': 'Password deleted successfully'})
366
+ else:
367
+ return jsonify({'error': 'Password not found'}), 404
368
+
369
+ if __name__ == '__main__':
370
+ app.run(debug=True, port=7860)
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ flask
2
+ pymongo
3
+ gunicorn
4
+ python-dotenv
static/icon.png ADDED

Git LFS Details

  • SHA256: a771f5aeb63cd83bcbd56624a78a08298b4d2b29ece0d303f2824ae98e5a7286
  • Pointer size: 131 Bytes
  • Size of remote file: 318 kB
static/script.js ADDED
@@ -0,0 +1,1348 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Clock functionality
2
+ function updateClock() {
3
+ const now = new Date();
4
+ const timeElement = document.getElementById('time');
5
+ const dateElement = document.getElementById('date');
6
+
7
+ // Format time
8
+ const hours = now.getHours().toString().padStart(2, '0');
9
+ const minutes = now.getMinutes().toString().padStart(2, '0');
10
+ const seconds = now.getSeconds().toString().padStart(2, '0');
11
+ timeElement.textContent = `${hours}:${minutes}:${seconds}`;
12
+
13
+ // Format date
14
+ const options = {
15
+ weekday: 'long',
16
+ year: 'numeric',
17
+ month: 'long',
18
+ day: 'numeric'
19
+ };
20
+ const formattedDate = now.toLocaleDateString('en-US', options);
21
+
22
+ // Add weather emoji
23
+ const month = now.getMonth();
24
+ const hour = now.getHours();
25
+ let weatherEmoji = '☀️';
26
+ if (month >= 11 || month <= 2) weatherEmoji = '❄️';
27
+ else if (month >= 3 && month <= 5) weatherEmoji = '🌸';
28
+ else if (month >= 6 && month <= 8) weatherEmoji = '☀️';
29
+ else weatherEmoji = '🍂';
30
+
31
+ if (hour >= 19 || hour <= 6) weatherEmoji = '🌙';
32
+
33
+ dateElement.textContent = `${formattedDate} ${weatherEmoji}`;
34
+ }
35
+
36
+ // Authentication functions
37
+ async function logout() {
38
+ try {
39
+ await fetch('/logout', {
40
+ method: 'POST',
41
+ headers: {
42
+ 'Content-Type': 'application/json',
43
+ }
44
+ });
45
+ window.location.href = '/';
46
+ } catch (error) {
47
+ console.error('Logout failed:', error);
48
+ window.location.href = '/';
49
+ }
50
+ }
51
+
52
+ // Search functionality
53
+ function handleSearch() {
54
+ const searchInput = document.getElementById('searchInput');
55
+ const query = searchInput.value.trim();
56
+
57
+ if (query) {
58
+ // Track search usage
59
+ fetch('/api/search', {
60
+ method: 'POST',
61
+ headers: {
62
+ 'Content-Type': 'application/json',
63
+ }
64
+ }).catch(err => console.log('Search tracking failed:', err));
65
+
66
+ // Check if it's a URL
67
+ const urlPattern = /^(https?:\/\/)?([\w\-])+\.{1}([a-zA-Z]{2,63})([\/\w\-\._~:?#[\]@!\$&'()*+,;=]*)?$/;
68
+
69
+ if (urlPattern.test(query)) {
70
+ // It's a URL, add https:// if not present
71
+ const url = query.startsWith('http') ? query : `https://${query}`;
72
+ window.open(url, '_blank');
73
+ } else {
74
+ // It's a search query
75
+ window.open(`https://www.google.com/search?q=${encodeURIComponent(query)}`, '_blank');
76
+ }
77
+
78
+ searchInput.value = '';
79
+ }
80
+ }
81
+
82
+ // Default bookmarks
83
+ const defaultBookmarks = [
84
+ { name: 'YouTube', url: 'https://youtube.com', icon: 'play_circle' },
85
+ { name: 'GitHub', url: 'https://github.com', icon: 'code' },
86
+ { name: 'Gmail', url: 'https://gmail.com', icon: 'mail' },
87
+ { name: 'Google Drive', url: 'https://drive.google.com', icon: 'cloud' },
88
+ { name: 'Netflix', url: 'https://netflix.com', icon: 'movie' },
89
+ { name: 'Reddit', url: 'https://reddit.com', icon: 'forum' },
90
+ { name: 'Twitter', url: 'https://twitter.com', icon: 'alternate_email' },
91
+ { name: 'LinkedIn', url: 'https://linkedin.com', icon: 'work' }
92
+ ];
93
+
94
+ // Bookmarks management
95
+ class BookmarksManager {
96
+ constructor() {
97
+ this.bookmarks = [];
98
+ this.loadBookmarks();
99
+ }
100
+
101
+ async loadBookmarks() {
102
+ try {
103
+ const response = await fetch('/api/bookmarks');
104
+ if (response.ok) {
105
+ this.bookmarks = await response.json();
106
+ } else {
107
+ // Fallback to default bookmarks if API fails
108
+ this.bookmarks = defaultBookmarks;
109
+ }
110
+ } catch (error) {
111
+ console.error('Failed to load bookmarks:', error);
112
+ this.bookmarks = defaultBookmarks;
113
+ }
114
+ this.renderBookmarks();
115
+ }
116
+
117
+ async saveBookmarks() {
118
+ try {
119
+ await fetch('/api/bookmarks', {
120
+ method: 'POST',
121
+ headers: {
122
+ 'Content-Type': 'application/json',
123
+ },
124
+ body: JSON.stringify({ bookmarks: this.bookmarks })
125
+ });
126
+ } catch (error) {
127
+ console.error('Failed to save bookmarks:', error);
128
+ }
129
+ }
130
+
131
+ addBookmark(bookmark) {
132
+ this.bookmarks.push(bookmark);
133
+ this.saveBookmarks();
134
+ this.renderBookmarks();
135
+ }
136
+
137
+ editBookmark(index, bookmark) {
138
+ this.bookmarks[index] = bookmark;
139
+ this.saveBookmarks();
140
+ this.renderBookmarks();
141
+ }
142
+
143
+ removeBookmark(index) {
144
+ this.bookmarks.splice(index, 1);
145
+ this.saveBookmarks();
146
+ this.renderBookmarks();
147
+ }
148
+
149
+ renderBookmarks() {
150
+ const container = document.getElementById('bookmarksGrid');
151
+ container.innerHTML = '';
152
+
153
+ // Render existing bookmarks
154
+ this.bookmarks.forEach((bookmark, index) => {
155
+ const bookmarkElement = document.createElement('a');
156
+ bookmarkElement.className = 'bookmark';
157
+ bookmarkElement.href = bookmark.url;
158
+ bookmarkElement.target = '_blank';
159
+ bookmarkElement.innerHTML = `
160
+ <span class="material-icons bookmark-icon">${bookmark.icon || 'bookmark'}</span>
161
+ <span class="bookmark-name">${bookmark.name}</span>
162
+ <span class="bookmark-url">${new URL(bookmark.url).hostname}</span>
163
+ `;
164
+
165
+ // Add context menu for editing
166
+ bookmarkElement.addEventListener('contextmenu', (e) => {
167
+ e.preventDefault();
168
+ this.openEditModal(index, bookmark);
169
+ });
170
+
171
+ container.appendChild(bookmarkElement);
172
+ });
173
+
174
+ // Add "Add Bookmark" button
175
+ const addButton = document.createElement('div');
176
+ addButton.className = 'add-bookmark-btn';
177
+ addButton.innerHTML = `
178
+ <span class="material-icons">add</span>
179
+ `;
180
+ addButton.addEventListener('click', () => {
181
+ this.openAddModal();
182
+ });
183
+
184
+ container.appendChild(addButton);
185
+ }
186
+
187
+ openEditModal(index, bookmark) {
188
+ const modal = document.getElementById('bookmarkModal');
189
+ const modalTitle = document.getElementById('modalTitle');
190
+ const submitBtn = document.getElementById('submitBtn');
191
+ const deleteBtn = document.getElementById('deleteBtn');
192
+ const nameInput = document.getElementById('bookmarkName');
193
+ const urlInput = document.getElementById('bookmarkUrl');
194
+ const iconSelect = document.getElementById('bookmarkIcon');
195
+
196
+ // Set modal to edit mode
197
+ modalTitle.textContent = 'Edit Bookmark';
198
+ submitBtn.textContent = 'Update';
199
+ deleteBtn.style.display = 'block';
200
+
201
+ // Fill form with current bookmark data
202
+ nameInput.value = bookmark.name;
203
+ urlInput.value = bookmark.url;
204
+ iconSelect.value = bookmark.icon || '';
205
+
206
+ // Store the index for editing
207
+ modal.dataset.editIndex = index;
208
+ modal.dataset.mode = 'edit';
209
+
210
+ // Set up delete button
211
+ deleteBtn.onclick = () => {
212
+ if (confirm(`Remove "${bookmark.name}" from bookmarks?`)) {
213
+ this.removeBookmark(index);
214
+ modal.style.display = 'none';
215
+ }
216
+ };
217
+
218
+ modal.style.display = 'block';
219
+ }
220
+
221
+ openAddModal() {
222
+ const modal = document.getElementById('bookmarkModal');
223
+ const modalTitle = document.getElementById('modalTitle');
224
+ const submitBtn = document.getElementById('submitBtn');
225
+ const deleteBtn = document.getElementById('deleteBtn');
226
+ const form = document.getElementById('bookmarkForm');
227
+
228
+ // Set modal to add mode
229
+ modalTitle.textContent = 'Add Bookmark';
230
+ submitBtn.textContent = 'Add Bookmark';
231
+ deleteBtn.style.display = 'none';
232
+
233
+ // Clear form
234
+ form.reset();
235
+
236
+ // Clear edit data
237
+ delete modal.dataset.editIndex;
238
+ modal.dataset.mode = 'add';
239
+
240
+ modal.style.display = 'block';
241
+ }
242
+ }
243
+
244
+ // Notepad functionality with multiple notes
245
+ class NotePad {
246
+ constructor() {
247
+ this.notes = [];
248
+ this.activeNoteId = 0;
249
+ this.saveTimeouts = new Map();
250
+ this.loadNotes();
251
+ }
252
+
253
+ async loadNotes() {
254
+ try {
255
+ const response = await fetch('/api/notes');
256
+ if (response.ok) {
257
+ this.notes = await response.json();
258
+ if (this.notes.length === 0) {
259
+ this.notes = [{ id: 0, title: 'Note 1', content: '' }];
260
+ }
261
+ } else {
262
+ this.notes = [{ id: 0, title: 'Note 1', content: '' }];
263
+ }
264
+ } catch (error) {
265
+ console.error('Failed to load notes:', error);
266
+ this.notes = [{ id: 0, title: 'Note 1', content: '' }];
267
+ }
268
+ this.renderNotes();
269
+ this.setupEventListeners();
270
+ }
271
+
272
+ async saveNotes() {
273
+ try {
274
+ await fetch('/api/notes', {
275
+ method: 'POST',
276
+ headers: {
277
+ 'Content-Type': 'application/json',
278
+ },
279
+ body: JSON.stringify({ notes: this.notes })
280
+ });
281
+ } catch (error) {
282
+ console.error('Failed to save notes:', error);
283
+ }
284
+ }
285
+
286
+ addNote() {
287
+ const newId = Math.max(...this.notes.map(n => n.id), -1) + 1;
288
+ const newNote = {
289
+ id: newId,
290
+ title: `Note ${newId + 1}`,
291
+ content: ''
292
+ };
293
+ this.notes.push(newNote);
294
+ this.saveNotes();
295
+ this.renderNotes();
296
+ this.switchToNote(newId);
297
+ }
298
+
299
+ removeNote(noteId) {
300
+ if (this.notes.length <= 1) {
301
+ alert('Cannot delete the last note!');
302
+ return;
303
+ }
304
+
305
+ if (confirm('Are you sure you want to delete this note?')) {
306
+ this.notes = this.notes.filter(note => note.id !== noteId);
307
+
308
+ // If we deleted the active note, switch to the first available note
309
+ if (this.activeNoteId === noteId) {
310
+ this.activeNoteId = this.notes[0].id;
311
+ }
312
+
313
+ this.saveNotes();
314
+ this.renderNotes();
315
+ }
316
+ }
317
+
318
+ switchToNote(noteId) {
319
+ // Save current note content first
320
+ this.saveCurrentNote();
321
+
322
+ this.activeNoteId = noteId;
323
+ this.renderNotes();
324
+
325
+ // Focus on the new note's textarea
326
+ const activeTextarea = document.querySelector('.note-content.active .notepad-textarea');
327
+ if (activeTextarea) {
328
+ activeTextarea.focus();
329
+ }
330
+ }
331
+
332
+ saveCurrentNote() {
333
+ const activeTextarea = document.querySelector('.note-content.active .notepad-textarea');
334
+ if (activeTextarea) {
335
+ const note = this.notes.find(n => n.id === this.activeNoteId);
336
+ if (note) {
337
+ note.content = activeTextarea.value;
338
+ this.saveNotes();
339
+ }
340
+ }
341
+ }
342
+
343
+ showSaveIndicator(noteId) {
344
+ const saveIndicator = document.querySelector(`[data-note-id="${noteId}"] .save-indicator`);
345
+ if (saveIndicator) {
346
+ saveIndicator.classList.add('visible');
347
+ setTimeout(() => {
348
+ saveIndicator.classList.remove('visible');
349
+ }, 1500);
350
+ }
351
+ }
352
+
353
+ renderNotes() {
354
+ const tabsContainer = document.querySelector('.notes-tabs');
355
+ const notesContainer = document.querySelector('.notepad-container');
356
+
357
+ // Clear existing tabs (except add button)
358
+ const addButton = tabsContainer.querySelector('.add-note-btn');
359
+ tabsContainer.innerHTML = '';
360
+
361
+ // Render note tabs
362
+ this.notes.forEach(note => {
363
+ const tab = document.createElement('button');
364
+ tab.className = `note-tab ${note.id === this.activeNoteId ? 'active' : ''}`;
365
+ tab.setAttribute('data-note-id', note.id);
366
+ tab.innerHTML = `
367
+ ${note.title}
368
+ ${this.notes.length > 1 ? `<span class="close-note" onclick="event.stopPropagation(); window.notePad.removeNote(${note.id});">×</span>` : ''}
369
+ `;
370
+ tab.addEventListener('click', () => this.switchToNote(note.id));
371
+ tabsContainer.appendChild(tab);
372
+ });
373
+
374
+ // Re-add the add button
375
+ tabsContainer.appendChild(addButton);
376
+
377
+ // Clear and render note contents
378
+ const existingContents = notesContainer.querySelectorAll('.note-content');
379
+ existingContents.forEach(content => content.remove());
380
+
381
+ this.notes.forEach(note => {
382
+ const noteContent = document.createElement('div');
383
+ noteContent.className = `note-content ${note.id === this.activeNoteId ? 'active' : ''}`;
384
+ noteContent.setAttribute('data-note-id', note.id);
385
+ noteContent.innerHTML = `
386
+ <textarea class="notepad-textarea" placeholder="Start typing your notes here...">${note.content}</textarea>
387
+ <div class="notepad-footer">
388
+ <span class="save-indicator">
389
+ <span class="material-icons" style="font-size: 1rem; vertical-align: middle;">check_circle</span>
390
+ Saved
391
+ </span>
392
+ </div>
393
+ `;
394
+ notesContainer.appendChild(noteContent);
395
+ });
396
+
397
+ this.setupNoteEventListeners();
398
+ }
399
+
400
+ setupEventListeners() {
401
+ // Global event listeners can be set up here
402
+ }
403
+
404
+ setupNoteEventListeners() {
405
+ this.notes.forEach(note => {
406
+ const textarea = document.querySelector(`[data-note-id="${note.id}"] .notepad-textarea`);
407
+
408
+ if (textarea) {
409
+ // Remove existing listeners to avoid duplicates
410
+ textarea.removeEventListener('input', this.handleInput);
411
+ textarea.removeEventListener('blur', this.handleBlur);
412
+
413
+ // Add new listeners
414
+ textarea.addEventListener('input', () => {
415
+ // Update note content
416
+ const noteData = this.notes.find(n => n.id === note.id);
417
+ if (noteData) {
418
+ noteData.content = textarea.value;
419
+ }
420
+
421
+ // Auto-save with debounce
422
+ if (this.saveTimeouts.has(note.id)) {
423
+ clearTimeout(this.saveTimeouts.get(note.id));
424
+ }
425
+
426
+ const timeout = setTimeout(() => {
427
+ this.saveNotes();
428
+ this.showSaveIndicator(note.id);
429
+ }, 1000);
430
+
431
+ this.saveTimeouts.set(note.id, timeout);
432
+ });
433
+
434
+ textarea.addEventListener('blur', () => {
435
+ if (this.saveTimeouts.has(note.id)) {
436
+ clearTimeout(this.saveTimeouts.get(note.id));
437
+ }
438
+ this.saveCurrentNote();
439
+ this.showSaveIndicator(note.id);
440
+ });
441
+ }
442
+ });
443
+ }
444
+ }
445
+
446
+ // Modal functionality
447
+ class Modal {
448
+ constructor() {
449
+ this.modal = document.getElementById('bookmarkModal');
450
+ this.form = document.getElementById('bookmarkForm');
451
+ this.closeBtn = document.getElementById('closeModal');
452
+ this.cancelBtn = document.getElementById('cancelBtn');
453
+
454
+ this.setupEventListeners();
455
+ }
456
+
457
+ setupEventListeners() {
458
+ // Close modal events
459
+ this.closeBtn.addEventListener('click', () => this.close());
460
+ this.cancelBtn.addEventListener('click', () => this.close());
461
+
462
+ // Close on outside click
463
+ window.addEventListener('click', (e) => {
464
+ if (e.target === this.modal) {
465
+ this.close();
466
+ }
467
+ });
468
+
469
+ // Form submission
470
+ this.form.addEventListener('submit', (e) => {
471
+ e.preventDefault();
472
+ this.handleSubmit();
473
+ });
474
+
475
+ // Close on Escape key
476
+ document.addEventListener('keydown', (e) => {
477
+ if (e.key === 'Escape' && this.modal.style.display === 'block') {
478
+ this.close();
479
+ }
480
+ });
481
+ }
482
+
483
+ open() {
484
+ this.modal.style.display = 'block';
485
+ document.getElementById('bookmarkName').focus();
486
+ }
487
+
488
+ close() {
489
+ this.modal.style.display = 'none';
490
+ this.form.reset();
491
+ // Clear edit data
492
+ delete this.modal.dataset.editIndex;
493
+ delete this.modal.dataset.mode;
494
+ // Reset modal to add mode
495
+ document.getElementById('modalTitle').textContent = 'Add Bookmark';
496
+ document.getElementById('submitBtn').textContent = 'Add Bookmark';
497
+ document.getElementById('deleteBtn').style.display = 'none';
498
+ }
499
+
500
+ handleSubmit() {
501
+ const name = document.getElementById('bookmarkName').value.trim();
502
+ const url = document.getElementById('bookmarkUrl').value.trim();
503
+ const icon = document.getElementById('bookmarkIcon').value.trim() || 'bookmark';
504
+
505
+ if (name && url) {
506
+ // Ensure URL has protocol
507
+ const finalUrl = url.startsWith('http') ? url : `https://${url}`;
508
+
509
+ const bookmark = {
510
+ name,
511
+ url: finalUrl,
512
+ icon
513
+ };
514
+
515
+ // Check if we're in edit mode
516
+ if (this.modal.dataset.mode === 'edit') {
517
+ const editIndex = parseInt(this.modal.dataset.editIndex);
518
+ bookmarksManager.editBookmark(editIndex, bookmark);
519
+ } else {
520
+ bookmarksManager.addBookmark(bookmark);
521
+ }
522
+
523
+ this.close();
524
+ }
525
+ }
526
+ }
527
+
528
+ // Theme and settings removed - using white background only
529
+
530
+ // Initialize everything when DOM is loaded
531
+ document.addEventListener('DOMContentLoaded', () => {
532
+ // Initialize clock
533
+ updateClock();
534
+ setInterval(updateClock, 1000);
535
+
536
+ // Initialize components
537
+ window.bookmarksManager = new BookmarksManager();
538
+ window.notePad = new NotePad();
539
+ window.modal = new Modal();
540
+
541
+ // Setup search functionality
542
+ const searchInput = document.getElementById('searchInput');
543
+ const searchIcon = document.getElementById('searchIcon');
544
+
545
+ searchInput.addEventListener('keypress', (e) => {
546
+ if (e.key === 'Enter') {
547
+ handleSearch();
548
+ }
549
+ });
550
+
551
+ searchIcon.addEventListener('click', handleSearch);
552
+
553
+ // Focus search on load
554
+ searchInput.focus();
555
+
556
+ // Display username (passed from Flask template)
557
+ const usernameElement = document.getElementById('username');
558
+ if (usernameElement && typeof window.currentUser !== 'undefined') {
559
+ usernameElement.textContent = window.currentUser;
560
+ }
561
+ });
562
+
563
+ // Service Worker for offline capability (future enhancement)
564
+ if ('serviceWorker' in navigator) {
565
+ window.addEventListener('load', () => {
566
+ // Service worker registration could be added here
567
+ console.log('Start page loaded successfully!');
568
+ });
569
+ }
570
+
571
+ // Export for potential future use
572
+ window.StartPageAPI = {
573
+ bookmarksManager: () => window.bookmarksManager,
574
+ notePad: () => window.notePad,
575
+ addBookmark: (bookmark) => window.bookmarksManager.addBookmark(bookmark),
576
+ exportData: () => ({
577
+ bookmarks: window.bookmarksManager.bookmarks,
578
+ notes: window.notePad.notes
579
+ }),
580
+ importData: (data) => {
581
+ if (data.bookmarks) {
582
+ localStorage.setItem('startpage-bookmarks', JSON.stringify(data.bookmarks));
583
+ }
584
+ if (data.notes) {
585
+ localStorage.setItem('startpage-notes', JSON.stringify(data.notes));
586
+ }
587
+ location.reload();
588
+ }
589
+ };
590
+
591
+ // Rain Effect
592
+ class RainEffect {
593
+ constructor() {
594
+ this.rainContainer = document.getElementById('rainContainer');
595
+ this.rainDrops = [];
596
+ this.maxDrops = 150;
597
+ this.init();
598
+ }
599
+
600
+ init() {
601
+ this.createRain();
602
+ this.animateRain();
603
+ }
604
+
605
+ createRain() {
606
+ for (let i = 0; i < this.maxDrops; i++) {
607
+ this.createRainDrop();
608
+ }
609
+ }
610
+
611
+ createRainDrop() {
612
+ const drop = document.createElement('div');
613
+ drop.className = 'rain-drop';
614
+
615
+ // Random horizontal position
616
+ const x = Math.random() * window.innerWidth;
617
+
618
+ // Random size variation
619
+ const size = Math.random() * 0.8 + 0.2;
620
+ drop.style.width = `${2 * size}px`;
621
+ drop.style.height = `${20 * size}px`;
622
+
623
+ // Random speed (duration)
624
+ const duration = Math.random() * 2 + 1; // 1-3 seconds
625
+ drop.style.animationDuration = `${duration}s`;
626
+
627
+ // Random delay
628
+ const delay = Math.random() * 2;
629
+ drop.style.animationDelay = `${delay}s`;
630
+
631
+ // Position the drop
632
+ drop.style.left = `${x}px`;
633
+ drop.style.top = '-20px';
634
+
635
+ this.rainContainer.appendChild(drop);
636
+ this.rainDrops.push(drop);
637
+ }
638
+
639
+ animateRain() {
640
+ // Clean up and recreate drops periodically
641
+ setInterval(() => {
642
+ this.rainDrops.forEach(drop => {
643
+ const rect = drop.getBoundingClientRect();
644
+ if (rect.top > window.innerHeight) {
645
+ // Reset the drop to the top with new random position
646
+ drop.style.left = `${Math.random() * window.innerWidth}px`;
647
+ drop.style.top = '-20px';
648
+
649
+ // Randomize properties again
650
+ const size = Math.random() * 0.8 + 0.2;
651
+ drop.style.width = `${2 * size}px`;
652
+ drop.style.height = `${20 * size}px`;
653
+
654
+ const duration = Math.random() * 2 + 1;
655
+ drop.style.animationDuration = `${duration}s`;
656
+ }
657
+ });
658
+ }, 100);
659
+ }
660
+ }
661
+
662
+ // Initialize rain effect when the page loads
663
+ document.addEventListener('DOMContentLoaded', () => {
664
+ new RainEffect();
665
+ });
666
+
667
+ // Account dropdown functionality
668
+ function toggleAccountDropdown() {
669
+ const dropdown = document.getElementById('accountDropdown');
670
+ dropdown.classList.toggle('active');
671
+ }
672
+
673
+ // Close dropdown when clicking outside
674
+ document.addEventListener('click', (e) => {
675
+ const dropdown = document.getElementById('accountDropdown');
676
+ const accountBtn = document.querySelector('.account-btn');
677
+
678
+ if (!accountBtn.contains(e.target) && !dropdown.contains(e.target)) {
679
+ dropdown.classList.remove('active');
680
+ }
681
+ });
682
+
683
+ // Change Password Modal Functions
684
+ function openChangePasswordModal() {
685
+ const modal = document.getElementById('passwordModal');
686
+ modal.style.display = 'block';
687
+ document.getElementById('accountDropdown').classList.remove('active');
688
+ }
689
+
690
+ function closePasswordModal() {
691
+ const modal = document.getElementById('passwordModal');
692
+ modal.style.display = 'none';
693
+ document.getElementById('passwordForm').reset();
694
+ }
695
+
696
+ // Handle password form submission
697
+ document.addEventListener('DOMContentLoaded', () => {
698
+ const passwordForm = document.getElementById('passwordForm');
699
+ if (passwordForm) {
700
+ passwordForm.addEventListener('submit', async (e) => {
701
+ e.preventDefault();
702
+
703
+ const currentPassword = document.getElementById('currentPassword').value;
704
+ const newPassword = document.getElementById('newPassword').value;
705
+ const confirmPassword = document.getElementById('confirmPassword').value;
706
+
707
+ if (newPassword !== confirmPassword) {
708
+ alert('New passwords do not match!');
709
+ return;
710
+ }
711
+
712
+ if (newPassword.length < 6) {
713
+ alert('Password must be at least 6 characters long!');
714
+ return;
715
+ }
716
+
717
+ try {
718
+ const response = await fetch('/api/change-password', {
719
+ method: 'POST',
720
+ headers: {
721
+ 'Content-Type': 'application/json',
722
+ },
723
+ body: JSON.stringify({
724
+ currentPassword,
725
+ newPassword
726
+ })
727
+ });
728
+
729
+ const data = await response.json();
730
+
731
+ if (response.ok) {
732
+ alert('Password changed successfully!');
733
+ closePasswordModal();
734
+ } else {
735
+ alert(data.error || 'Failed to change password');
736
+ }
737
+ } catch (error) {
738
+ console.error('Error changing password:', error);
739
+ alert('Failed to change password. Please try again.');
740
+ }
741
+ });
742
+ }
743
+ });
744
+
745
+ // Developer Stats Modal Functions
746
+ function openDeveloperPage() {
747
+ const modal = document.getElementById('developerModal');
748
+ modal.style.display = 'block';
749
+ document.getElementById('accountDropdown').classList.remove('active');
750
+ loadDeveloperStats();
751
+ }
752
+
753
+ function closeDeveloperModal() {
754
+ const modal = document.getElementById('developerModal');
755
+ modal.style.display = 'none';
756
+
757
+ // Destroy chart to prevent memory leaks
758
+ if (activityChart) {
759
+ activityChart.destroy();
760
+ activityChart = null;
761
+ }
762
+ }
763
+
764
+ async function loadDeveloperStats() {
765
+ try {
766
+ const response = await fetch('/api/developer-stats');
767
+ if (response.ok) {
768
+ const data = await response.json();
769
+
770
+ // Update stats display
771
+ document.getElementById('totalUsers').textContent = data.totalUsers || 0;
772
+ document.getElementById('totalVisits').textContent = data.totalVisits || 0;
773
+ document.getElementById('totalSearches').textContent = data.totalSearches || 0;
774
+
775
+ // Create chart
776
+ createActivityChart(data.monthlyData || []);
777
+ } else {
778
+ console.error('Failed to load developer stats');
779
+ }
780
+ } catch (error) {
781
+ console.error('Error loading developer stats:', error);
782
+ }
783
+ }
784
+
785
+ let activityChart = null; // Global variable to store chart instance
786
+
787
+ function createActivityChart(monthlyData) {
788
+ const canvas = document.getElementById('activityChart');
789
+ const ctx = canvas.getContext('2d');
790
+
791
+ // Destroy existing chart if it exists
792
+ if (activityChart) {
793
+ activityChart.destroy();
794
+ }
795
+
796
+ // Generate sample monthly data if none provided
797
+ if (monthlyData.length === 0) {
798
+ const currentDate = new Date();
799
+ monthlyData = [];
800
+
801
+ for (let i = 11; i >= 0; i--) {
802
+ const date = new Date(currentDate.getFullYear(), currentDate.getMonth() - i, 1);
803
+ const monthName = date.toLocaleString('default', { month: 'short' });
804
+
805
+ monthlyData.push({
806
+ month: monthName,
807
+ visits: Math.floor(Math.random() * 500) + 200,
808
+ searches: Math.floor(Math.random() * 300) + 100
809
+ });
810
+ }
811
+ }
812
+
813
+ // Extract data for Chart.js
814
+ const labels = monthlyData.map(data => data.month);
815
+ const visitsData = monthlyData.map(data => data.visits);
816
+ const searchesData = monthlyData.map(data => data.searches);
817
+
818
+ // Create Chart.js line chart
819
+ activityChart = new Chart(ctx, {
820
+ type: 'line',
821
+ data: {
822
+ labels: labels,
823
+ datasets: [
824
+ {
825
+ label: 'Page Visits',
826
+ data: visitsData,
827
+ borderColor: '#4285f4',
828
+ backgroundColor: 'rgba(66, 133, 244, 0.1)',
829
+ borderWidth: 3,
830
+ fill: true,
831
+ tension: 0.4,
832
+ pointBackgroundColor: '#4285f4',
833
+ pointBorderColor: '#4285f4',
834
+ pointHoverBackgroundColor: '#ffffff',
835
+ pointHoverBorderColor: '#4285f4',
836
+ pointRadius: 5,
837
+ pointHoverRadius: 7
838
+ },
839
+ {
840
+ label: 'Searches',
841
+ data: searchesData,
842
+ borderColor: '#34a853',
843
+ backgroundColor: 'rgba(52, 168, 83, 0.1)',
844
+ borderWidth: 3,
845
+ fill: true,
846
+ tension: 0.4,
847
+ pointBackgroundColor: '#34a853',
848
+ pointBorderColor: '#34a853',
849
+ pointHoverBackgroundColor: '#ffffff',
850
+ pointHoverBorderColor: '#34a853',
851
+ pointRadius: 5,
852
+ pointHoverRadius: 7
853
+ }
854
+ ]
855
+ },
856
+ options: {
857
+ responsive: true,
858
+ maintainAspectRatio: false,
859
+ plugins: {
860
+ legend: {
861
+ display: true,
862
+ position: 'top',
863
+ labels: {
864
+ usePointStyle: true,
865
+ padding: 20,
866
+ font: {
867
+ size: 12,
868
+ family: 'Lexend, sans-serif'
869
+ }
870
+ }
871
+ },
872
+ tooltip: {
873
+ mode: 'index',
874
+ intersect: false,
875
+ backgroundColor: 'rgba(0, 0, 0, 0.8)',
876
+ titleColor: '#ffffff',
877
+ bodyColor: '#ffffff',
878
+ borderColor: '#4285f4',
879
+ borderWidth: 1,
880
+ cornerRadius: 8,
881
+ displayColors: true,
882
+ titleFont: {
883
+ size: 14,
884
+ weight: 'bold'
885
+ },
886
+ bodyFont: {
887
+ size: 13
888
+ }
889
+ }
890
+ },
891
+ interaction: {
892
+ mode: 'nearest',
893
+ axis: 'x',
894
+ intersect: false
895
+ },
896
+ scales: {
897
+ x: {
898
+ display: true,
899
+ title: {
900
+ display: true,
901
+ text: 'Month',
902
+ font: {
903
+ size: 14,
904
+ weight: 'bold',
905
+ family: 'Lexend, sans-serif'
906
+ },
907
+ color: '#666'
908
+ },
909
+ grid: {
910
+ display: true,
911
+ color: 'rgba(0, 0, 0, 0.1)'
912
+ },
913
+ ticks: {
914
+ font: {
915
+ size: 12,
916
+ family: 'Lexend, sans-serif'
917
+ },
918
+ color: '#666'
919
+ }
920
+ },
921
+ y: {
922
+ display: true,
923
+ title: {
924
+ display: true,
925
+ text: 'Count',
926
+ font: {
927
+ size: 14,
928
+ weight: 'bold',
929
+ family: 'Lexend, sans-serif'
930
+ },
931
+ color: '#666'
932
+ },
933
+ grid: {
934
+ display: true,
935
+ color: 'rgba(0, 0, 0, 0.1)'
936
+ },
937
+ ticks: {
938
+ beginAtZero: true,
939
+ font: {
940
+ size: 12,
941
+ family: 'Lexend, sans-serif'
942
+ },
943
+ color: '#666'
944
+ }
945
+ }
946
+ },
947
+ elements: {
948
+ line: {
949
+ borderJoinStyle: 'round'
950
+ },
951
+ point: {
952
+ borderWidth: 2,
953
+ hoverBorderWidth: 3
954
+ }
955
+ },
956
+ animation: {
957
+ duration: 1500,
958
+ easing: 'easeInOutQuart'
959
+ }
960
+ }
961
+ });
962
+ }
963
+
964
+ // Close modals on Escape key
965
+ document.addEventListener('keydown', (e) => {
966
+ if (e.key === 'Escape') {
967
+ const passwordModal = document.getElementById('passwordModal');
968
+ const developerModal = document.getElementById('developerModal');
969
+ const vaultAuthModal = document.getElementById('vaultAuthModal');
970
+ const vaultModal = document.getElementById('vaultModal');
971
+ const passwordEntryModal = document.getElementById('passwordEntryModal');
972
+
973
+ if (passwordModal.style.display === 'block') {
974
+ closePasswordModal();
975
+ }
976
+ if (developerModal.style.display === 'block') {
977
+ closeDeveloperModal();
978
+ }
979
+ if (vaultAuthModal.style.display === 'block') {
980
+ closeVaultAuthModal();
981
+ }
982
+ if (vaultModal.style.display === 'block') {
983
+ closeVaultModal();
984
+ }
985
+ if (passwordEntryModal.style.display === 'block') {
986
+ closePasswordEntryModal();
987
+ }
988
+ }
989
+ });
990
+
991
+ // Close modals when clicking outside
992
+ window.addEventListener('click', (e) => {
993
+ const passwordModal = document.getElementById('passwordModal');
994
+ const developerModal = document.getElementById('developerModal');
995
+ const vaultAuthModal = document.getElementById('vaultAuthModal');
996
+ const vaultModal = document.getElementById('vaultModal');
997
+ const passwordEntryModal = document.getElementById('passwordEntryModal');
998
+
999
+ if (e.target === passwordModal) {
1000
+ closePasswordModal();
1001
+ }
1002
+ if (e.target === developerModal) {
1003
+ closeDeveloperModal();
1004
+ }
1005
+ if (e.target === vaultAuthModal) {
1006
+ closeVaultAuthModal();
1007
+ }
1008
+ if (e.target === vaultModal) {
1009
+ closeVaultModal();
1010
+ }
1011
+ if (e.target === passwordEntryModal) {
1012
+ closePasswordEntryModal();
1013
+ }
1014
+ });
1015
+
1016
+ // Vault functionality
1017
+ let vaultPasswords = [];
1018
+ let currentEditingPassword = null;
1019
+
1020
+ function openVaultPage() {
1021
+ document.getElementById('accountDropdown').classList.remove('active');
1022
+ document.getElementById('vaultAuthModal').style.display = 'block';
1023
+ }
1024
+
1025
+ function closeVaultAuthModal() {
1026
+ document.getElementById('vaultAuthModal').style.display = 'none';
1027
+ document.getElementById('vaultAuthForm').reset();
1028
+ }
1029
+
1030
+ function openVaultModal() {
1031
+ document.getElementById('vaultModal').style.display = 'block';
1032
+ loadVaultPasswords();
1033
+ }
1034
+
1035
+ function closeVaultModal() {
1036
+ document.getElementById('vaultModal').style.display = 'none';
1037
+ vaultPasswords = [];
1038
+ renderVaultPasswords();
1039
+ }
1040
+
1041
+ function openAddPasswordModal() {
1042
+ currentEditingPassword = null;
1043
+ document.getElementById('passwordEntryTitle').textContent = 'Add Password';
1044
+ document.getElementById('savePasswordBtn').textContent = 'Save Password';
1045
+ document.getElementById('deletePasswordBtn').style.display = 'none';
1046
+ document.getElementById('passwordEntryForm').reset();
1047
+ document.getElementById('passwordEntryModal').style.display = 'block';
1048
+ }
1049
+
1050
+ function openEditPasswordModal(index) {
1051
+ currentEditingPassword = index;
1052
+ const password = vaultPasswords[index];
1053
+
1054
+ document.getElementById('passwordEntryTitle').textContent = 'Edit Password';
1055
+ document.getElementById('savePasswordBtn').textContent = 'Update Password';
1056
+ document.getElementById('deletePasswordBtn').style.display = 'block';
1057
+
1058
+ document.getElementById('entryTitle').value = password.title;
1059
+ document.getElementById('entryUsername').value = password.username || '';
1060
+ document.getElementById('entryPassword').value = password.password;
1061
+ document.getElementById('entryWebsite').value = password.website || '';
1062
+ document.getElementById('entryNotes').value = password.notes || '';
1063
+
1064
+ document.getElementById('passwordEntryModal').style.display = 'block';
1065
+ }
1066
+
1067
+ function closePasswordEntryModal() {
1068
+ document.getElementById('passwordEntryModal').style.display = 'none';
1069
+ document.getElementById('passwordEntryForm').reset();
1070
+ currentEditingPassword = null;
1071
+ }
1072
+
1073
+ async function loadVaultPasswords() {
1074
+ try {
1075
+ const response = await fetch('/api/vault/passwords');
1076
+ if (response.ok) {
1077
+ vaultPasswords = await response.json();
1078
+ } else {
1079
+ vaultPasswords = [];
1080
+ }
1081
+ renderVaultPasswords();
1082
+ } catch (error) {
1083
+ console.error('Failed to load vault passwords:', error);
1084
+ vaultPasswords = [];
1085
+ renderVaultPasswords();
1086
+ }
1087
+ }
1088
+
1089
+ async function saveVaultPassword(passwordData) {
1090
+ try {
1091
+ const response = await fetch('/api/vault/passwords', {
1092
+ method: 'POST',
1093
+ headers: {
1094
+ 'Content-Type': 'application/json',
1095
+ },
1096
+ body: JSON.stringify(passwordData)
1097
+ });
1098
+
1099
+ if (response.ok) {
1100
+ loadVaultPasswords();
1101
+ return true;
1102
+ } else {
1103
+ console.error('Failed to save password');
1104
+ return false;
1105
+ }
1106
+ } catch (error) {
1107
+ console.error('Failed to save password:', error);
1108
+ return false;
1109
+ }
1110
+ }
1111
+
1112
+ async function deleteVaultPassword(index) {
1113
+ try {
1114
+ const password = vaultPasswords[index];
1115
+ const response = await fetch(`/api/vault/passwords/${password.id}`, {
1116
+ method: 'DELETE'
1117
+ });
1118
+
1119
+ if (response.ok) {
1120
+ loadVaultPasswords();
1121
+ return true;
1122
+ } else {
1123
+ console.error('Failed to delete password');
1124
+ return false;
1125
+ }
1126
+ } catch (error) {
1127
+ console.error('Failed to delete password:', error);
1128
+ return false;
1129
+ }
1130
+ }
1131
+
1132
+ function renderVaultPasswords() {
1133
+ const vaultList = document.getElementById('vaultList');
1134
+ const searchTerm = document.getElementById('vaultSearchInput').value.toLowerCase();
1135
+
1136
+ // Filter passwords based on search term
1137
+ const filteredPasswords = vaultPasswords.filter(password =>
1138
+ password.title.toLowerCase().includes(searchTerm) ||
1139
+ (password.username && password.username.toLowerCase().includes(searchTerm)) ||
1140
+ (password.website && password.website.toLowerCase().includes(searchTerm)) ||
1141
+ (password.notes && password.notes.toLowerCase().includes(searchTerm))
1142
+ );
1143
+
1144
+ if (filteredPasswords.length === 0) {
1145
+ vaultList.innerHTML = `
1146
+ <div class="vault-empty">
1147
+ <div class="material-icons">lock</div>
1148
+ <p>${searchTerm ? 'No passwords found matching your search.' : 'Your vault is empty. Add your first password!'}</p>
1149
+ </div>
1150
+ `;
1151
+ return;
1152
+ }
1153
+
1154
+ vaultList.innerHTML = filteredPasswords.map((password, originalIndex) => {
1155
+ const index = vaultPasswords.indexOf(password);
1156
+ return `
1157
+ <div class="vault-entry" onclick="openEditPasswordModal(${index})">
1158
+ <div class="vault-entry-header">
1159
+ <div class="vault-entry-title">${password.title}</div>
1160
+ <div class="vault-entry-actions" onclick="event.stopPropagation();">
1161
+ <button class="vault-action-btn" onclick="copyToClipboard('${password.password}', 'Password')" title="Copy Password">
1162
+ <span class="material-icons">content_copy</span>
1163
+ </button>
1164
+ <button class="vault-action-btn" onclick="openEditPasswordModal(${index})" title="Edit">
1165
+ <span class="material-icons">edit</span>
1166
+ </button>
1167
+ </div>
1168
+ </div>
1169
+ <div class="vault-entry-info">
1170
+ ${password.username ? `
1171
+ <span class="vault-entry-label">Username:</span>
1172
+ <span class="vault-entry-value">${password.username}</span>
1173
+ ` : ''}
1174
+ ${password.website ? `
1175
+ <span class="vault-entry-label">Website:</span>
1176
+ <span class="vault-entry-value">${password.website}</span>
1177
+ ` : ''}
1178
+ ${password.notes ? `
1179
+ <span class="vault-entry-label">Notes:</span>
1180
+ <span class="vault-entry-value">${password.notes}</span>
1181
+ ` : ''}
1182
+ </div>
1183
+ </div>
1184
+ `;
1185
+ }).join('');
1186
+ }
1187
+
1188
+ function copyToClipboard(text, type) {
1189
+ navigator.clipboard.writeText(text).then(() => {
1190
+ // Show temporary success message
1191
+ const message = document.createElement('div');
1192
+ message.style.cssText = `
1193
+ position: fixed;
1194
+ top: 20px;
1195
+ right: 20px;
1196
+ background: #4caf50;
1197
+ color: white;
1198
+ padding: 12px 20px;
1199
+ border-radius: 8px;
1200
+ z-index: 10000;
1201
+ font-size: 14px;
1202
+ `;
1203
+ message.textContent = `${type} copied to clipboard!`;
1204
+ document.body.appendChild(message);
1205
+
1206
+ setTimeout(() => {
1207
+ document.body.removeChild(message);
1208
+ }, 2000);
1209
+ }).catch(() => {
1210
+ alert('Failed to copy to clipboard');
1211
+ });
1212
+ }
1213
+
1214
+ function togglePasswordVisibility(inputId) {
1215
+ const input = document.getElementById(inputId);
1216
+ const toggleBtn = input.parentNode.querySelector('.password-toggle-btn span');
1217
+
1218
+ if (input.type === 'password') {
1219
+ input.type = 'text';
1220
+ toggleBtn.textContent = 'visibility_off';
1221
+ } else {
1222
+ input.type = 'password';
1223
+ toggleBtn.textContent = 'visibility';
1224
+ }
1225
+ }
1226
+
1227
+ function generatePassword() {
1228
+ const length = 16;
1229
+ const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';
1230
+ let password = '';
1231
+
1232
+ for (let i = 0; i < length; i++) {
1233
+ password += charset.charAt(Math.floor(Math.random() * charset.length));
1234
+ }
1235
+
1236
+ document.getElementById('entryPassword').value = password;
1237
+ updatePasswordStrength(password);
1238
+ }
1239
+
1240
+ function updatePasswordStrength(password) {
1241
+ const strengthIndicator = document.querySelector('.password-strength-bar');
1242
+ if (!strengthIndicator) return;
1243
+
1244
+ let strength = 0;
1245
+
1246
+ if (password.length >= 8) strength++;
1247
+ if (/[a-z]/.test(password)) strength++;
1248
+ if (/[A-Z]/.test(password)) strength++;
1249
+ if (/[0-9]/.test(password)) strength++;
1250
+ if (/[^A-Za-z0-9]/.test(password)) strength++;
1251
+
1252
+ const classes = ['strength-weak', 'strength-fair', 'strength-good', 'strength-strong'];
1253
+ strengthIndicator.className = 'password-strength-bar ' + (classes[Math.min(strength - 1, 3)] || '');
1254
+ }
1255
+
1256
+ // Initialize vault event listeners
1257
+ document.addEventListener('DOMContentLoaded', () => {
1258
+ // Vault authentication form
1259
+ const vaultAuthForm = document.getElementById('vaultAuthForm');
1260
+ if (vaultAuthForm) {
1261
+ vaultAuthForm.addEventListener('submit', async (e) => {
1262
+ e.preventDefault();
1263
+
1264
+ const password = document.getElementById('vaultPassword').value;
1265
+
1266
+ try {
1267
+ const response = await fetch('/api/vault/authenticate', {
1268
+ method: 'POST',
1269
+ headers: {
1270
+ 'Content-Type': 'application/json',
1271
+ },
1272
+ body: JSON.stringify({ password })
1273
+ });
1274
+
1275
+ if (response.ok) {
1276
+ closeVaultAuthModal();
1277
+ openVaultModal();
1278
+ } else {
1279
+ const data = await response.json();
1280
+ alert(data.error || 'Invalid password');
1281
+ }
1282
+ } catch (error) {
1283
+ console.error('Vault authentication failed:', error);
1284
+ alert('Authentication failed. Please try again.');
1285
+ }
1286
+ });
1287
+ }
1288
+
1289
+ // Password entry form
1290
+ const passwordEntryForm = document.getElementById('passwordEntryForm');
1291
+ if (passwordEntryForm) {
1292
+ passwordEntryForm.addEventListener('submit', async (e) => {
1293
+ e.preventDefault();
1294
+
1295
+ const passwordData = {
1296
+ title: document.getElementById('entryTitle').value,
1297
+ username: document.getElementById('entryUsername').value,
1298
+ password: document.getElementById('entryPassword').value,
1299
+ website: document.getElementById('entryWebsite').value,
1300
+ notes: document.getElementById('entryNotes').value
1301
+ };
1302
+
1303
+ if (currentEditingPassword !== null) {
1304
+ passwordData.id = vaultPasswords[currentEditingPassword].id;
1305
+ }
1306
+
1307
+ const success = await saveVaultPassword(passwordData);
1308
+ if (success) {
1309
+ closePasswordEntryModal();
1310
+ } else {
1311
+ alert('Failed to save password. Please try again.');
1312
+ }
1313
+ });
1314
+ }
1315
+
1316
+ // Delete password button
1317
+ const deletePasswordBtn = document.getElementById('deletePasswordBtn');
1318
+ if (deletePasswordBtn) {
1319
+ deletePasswordBtn.addEventListener('click', async () => {
1320
+ if (currentEditingPassword !== null) {
1321
+ if (confirm('Are you sure you want to delete this password?')) {
1322
+ const success = await deleteVaultPassword(currentEditingPassword);
1323
+ if (success) {
1324
+ closePasswordEntryModal();
1325
+ } else {
1326
+ alert('Failed to delete password. Please try again.');
1327
+ }
1328
+ }
1329
+ }
1330
+ });
1331
+ }
1332
+
1333
+ // Vault search functionality
1334
+ const vaultSearchInput = document.getElementById('vaultSearchInput');
1335
+ if (vaultSearchInput) {
1336
+ vaultSearchInput.addEventListener('input', () => {
1337
+ renderVaultPasswords();
1338
+ });
1339
+ }
1340
+
1341
+ // Password strength indicator
1342
+ const entryPasswordInput = document.getElementById('entryPassword');
1343
+ if (entryPasswordInput) {
1344
+ entryPasswordInput.addEventListener('input', (e) => {
1345
+ updatePasswordStrength(e.target.value);
1346
+ });
1347
+ }
1348
+ });
static/styles.css ADDED
@@ -0,0 +1,978 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import url('https://fonts.googleapis.com/css2?family=Audiowide&family=Lexend:wght@100..900&display=swap');
2
+
3
+ * {
4
+ margin: 0;
5
+ padding: 0;
6
+ box-sizing: border-box;
7
+ font-family: "Lexend", sans-serif;
8
+ }
9
+
10
+ body {
11
+ font-family: 'Roboto', sans-serif;
12
+ /* background: linear-gradient(135deg, #e3f2fd 0%, #f5f5f5 50%, #e8f5e8 100%); */
13
+ background: #4285f4;
14
+ min-height: 100vh;
15
+ display: flex;
16
+ flex-direction: column;
17
+ color: #333;
18
+ position: relative;
19
+ overflow: hidden;
20
+ }
21
+
22
+ /* Rain Effect Styles */
23
+ .rain-container {
24
+ position: fixed;
25
+ top: 0;
26
+ left: 0;
27
+ width: 100%;
28
+ height: 100%;
29
+ pointer-events: none;
30
+ z-index: -1;
31
+ }
32
+
33
+ .rain-drop {
34
+ position: absolute;
35
+ width: 2px;
36
+ height: 20px;
37
+ /* background: linear-gradient(to bottom, rgba(174, 194, 224, 0.8), rgba(174, 194, 224, 0.3)); */
38
+ background: linear-gradient(to bottom, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.9));
39
+ border-radius: 50%;
40
+ animation: fall linear infinite;
41
+ }
42
+
43
+ @keyframes fall {
44
+ 0% {
45
+ transform: translateY(-100vh);
46
+ opacity: 1;
47
+ }
48
+
49
+ 100% {
50
+ transform: translateY(100vh);
51
+ opacity: 0;
52
+ }
53
+ }
54
+
55
+ .container {
56
+ flex: 1;
57
+ display: flex;
58
+ flex-direction: column;
59
+ padding: 20px;
60
+ /* max-width: 1200px; */
61
+ margin: 0 auto;
62
+ width: 100%;
63
+ }
64
+
65
+ /* Top Bar */
66
+ .top-bar {
67
+ display: flex;
68
+ justify-content: space-between;
69
+ align-items: center;
70
+ margin-bottom: 20px;
71
+ }
72
+
73
+ /* User Info Styles */
74
+ .user-info {
75
+ display: flex;
76
+ align-items: center;
77
+ gap: 10px;
78
+ color: #ffffff;
79
+ font-size: 1rem;
80
+ font-weight: 500;
81
+ position: relative;
82
+ margin-right: 20px;
83
+ }
84
+
85
+ .account-btn {
86
+ background: rgba(255, 255, 255, 0.2);
87
+ border: none;
88
+ border-radius: 50%;
89
+ width: 50px;
90
+ height: 50px;
91
+ display: flex;
92
+ align-items: center;
93
+ justify-content: center;
94
+ cursor: pointer;
95
+ transition: all 0.3s ease;
96
+ color: #ffffff;
97
+ }
98
+
99
+ .account-btn:hover {
100
+ background: rgba(255, 255, 255, 0.3);
101
+ transform: scale(1.1);
102
+ }
103
+
104
+ .account-btn .material-icons {
105
+ font-size: 2rem;
106
+ }
107
+
108
+ /* Account Dropdown */
109
+ .account-dropdown {
110
+ position: absolute;
111
+ top: 50px;
112
+ left: 0;
113
+ background: white;
114
+ border-radius: 12px;
115
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
116
+ min-width: 220px;
117
+ opacity: 0;
118
+ visibility: hidden;
119
+ transform: translateY(-10px);
120
+ transition: all 0.3s ease;
121
+ z-index: 1000;
122
+ }
123
+
124
+ .account-dropdown.active {
125
+ opacity: 1;
126
+ visibility: visible;
127
+ transform: translateY(0);
128
+ }
129
+
130
+ .dropdown-header {
131
+ display: flex;
132
+ align-items: center;
133
+ gap: 12px;
134
+ padding: 16px;
135
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
136
+ }
137
+
138
+ .dropdown-header .material-icons {
139
+ color: #4285f4;
140
+ font-size: 1.5rem;
141
+ }
142
+
143
+ .dropdown-username {
144
+ color: #333;
145
+ font-weight: 500;
146
+ }
147
+
148
+ .dropdown-item {
149
+ display: flex;
150
+ align-items: center;
151
+ gap: 12px;
152
+ padding: 12px 16px;
153
+ cursor: pointer;
154
+ transition: background-color 0.2s ease;
155
+ color: #333;
156
+ }
157
+
158
+ .dropdown-item:hover {
159
+ background-color: rgba(66, 133, 244, 0.1);
160
+ }
161
+
162
+ .dropdown-item .material-icons {
163
+ font-size: 1.2rem;
164
+ color: #666;
165
+ }
166
+
167
+ .dropdown-divider {
168
+ height: 1px;
169
+ background-color: rgba(0, 0, 0, 0.1);
170
+ margin: 8px 0;
171
+ }
172
+
173
+ .logout-btn {
174
+ background: rgba(255, 255, 255, 0.2);
175
+ border: none;
176
+ border-radius: 50%;
177
+ width: 40px;
178
+ height: 40px;
179
+ display: flex;
180
+ align-items: center;
181
+ justify-content: center;
182
+ cursor: pointer;
183
+ transition: all 0.3s ease;
184
+ color: #ffffff;
185
+ }
186
+
187
+ .logout-btn:hover {
188
+ background: rgba(255, 255, 255, 0.3);
189
+ transform: scale(1.1);
190
+ }
191
+
192
+ .logout-btn .material-icons {
193
+ font-size: 1.2rem;
194
+ }
195
+
196
+ .username {
197
+ font-size: 1.1rem;
198
+ color: #ffffff;
199
+ margin-right: 20px;
200
+ }
201
+
202
+ /* Clock Styles */
203
+ .clock {
204
+ color: #ffffff;
205
+ text-align: left;
206
+ text-shadow: none;
207
+ width: 320px;
208
+ }
209
+
210
+ .time {
211
+ font-family: "Lexend", sans-serif;
212
+ font-size: 3rem;
213
+ /* font-weight: 300; */
214
+ margin-bottom: 8px;
215
+ }
216
+
217
+ .date {
218
+ font-size: 1.1rem;
219
+ font-weight: 400;
220
+ opacity: 0.9;
221
+ }
222
+
223
+ /* Search Bar Styles */
224
+ .search-container {
225
+ width: 100%;
226
+ /* max-width: 400px; */
227
+ margin-right: 50px;
228
+ position: relative;
229
+ }
230
+
231
+ .search-container::placeholder {
232
+ color: #999;
233
+ font-family: "Lexend", sans-serif;
234
+ }
235
+
236
+ .search-box {
237
+ width: 100%;
238
+ padding: 16px 24px;
239
+ border: none;
240
+ border-radius: 50px;
241
+ font-size: 1.1rem;
242
+ background: rgba(255, 255, 255, 0.95);
243
+ backdrop-filter: blur(10px);
244
+ /* box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); */
245
+ outline: none;
246
+ transition: all 0.3s ease;
247
+ /* border: 2px solid rgba(0, 0, 0, 0.2); */
248
+ }
249
+
250
+ .search-icon {
251
+ position: absolute;
252
+ right: 20px;
253
+ top: 50%;
254
+ transform: translateY(-50%);
255
+ color: #666;
256
+ cursor: pointer;
257
+ transition: color 0.3s ease;
258
+ }
259
+
260
+ .search-icon:hover {
261
+ color: #4285f4;
262
+ }
263
+
264
+ /* Main Content Grid */
265
+ .main-content {
266
+ display: grid;
267
+ grid-template-columns: 1fr 1fr;
268
+ gap: 30px;
269
+ width: 100%;
270
+ /* max-width: 1000px; */
271
+ margin: 0 auto;
272
+ flex: 1;
273
+ }
274
+
275
+ /* Card Styles */
276
+ .card {
277
+ background: rgba(255, 255, 255, 0.95);
278
+ backdrop-filter: blur(10px);
279
+ border-radius: 16px;
280
+ padding: 24px;
281
+ /* box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); */
282
+ transition: all 0.3s ease;
283
+ }
284
+
285
+ .card-header {
286
+ display: flex;
287
+ align-items: center;
288
+ margin-bottom: 20px;
289
+ padding-bottom: 16px;
290
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
291
+ }
292
+
293
+ .card-icon {
294
+ margin-right: 12px;
295
+ color: #4285f4;
296
+ }
297
+
298
+ .card-title {
299
+ font-size: 1.3rem;
300
+ font-weight: 500;
301
+ color: #333;
302
+ }
303
+
304
+ /* Bookmarks Styles */
305
+ .bookmarks-grid {
306
+ display: grid;
307
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
308
+ gap: 16px;
309
+ max-height: 400px;
310
+ overflow-y: auto;
311
+ padding-right: 8px;
312
+ }
313
+
314
+ /* Custom scrollbar for bookmarks */
315
+ .bookmarks-grid::-webkit-scrollbar {
316
+ width: 4px;
317
+ }
318
+
319
+ .bookmarks-grid::-webkit-scrollbar-track {
320
+ background: rgba(0, 0, 0, 0.1);
321
+ border-radius: 2px;
322
+ }
323
+
324
+ .bookmarks-grid::-webkit-scrollbar-thumb {
325
+ background: #4285f4;
326
+ border-radius: 2px;
327
+ }
328
+
329
+ .bookmarks-grid::-webkit-scrollbar-thumb:hover {
330
+ background: #3367d6;
331
+ }
332
+
333
+ .bookmark {
334
+ display: flex;
335
+ flex-direction: column;
336
+ align-items: center;
337
+ padding: 16px;
338
+ border-radius: 12px;
339
+ background: rgba(66, 133, 244, 0.1);
340
+ text-decoration: none;
341
+ color: #333;
342
+ transition: all 0.3s ease;
343
+ cursor: pointer;
344
+ }
345
+
346
+ .bookmark:hover {
347
+ background: rgba(66, 133, 244, 0.2);
348
+ transform: translateY(-2px);
349
+ }
350
+
351
+ .bookmark-icon {
352
+ font-size: 2rem;
353
+ margin-bottom: 8px;
354
+ color: #4285f4;
355
+ }
356
+
357
+ .bookmark-name {
358
+ font-size: 0.9rem;
359
+ text-align: center;
360
+ font-weight: 500;
361
+ }
362
+
363
+ .bookmark-url {
364
+ font-size: 0.8rem;
365
+ color: #666;
366
+ margin-top: 4px;
367
+ }
368
+
369
+ /* Notepad Styles */
370
+ .notepad-container {
371
+ /* height: 500px; */
372
+ height: calc(100% - 50px);
373
+ display: flex;
374
+ flex-direction: column;
375
+ }
376
+
377
+ .notes-tabs {
378
+ display: flex;
379
+ margin-bottom: 16px;
380
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
381
+ }
382
+
383
+ .note-tab {
384
+ padding: 8px 16px;
385
+ background: none;
386
+ border: none;
387
+ cursor: pointer;
388
+ border-bottom: 2px solid transparent;
389
+ transition: all 0.3s ease;
390
+ font-size: 0.9rem;
391
+ color: #666;
392
+ position: relative;
393
+ }
394
+
395
+ .note-tab.active {
396
+ color: #4285f4;
397
+ border-bottom-color: #4285f4;
398
+ }
399
+
400
+ .note-tab:hover {
401
+ background: rgba(0, 0, 0, 0.05);
402
+ }
403
+
404
+ .note-tab .close-note {
405
+ margin-left: 8px;
406
+ font-size: 0.8rem;
407
+ opacity: 0.6;
408
+ }
409
+
410
+ .note-tab .close-note:hover {
411
+ opacity: 1;
412
+ color: #f44336;
413
+ }
414
+
415
+ .add-note-btn {
416
+ padding: 8px 12px;
417
+ background: rgba(66, 133, 244, 0.1);
418
+ border: none;
419
+ border-radius: 4px;
420
+ cursor: pointer;
421
+ color: #4285f4;
422
+ font-size: 0.9rem;
423
+ transition: all 0.3s ease;
424
+ margin-left: 8px;
425
+ }
426
+
427
+ .add-note-btn:hover {
428
+ background: rgba(66, 133, 244, 0.2);
429
+ }
430
+
431
+ .note-content {
432
+ flex: 1;
433
+ display: none;
434
+ flex-direction: column;
435
+ }
436
+
437
+ .note-content.active {
438
+ display: flex;
439
+ }
440
+
441
+ .notepad-textarea {
442
+ flex: 1;
443
+ border: none;
444
+ resize: none;
445
+ font-family: 'Lexend', sans-serif;
446
+ font-size: 1rem;
447
+ line-height: 1.5;
448
+ padding: 16px;
449
+ border-radius: 8px;
450
+ background: rgba(0, 0, 0, 0.02);
451
+ outline: none;
452
+ transition: all 0.3s ease;
453
+ min-height: 300px;
454
+ }
455
+
456
+ .notepad-textarea:focus {
457
+ background: rgba(0, 0, 0, 0.05);
458
+ }
459
+
460
+ .notepad-textarea::placeholder {
461
+ color: #999;
462
+ font-family: "Lexend", sans-serif;
463
+ }
464
+
465
+ .notepad-footer {
466
+ display: flex;
467
+ justify-content: space-between;
468
+ align-items: center;
469
+ margin-top: 12px;
470
+ font-size: 0.9rem;
471
+ color: #666;
472
+ }
473
+
474
+ .save-indicator {
475
+ color: #4caf50;
476
+ opacity: 0;
477
+ transition: opacity 0.3s ease;
478
+ }
479
+
480
+ .save-indicator.visible {
481
+ opacity: 1;
482
+ }
483
+
484
+ /* Add Bookmark Button */
485
+ .add-bookmark-btn {
486
+ display: flex;
487
+ align-items: center;
488
+ justify-content: center;
489
+ padding: 12px;
490
+ background: rgba(66, 133, 244, 0.1);
491
+ border: 2px dashed rgba(66, 133, 244, 0.3);
492
+ border-radius: 12px;
493
+ color: #4285f4;
494
+ cursor: pointer;
495
+ transition: all 0.3s ease;
496
+ text-decoration: none;
497
+ }
498
+
499
+ .add-bookmark-btn:hover {
500
+ background: rgba(66, 133, 244, 0.2);
501
+ border-color: rgba(66, 133, 244, 0.5);
502
+ }
503
+
504
+ /* Modal Styles */
505
+ .modal {
506
+ display: none;
507
+ position: fixed;
508
+ z-index: 1000;
509
+ left: 0;
510
+ top: 0;
511
+ width: 100%;
512
+ height: 100%;
513
+ background: rgba(0, 0, 0, 0.5);
514
+ backdrop-filter: blur(5px);
515
+ }
516
+
517
+ .modal-content {
518
+ background: white;
519
+ margin: 15% auto;
520
+ padding: 24px;
521
+ border-radius: 16px;
522
+ width: 90%;
523
+ max-width: 400px;
524
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
525
+ }
526
+
527
+ /* Password Entry Modal - needs less top margin due to height */
528
+ #passwordEntryModal .modal-content {
529
+ margin: 5% auto;
530
+ max-width: 500px;
531
+ }
532
+
533
+ .modal-header {
534
+ display: flex;
535
+ justify-content: space-between;
536
+ align-items: center;
537
+ margin-bottom: 20px;
538
+ }
539
+
540
+ .modal-title {
541
+ font-size: 1.3rem;
542
+ font-weight: 500;
543
+ }
544
+
545
+ .close-btn {
546
+ background: none;
547
+ border: none;
548
+ font-size: 1.5rem;
549
+ cursor: pointer;
550
+ color: #666;
551
+ padding: 4px;
552
+ }
553
+
554
+ .form-group {
555
+ margin-bottom: 16px;
556
+ }
557
+
558
+ .form-label {
559
+ display: block;
560
+ margin-bottom: 8px;
561
+ font-weight: 500;
562
+ color: #333;
563
+ }
564
+
565
+ .form-input {
566
+ width: 100%;
567
+ padding: 12px;
568
+ border: 2px solid #e0e0e0;
569
+ border-radius: 8px;
570
+ font-size: 1rem;
571
+ outline: none;
572
+ transition: border-color 0.3s ease;
573
+ }
574
+
575
+ .form-input:focus {
576
+ border-color: #4285f4;
577
+ }
578
+
579
+ .btn {
580
+ background: #4285f4;
581
+ color: white;
582
+ border: none;
583
+ padding: 12px 24px;
584
+ border-radius: 8px;
585
+ font-size: 1rem;
586
+ cursor: pointer;
587
+ transition: all 0.3s ease;
588
+ }
589
+
590
+ .btn:hover {
591
+ background: #3367d6;
592
+ transform: translateY(-1px);
593
+ }
594
+
595
+ .btn-secondary {
596
+ background: #f0f0f0;
597
+ color: #333;
598
+ margin-right: 12px;
599
+ }
600
+
601
+ .btn-secondary:hover {
602
+ background: #e0e0e0;
603
+ }
604
+
605
+ .btn-danger {
606
+ background: #dc3545;
607
+ color: white;
608
+ border: none;
609
+ padding: 12px 24px;
610
+ border-radius: 8px;
611
+ font-size: 1rem;
612
+ cursor: pointer;
613
+ transition: all 0.3s ease;
614
+ }
615
+
616
+ .btn-danger:hover {
617
+ background: #c82333;
618
+ }
619
+
620
+ /* Developer Modal Styles */
621
+ .developer-modal {
622
+ max-width: 1000px;
623
+ width: 90%;
624
+ margin: 5% auto;
625
+ }
626
+
627
+ .developer-content {
628
+ padding: 20px 0;
629
+ }
630
+
631
+ .stats-grid {
632
+ display: grid;
633
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
634
+ gap: 20px;
635
+ margin-bottom: 30px;
636
+ }
637
+
638
+ .stat-card {
639
+ display: flex;
640
+ align-items: center;
641
+ gap: 15px;
642
+ padding: 20px;
643
+ background: rgba(66, 133, 244, 0.1);
644
+ border-radius: 12px;
645
+ transition: all 0.3s ease;
646
+ }
647
+
648
+ .stat-icon {
649
+ width: 50px;
650
+ height: 50px;
651
+ border-radius: 50%;
652
+ background: #4285f4;
653
+ display: flex;
654
+ align-items: center;
655
+ justify-content: center;
656
+ color: white;
657
+ }
658
+
659
+ .stat-icon .material-icons {
660
+ font-size: 1.5rem;
661
+ }
662
+
663
+ .stat-info h4 {
664
+ font-size: 1.8rem;
665
+ font-weight: 700;
666
+ margin: 0;
667
+ color: #333;
668
+ }
669
+
670
+ .stat-info p {
671
+ font-size: 0.9rem;
672
+ color: #666;
673
+ margin: 2px 0 0 0;
674
+ }
675
+
676
+ .chart-container {
677
+ background: rgba(0, 0, 0, 0.02);
678
+ border-radius: 12px;
679
+ padding: 20px;
680
+ text-align: center;
681
+ height: 350px;
682
+ }
683
+
684
+ .chart-container h4 {
685
+ margin-bottom: 20px;
686
+ color: #333;
687
+ font-size: 1.2rem;
688
+ font-weight: 600;
689
+ }
690
+
691
+ #activityChart {
692
+ max-width: 100%;
693
+ height: 300px !important;
694
+ }
695
+
696
+ /* Vault Styles */
697
+ .vault-modal {
698
+ max-width: 900px;
699
+ width: 95%;
700
+ margin: 3% auto;
701
+ max-height: 85vh;
702
+ }
703
+
704
+ .vault-content {
705
+ padding: 20px 0;
706
+ max-height: 70vh;
707
+ display: flex;
708
+ flex-direction: column;
709
+ }
710
+
711
+ .vault-auth-content {
712
+ text-align: center;
713
+ padding: 20px;
714
+ }
715
+
716
+ .vault-lock-icon {
717
+ font-size: 3rem;
718
+ color: #4285f4;
719
+ margin-bottom: 20px;
720
+ }
721
+
722
+ .vault-lock-icon .material-icons {
723
+ font-size: 3rem;
724
+ }
725
+
726
+ .vault-toolbar {
727
+ display: flex;
728
+ justify-content: space-between;
729
+ align-items: center;
730
+ margin-bottom: 20px;
731
+ gap: 20px;
732
+ }
733
+
734
+ .vault-search {
735
+ flex: 1;
736
+ position: relative;
737
+ max-width: 400px;
738
+ }
739
+
740
+ .search-input {
741
+ width: 100%;
742
+ padding: 12px 45px 12px 16px;
743
+ border: 2px solid #e0e0e0;
744
+ border-radius: 25px;
745
+ font-size: 1rem;
746
+ outline: none;
747
+ transition: border-color 0.3s ease;
748
+ background: rgba(255, 255, 255, 0.95);
749
+ }
750
+
751
+ .search-input:focus {
752
+ border-color: #4285f4;
753
+ }
754
+
755
+ .vault-search .search-icon {
756
+ position: absolute;
757
+ right: 15px;
758
+ top: 50%;
759
+ transform: translateY(-50%);
760
+ color: #666;
761
+ cursor: pointer;
762
+ }
763
+
764
+ .add-password-btn {
765
+ display: flex;
766
+ align-items: center;
767
+ gap: 8px;
768
+ white-space: nowrap;
769
+ }
770
+
771
+ .vault-list {
772
+ flex: 1;
773
+ overflow-y: auto;
774
+ border-radius: 12px;
775
+ background: rgba(0, 0, 0, 0.02);
776
+ padding: 10px;
777
+ max-height: 400px;
778
+ }
779
+
780
+ .vault-entry {
781
+ background: white;
782
+ border-radius: 12px;
783
+ padding: 16px;
784
+ margin-bottom: 12px;
785
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
786
+ cursor: pointer;
787
+ transition: all 0.3s ease;
788
+ border-left: 4px solid #4285f4;
789
+ }
790
+
791
+ .vault-entry-header {
792
+ display: flex;
793
+ justify-content: space-between;
794
+ align-items: center;
795
+ margin-bottom: 8px;
796
+ }
797
+
798
+ .vault-entry-title {
799
+ font-weight: 600;
800
+ font-size: 1.1rem;
801
+ color: #333;
802
+ }
803
+
804
+ .vault-entry-actions {
805
+ display: flex;
806
+ gap: 8px;
807
+ }
808
+
809
+ .vault-action-btn {
810
+ background: none;
811
+ border: none;
812
+ padding: 6px;
813
+ border-radius: 50%;
814
+ cursor: pointer;
815
+ transition: all 0.3s ease;
816
+ color: #666;
817
+ }
818
+
819
+ .vault-action-btn:hover {
820
+ background: rgba(66, 133, 244, 0.1);
821
+ color: #4285f4;
822
+ }
823
+
824
+ .vault-entry-info {
825
+ display: grid;
826
+ grid-template-columns: auto 1fr;
827
+ gap: 8px 16px;
828
+ font-size: 0.9rem;
829
+ color: #666;
830
+ }
831
+
832
+ .vault-entry-label {
833
+ font-weight: 500;
834
+ color: #333;
835
+ }
836
+
837
+ .vault-entry-value {
838
+ word-break: break-word;
839
+ }
840
+
841
+ .password-input-container {
842
+ position: relative;
843
+ display: flex;
844
+ }
845
+
846
+ .password-input-container .form-input {
847
+ padding-right: 80px;
848
+ }
849
+
850
+ .password-toggle-btn,
851
+ .password-generate-btn {
852
+ position: absolute;
853
+ right: 5px;
854
+ top: 50%;
855
+ transform: translateY(-50%);
856
+ background: none;
857
+ border: none;
858
+ padding: 8px;
859
+ cursor: pointer;
860
+ color: #666;
861
+ border-radius: 50%;
862
+ transition: all 0.3s ease;
863
+ }
864
+
865
+ .password-toggle-btn {
866
+ right: 40px;
867
+ }
868
+
869
+ .password-generate-btn {
870
+ right: 5px;
871
+ }
872
+
873
+ .password-toggle-btn:hover,
874
+ .password-generate-btn:hover {
875
+ background: rgba(66, 133, 244, 0.1);
876
+ color: #4285f4;
877
+ }
878
+
879
+ .vault-empty {
880
+ text-align: center;
881
+ padding: 40px;
882
+ color: #666;
883
+ }
884
+
885
+ .vault-empty .material-icons {
886
+ font-size: 3rem;
887
+ color: #ccc;
888
+ margin-bottom: 16px;
889
+ }
890
+
891
+ .password-strength {
892
+ margin-top: 5px;
893
+ height: 4px;
894
+ border-radius: 2px;
895
+ background: #e0e0e0;
896
+ overflow: hidden;
897
+ }
898
+
899
+ .password-strength-bar {
900
+ height: 100%;
901
+ transition: all 0.3s ease;
902
+ border-radius: 2px;
903
+ }
904
+
905
+ .strength-weak {
906
+ background: #f44336;
907
+ width: 25%;
908
+ }
909
+
910
+ .strength-fair {
911
+ background: #ff9800;
912
+ width: 50%;
913
+ }
914
+
915
+ .strength-good {
916
+ background: #2196f3;
917
+ width: 75%;
918
+ }
919
+
920
+ .strength-strong {
921
+ background: #4caf50;
922
+ width: 100%;
923
+ }
924
+
925
+ /* Responsive Design */
926
+ @media (max-width: 768px) {
927
+ .top-bar {
928
+ flex-direction: column;
929
+ gap: 20px;
930
+ align-items: center;
931
+ }
932
+
933
+ .main-content {
934
+ grid-template-columns: 1fr;
935
+ gap: 20px;
936
+ }
937
+
938
+ .time {
939
+ font-size: 2.5rem;
940
+ }
941
+
942
+ .bookmarks-grid {
943
+ grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
944
+ }
945
+
946
+ .card {
947
+ padding: 20px;
948
+ }
949
+
950
+ .search-container {
951
+ max-width: 100%;
952
+ }
953
+ }
954
+
955
+ @media (max-width: 480px) {
956
+ .time {
957
+ font-size: 2rem;
958
+ }
959
+
960
+ .container {
961
+ padding: 15px;
962
+ }
963
+
964
+ .modal-content {
965
+ margin: 10% auto;
966
+ width: 95%;
967
+ }
968
+
969
+ .notes-tabs {
970
+ flex-wrap: wrap;
971
+ gap: 4px;
972
+ }
973
+
974
+ .note-tab {
975
+ padding: 6px 12px;
976
+ font-size: 0.8rem;
977
+ }
978
+ }
templates/auth.html ADDED
@@ -0,0 +1,362 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Start Suite - Login</title>
8
+ <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
9
+ <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
10
+ <link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='icon.png') }}">
11
+ <style>
12
+ @import url('https://fonts.googleapis.com/css2?family=Audiowide&family=Lexend:wght@100..900&display=swap');
13
+
14
+ * {
15
+ margin: 0;
16
+ padding: 0;
17
+ font-family: "Lexend", sans-serif;
18
+ }
19
+
20
+ body {
21
+ font-family: 'Roboto', sans-serif;
22
+ background: #4285f4;
23
+ min-height: 100vh;
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+ color: #333;
28
+ overflow: hidden;
29
+ position: relative;
30
+ }
31
+
32
+ /* Rain Effect Styles */
33
+ .rain-container {
34
+ position: fixed;
35
+ top: 0;
36
+ left: 0;
37
+ width: 100%;
38
+ height: 100%;
39
+ pointer-events: none;
40
+ z-index: 1;
41
+ }
42
+
43
+ .rain-drop {
44
+ position: absolute;
45
+ background: linear-gradient(to bottom, rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0.2));
46
+ border-radius: 50px;
47
+ animation: fall linear infinite;
48
+ opacity: 0.7;
49
+ }
50
+
51
+ @keyframes fall {
52
+ 0% {
53
+ transform: translateY(-20px);
54
+ opacity: 1;
55
+ }
56
+
57
+ 100% {
58
+ transform: translateY(100vh);
59
+ opacity: 0.3;
60
+ }
61
+ }
62
+
63
+ .auth-container {
64
+ background: rgba(255, 255, 255, 0.95);
65
+ border-radius: 20px;
66
+ padding: 40px;
67
+ width: 100%;
68
+ max-width: 400px;
69
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
70
+ backdrop-filter: blur(10px);
71
+ position: relative;
72
+ z-index: 2;
73
+ }
74
+
75
+ .auth-header {
76
+ text-align: center;
77
+ margin-bottom: 30px;
78
+ }
79
+
80
+ .auth-title {
81
+ font-size: 2rem;
82
+ font-weight: 700;
83
+ color: #333;
84
+ margin-bottom: 10px;
85
+ }
86
+
87
+ .auth-subtitle {
88
+ color: #666;
89
+ font-size: 1rem;
90
+ }
91
+
92
+ .auth-form {
93
+ display: flex;
94
+ flex-direction: column;
95
+ gap: 20px;
96
+ }
97
+
98
+ .form-group {
99
+ position: relative;
100
+ }
101
+
102
+ .form-input {
103
+ width: calc(100% - 42px);
104
+ padding: 15px 20px;
105
+ border: 2px solid #e1e5e9;
106
+ border-radius: 10px;
107
+ font-size: 1rem;
108
+ transition: all 0.3s ease;
109
+ background: white;
110
+ }
111
+
112
+ .form-input:focus {
113
+ outline: none;
114
+ border-color: #667eea;
115
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
116
+ }
117
+
118
+ .form-input::placeholder {
119
+ color: #999;
120
+ }
121
+
122
+ .auth-btn {
123
+ padding: 15px 30px;
124
+ border: none;
125
+ border-radius: 10px;
126
+ font-size: 1rem;
127
+ font-weight: 500;
128
+ cursor: pointer;
129
+ transition: all 0.3s ease;
130
+ background: #667eea;
131
+ color: white;
132
+ }
133
+
134
+ .auth-btn:hover {
135
+ transform: translateY(-2px);
136
+ box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
137
+ }
138
+
139
+ .auth-btn:active {
140
+ transform: translateY(0);
141
+ }
142
+
143
+ .auth-toggle {
144
+ text-align: center;
145
+ margin-top: 20px;
146
+ }
147
+
148
+ .auth-toggle a {
149
+ color: #667eea;
150
+ text-decoration: none;
151
+ font-weight: 500;
152
+ cursor: pointer;
153
+ }
154
+
155
+ .auth-toggle a:hover {
156
+ text-decoration: underline;
157
+ }
158
+
159
+ .error-message {
160
+ color: #e74c3c;
161
+ font-size: 0.9rem;
162
+ margin-top: 10px;
163
+ text-align: center;
164
+ }
165
+
166
+ .success-message {
167
+ color: #27ae60;
168
+ font-size: 0.9rem;
169
+ margin-top: 10px;
170
+ text-align: center;
171
+ }
172
+
173
+ .hidden {
174
+ display: none;
175
+ }
176
+ </style>
177
+ </head>
178
+
179
+ <body>
180
+ <div class="rain-container" id="rainContainer"></div>
181
+
182
+ <div class="auth-container">
183
+ <div class="auth-header">
184
+ <h1 class="auth-title">Start Suite</h1>
185
+ <p class="auth-subtitle">Your personalized dashboard</p>
186
+ </div>
187
+
188
+ <form class="auth-form" id="loginForm">
189
+ <div class="form-group">
190
+ <input type="text" class="form-input" id="username" placeholder="Username" required>
191
+ </div>
192
+ <div class="form-group">
193
+ <input type="password" class="form-input" id="password" placeholder="Password" required>
194
+ </div>
195
+ <button type="submit" class="auth-btn" id="authBtn">Login</button>
196
+ <div class="error-message hidden" id="errorMessage"></div>
197
+ <div class="success-message hidden" id="successMessage"></div>
198
+ </form>
199
+
200
+ <div class="auth-toggle">
201
+ <span id="toggleText">Don't have an account? </span>
202
+ <a href="#" id="toggleLink">Sign up</a>
203
+ </div>
204
+ </div>
205
+
206
+ <script>
207
+ let isLoginMode = true;
208
+ const form = document.getElementById('loginForm');
209
+ const authBtn = document.getElementById('authBtn');
210
+ const toggleLink = document.getElementById('toggleLink');
211
+ const toggleText = document.getElementById('toggleText');
212
+ const errorMessage = document.getElementById('errorMessage');
213
+ const successMessage = document.getElementById('successMessage');
214
+
215
+ function showError(message) {
216
+ errorMessage.textContent = message;
217
+ errorMessage.classList.remove('hidden');
218
+ successMessage.classList.add('hidden');
219
+ }
220
+
221
+ function showSuccess(message) {
222
+ successMessage.textContent = message;
223
+ successMessage.classList.remove('hidden');
224
+ errorMessage.classList.add('hidden');
225
+ }
226
+
227
+ function hideMessages() {
228
+ errorMessage.classList.add('hidden');
229
+ successMessage.classList.add('hidden');
230
+ }
231
+
232
+ toggleLink.addEventListener('click', (e) => {
233
+ e.preventDefault();
234
+ isLoginMode = !isLoginMode;
235
+
236
+ if (isLoginMode) {
237
+ authBtn.textContent = 'Login';
238
+ toggleText.textContent = "Don't have an account? ";
239
+ toggleLink.textContent = 'Sign up';
240
+ } else {
241
+ authBtn.textContent = 'Sign Up';
242
+ toggleText.textContent = 'Already have an account? ';
243
+ toggleLink.textContent = 'Login';
244
+ }
245
+
246
+ hideMessages();
247
+ });
248
+
249
+ form.addEventListener('submit', async (e) => {
250
+ e.preventDefault();
251
+
252
+ const username = document.getElementById('username').value;
253
+ const password = document.getElementById('password').value;
254
+
255
+ if (!username || !password) {
256
+ showError('Please fill in all fields');
257
+ return;
258
+ }
259
+
260
+ const endpoint = isLoginMode ? '/login' : '/register';
261
+
262
+ try {
263
+ const response = await fetch(endpoint, {
264
+ method: 'POST',
265
+ headers: {
266
+ 'Content-Type': 'application/json',
267
+ },
268
+ body: JSON.stringify({ username, password })
269
+ });
270
+
271
+ const data = await response.json();
272
+
273
+ if (response.ok) {
274
+ showSuccess(data.message);
275
+ setTimeout(() => {
276
+ window.location.href = '/';
277
+ }, 1000);
278
+ } else {
279
+ showError(data.error);
280
+ }
281
+ } catch (error) {
282
+ showError('An error occurred. Please try again.');
283
+ }
284
+ });
285
+
286
+ // Rain Effect
287
+ class RainEffect {
288
+ constructor() {
289
+ this.rainContainer = document.getElementById('rainContainer');
290
+ this.rainDrops = [];
291
+ this.maxDrops = 100;
292
+ this.init();
293
+ }
294
+
295
+ init() {
296
+ this.createRain();
297
+ this.animateRain();
298
+ }
299
+
300
+ createRain() {
301
+ for (let i = 0; i < this.maxDrops; i++) {
302
+ this.createRainDrop();
303
+ }
304
+ }
305
+
306
+ createRainDrop() {
307
+ const drop = document.createElement('div');
308
+ drop.className = 'rain-drop';
309
+
310
+ // Random horizontal position
311
+ const x = Math.random() * window.innerWidth;
312
+
313
+ // Random size variation
314
+ const size = Math.random() * 0.8 + 0.2;
315
+ drop.style.width = `${2 * size}px`;
316
+ drop.style.height = `${20 * size}px`;
317
+
318
+ // Random speed (duration)
319
+ const duration = Math.random() * 2 + 1; // 1-3 seconds
320
+ drop.style.animationDuration = `${duration}s`;
321
+
322
+ // Random delay
323
+ const delay = Math.random() * 2;
324
+ drop.style.animationDelay = `${delay}s`;
325
+
326
+ // Position the drop
327
+ drop.style.left = `${x}px`;
328
+ drop.style.top = '-20px';
329
+
330
+ this.rainContainer.appendChild(drop);
331
+ this.rainDrops.push(drop);
332
+ }
333
+
334
+ animateRain() {
335
+ // Clean up and recreate drops periodically
336
+ setInterval(() => {
337
+ this.rainDrops.forEach(drop => {
338
+ const rect = drop.getBoundingClientRect();
339
+ if (rect.top > window.innerHeight) {
340
+ // Reset the drop to the top with new random position
341
+ drop.style.left = `${Math.random() * window.innerWidth}px`;
342
+ drop.style.top = '-20px';
343
+
344
+ // Randomize properties again
345
+ const size = Math.random() * 0.8 + 0.2;
346
+ drop.style.width = `${2 * size}px`;
347
+ drop.style.height = `${20 * size}px`;
348
+
349
+ const duration = Math.random() * 2 + 1;
350
+ drop.style.animationDuration = `${duration}s`;
351
+ }
352
+ });
353
+ }, 100);
354
+ }
355
+ }
356
+
357
+ // Initialize rain effect when the page loads
358
+ new RainEffect();
359
+ </script>
360
+ </body>
361
+
362
+ </html>
templates/index.html ADDED
@@ -0,0 +1,346 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Start Suite</title>
8
+ <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
9
+ <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
10
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
11
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
12
+ <link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='icon.png') }}">
13
+ </head>
14
+
15
+ <body>
16
+ <!-- Rain Effect Background -->
17
+ <div class="rain-container" id="rainContainer"></div>
18
+
19
+ <div class="container">
20
+ <!-- Top Bar with Clock and Search -->
21
+ <div class="top-bar">
22
+ <!-- User Info -->
23
+ <div class="user-info">
24
+ <button class="account-btn" onclick="toggleAccountDropdown()">
25
+ <span class="material-icons">account_circle</span>
26
+ </button>
27
+
28
+ <!-- Account Dropdown -->
29
+ <div class="account-dropdown" id="accountDropdown">
30
+ <div class="dropdown-header">
31
+ <span class="material-icons">person</span>
32
+ <span id="username" class="dropdown-username">{{ username }}</span>
33
+ </div>
34
+ <!-- <div class="dropdown-divider"></div> -->
35
+ <div class="dropdown-item" onclick="openChangePasswordModal()">
36
+ <span class="material-icons">lock</span>
37
+ <span>Change Password</span>
38
+ </div>
39
+ <div class="dropdown-item" onclick="openVaultPage()">
40
+ <span class="material-icons">security</span>
41
+ <span>Password Vault</span>
42
+ </div>
43
+ <div class="dropdown-item" onclick="openDeveloperPage()">
44
+ <span class="material-icons">analytics</span>
45
+ <span>Developer Stats</span>
46
+ </div>
47
+ <div class="dropdown-divider"></div>
48
+ <div class="dropdown-item" onclick="logout()">
49
+ <span class="material-icons">logout</span>
50
+ <span>Logout</span>
51
+ </div>
52
+ </div>
53
+ </div>
54
+
55
+ <!-- Search Bar -->
56
+ <div class="search-container">
57
+ <input type="text" class="search-box" placeholder="Search Google or type a URL" id="searchInput">
58
+ <span class="material-icons search-icon" id="searchIcon">search</span>
59
+ </div>
60
+
61
+ <!-- Clock Section -->
62
+ <div class="clock">
63
+ <div class="time" id="time">12:00:00</div>
64
+ <div class="date" id="date">Monday, January 1, 2024</div>
65
+ </div>
66
+
67
+
68
+ </div>
69
+
70
+ <!-- Main Content -->
71
+ <div class="main-content">
72
+ <!-- Bookmarks Card -->
73
+ <div class="card">
74
+ <div class="card-header">
75
+ <span class="material-icons card-icon">bookmark</span>
76
+ <h3 class="card-title">Quick Access</h3>
77
+ </div>
78
+ <div class="bookmarks-grid" id="bookmarksGrid">
79
+ <!-- Bookmarks will be populated by JavaScript -->
80
+ </div>
81
+ </div>
82
+
83
+ <!-- Notepad Card -->
84
+ <div class="card">
85
+ <div class="card-header">
86
+ <span class="material-icons card-icon">notes</span>
87
+ <h3 class="card-title">Notes</h3>
88
+ </div>
89
+ <div class="notepad-container">
90
+ <div class="notes-tabs">
91
+ <button class="note-tab active" data-note-id="0">
92
+ Note 1
93
+ <span class="close-note"
94
+ onclick="event.stopPropagation(); window.notePad.removeNote(0);">×</span>
95
+ </button>
96
+ <button class="add-note-btn" onclick="window.notePad.addNote()">
97
+ <span class="material-icons" style="font-size: 1rem;">add</span>
98
+ </button>
99
+ </div>
100
+ <div class="note-content active" data-note-id="0">
101
+ <textarea class="notepad-textarea" placeholder="Start typing your notes here..."></textarea>
102
+ <div class="notepad-footer">
103
+ <span class="save-indicator">
104
+ <span class="material-icons"
105
+ style="font-size: 1rem; vertical-align: middle;">check_circle</span>
106
+ Saved
107
+ </span>
108
+ </div>
109
+ </div>
110
+ </div>
111
+ </div>
112
+ </div>
113
+ </div>
114
+
115
+ <!-- Add Bookmark Modal -->
116
+ <div id="bookmarkModal" class="modal">
117
+ <div class="modal-content">
118
+ <div class="modal-header">
119
+ <h3 class="modal-title" id="modalTitle">Add Bookmark</h3>
120
+ <button class="close-btn" id="closeModal">&times;</button>
121
+ </div>
122
+ <form id="bookmarkForm">
123
+ <div class="form-group">
124
+ <label class="form-label" for="bookmarkName">Name</label>
125
+ <input type="text" class="form-input" id="bookmarkName" required>
126
+ </div>
127
+ <div class="form-group">
128
+ <label class="form-label" for="bookmarkUrl">URL</label>
129
+ <input type="url" class="form-input" id="bookmarkUrl" required>
130
+ </div>
131
+ <div class="form-group">
132
+ <label class="form-label" for="bookmarkIcon">Icon</label>
133
+ <select class="form-input" id="bookmarkIcon" required>
134
+ <option value="">Select an icon</option>
135
+ <option value="language">🌐 Web/Language</option>
136
+ <option value="work">💼 Work</option>
137
+ <option value="home">🏠 Home</option>
138
+ <option value="school">🎓 Education</option>
139
+ <option value="shopping_cart">🛒 Shopping</option>
140
+ <option value="movie">🎬 Entertainment</option>
141
+ <option value="music_note">🎵 Music</option>
142
+ <option value="photo">📸 Photos</option>
143
+ <option value="mail">📧 Email</option>
144
+ <option value="forum">💬 Social</option>
145
+ <option value="sports">⚽ Sports</option>
146
+ <option value="local_dining">🍽️ Food</option>
147
+ <option value="flight">✈️ Travel</option>
148
+ <option value="account_balance">🏦 Finance</option>
149
+ <option value="favorite">❤️ Favorites</option>
150
+ </select>
151
+ </div>
152
+ <div style="display: flex; justify-content: space-between; align-items: center;">
153
+ <button type="button" class="btn btn-danger" id="deleteBtn" style="display: none;">Delete</button>
154
+ <div>
155
+ <button type="button" class="btn btn-secondary" id="cancelBtn">Cancel</button>
156
+ <button type="submit" class="btn" id="submitBtn">Add Bookmark</button>
157
+ </div>
158
+ </div>
159
+ </form>
160
+ </div>
161
+ </div>
162
+
163
+ <!-- Change Password Modal -->
164
+ <div id="passwordModal" class="modal">
165
+ <div class="modal-content">
166
+ <div class="modal-header">
167
+ <h3 class="modal-title">Change Password</h3>
168
+ <button class="close-btn" onclick="closePasswordModal()">&times;</button>
169
+ </div>
170
+ <form id="passwordForm">
171
+ <div class="form-group">
172
+ <label class="form-label" for="currentPassword">Current Password</label>
173
+ <input type="password" class="form-input" id="currentPassword" required>
174
+ </div>
175
+ <div class="form-group">
176
+ <label class="form-label" for="newPassword">New Password</label>
177
+ <input type="password" class="form-input" id="newPassword" required>
178
+ </div>
179
+ <div class="form-group">
180
+ <label class="form-label" for="confirmPassword">Confirm New Password</label>
181
+ <input type="password" class="form-input" id="confirmPassword" required>
182
+ </div>
183
+ <div style="display: flex; justify-content: flex-end;">
184
+ <button type="button" class="btn btn-secondary" onclick="closePasswordModal()">Cancel</button>
185
+ <button type="submit" class="btn">Change Password</button>
186
+ </div>
187
+ </form>
188
+ </div>
189
+ </div>
190
+
191
+ <!-- Developer Stats Modal -->
192
+ <div id="developerModal" class="modal">
193
+ <div class="modal-content developer-modal">
194
+ <div class="modal-header">
195
+ <h3 class="modal-title">Developer Statistics</h3>
196
+ <button class="close-btn" onclick="closeDeveloperModal()">&times;</button>
197
+ </div>
198
+ <div class="developer-content">
199
+ <div class="stats-grid">
200
+ <div class="stat-card">
201
+ <div class="stat-icon">
202
+ <span class="material-icons">people</span>
203
+ </div>
204
+ <div class="stat-info">
205
+ <h4 id="totalUsers">0</h4>
206
+ <p>Total Users</p>
207
+ </div>
208
+ </div>
209
+ <div class="stat-card">
210
+ <div class="stat-icon">
211
+ <span class="material-icons">visibility</span>
212
+ </div>
213
+ <div class="stat-info">
214
+ <h4 id="totalVisits">0</h4>
215
+ <p>Total Visits</p>
216
+ </div>
217
+ </div>
218
+ <div class="stat-card">
219
+ <div class="stat-icon">
220
+ <span class="material-icons">search</span>
221
+ </div>
222
+ <div class="stat-info">
223
+ <h4 id="totalSearches">0</h4>
224
+ <p>Total Searches</p>
225
+ </div>
226
+ </div>
227
+ </div>
228
+ <div class="chart-container">
229
+ <h4>Monthly Activity Trends</h4>
230
+ <canvas id="activityChart" width="600" height="300"></canvas>
231
+ </div>
232
+ </div>
233
+ </div>
234
+ </div>
235
+
236
+ <!-- Vault Authentication Modal -->
237
+ <div id="vaultAuthModal" class="modal">
238
+ <div class="modal-content">
239
+ <div class="modal-header">
240
+ <h3 class="modal-title">Vault Access</h3>
241
+ <button class="close-btn" onclick="closeVaultAuthModal()">&times;</button>
242
+ </div>
243
+ <div class="vault-auth-content">
244
+ <div class="vault-lock-icon">
245
+ <span class="material-icons">lock</span>
246
+ </div>
247
+ <!-- <p>Enter your password to access the vault</p> -->
248
+ <form id="vaultAuthForm">
249
+ <div class="form-group">
250
+ <input type="password" class="form-input" id="vaultPassword" placeholder="Enter your password"
251
+ required>
252
+ </div>
253
+ <div style="display: flex; justify-content: flex-start; gap: 10px;">
254
+ <button type="button" class="btn btn-secondary" onclick="closeVaultAuthModal()">Cancel</button>
255
+ <button type="submit" class="btn">Access</button>
256
+ </div>
257
+ </form>
258
+ </div>
259
+ </div>
260
+ </div>
261
+
262
+ <!-- Password Vault Modal -->
263
+ <div id="vaultModal" class="modal">
264
+ <div class="modal-content vault-modal">
265
+ <div class="modal-header">
266
+ <h3 class="modal-title">Password Vault</h3>
267
+ <button class="close-btn" onclick="closeVaultModal()">&times;</button>
268
+ </div>
269
+ <div class="vault-content">
270
+ <div class="vault-toolbar">
271
+ <div class="vault-search">
272
+ <input type="text" class="search-input" placeholder="Search passwords..." id="vaultSearchInput">
273
+ <span class="material-icons search-icon">search</span>
274
+ </div>
275
+ <button class="btn add-password-btn" onclick="openAddPasswordModal()">
276
+ <span class="material-icons">add</span>
277
+ Add Password
278
+ </button>
279
+ </div>
280
+ <div class="vault-list" id="vaultList">
281
+ <!-- Password entries will be populated here -->
282
+ </div>
283
+ </div>
284
+ </div>
285
+ </div>
286
+
287
+ <!-- Add/Edit Password Modal -->
288
+ <div id="passwordEntryModal" class="modal">
289
+ <div class="modal-content">
290
+ <div class="modal-header">
291
+ <h3 class="modal-title" id="passwordEntryTitle">Add Password</h3>
292
+ <button class="close-btn" onclick="closePasswordEntryModal()">&times;</button>
293
+ </div>
294
+ <form id="passwordEntryForm">
295
+ <div class="form-group">
296
+ <label class="form-label" for="entryTitle">Title</label>
297
+ <input type="text" class="form-input" id="entryTitle" required>
298
+ </div>
299
+ <div class="form-group">
300
+ <label class="form-label" for="entryUsername">Username/Email</label>
301
+ <input type="text" class="form-input" id="entryUsername">
302
+ </div>
303
+ <div class="form-group">
304
+ <label class="form-label" for="entryPassword">Password</label>
305
+ <div class="password-input-container">
306
+ <input type="password" class="form-input" id="entryPassword" required>
307
+ <button type="button" class="password-toggle-btn"
308
+ onclick="togglePasswordVisibility('entryPassword')">
309
+ <span class="material-icons">visibility</span>
310
+ </button>
311
+ <button type="button" class="password-generate-btn" onclick="generatePassword()">
312
+ <span class="material-icons">refresh</span>
313
+ </button>
314
+ </div>
315
+ <div class="password-strength">
316
+ <div class="password-strength-bar"></div>
317
+ </div>
318
+ </div>
319
+ <div class="form-group">
320
+ <label class="form-label" for="entryWebsite">Website</label>
321
+ <input type="url" class="form-input" id="entryWebsite">
322
+ </div>
323
+ <div class="form-group">
324
+ <label class="form-label" for="entryNotes">Notes</label>
325
+ <textarea class="form-input" id="entryNotes" rows="3"></textarea>
326
+ </div>
327
+ <div style="display: flex; justify-content: space-between; align-items: center;">
328
+ <button type="button" class="btn btn-danger" id="deletePasswordBtn"
329
+ style="display: none;">Delete</button>
330
+ <div>
331
+ <button type="button" class="btn btn-secondary"
332
+ onclick="closePasswordEntryModal()">Cancel</button>
333
+ <button type="submit" class="btn" id="savePasswordBtn">Save Password</button>
334
+ </div>
335
+ </div>
336
+ </form>
337
+ </div>
338
+ </div>
339
+
340
+ <script>
341
+ window.currentUser = '{{ username }}';
342
+ </script>
343
+ <script src="{{ url_for('static', filename='script.js') }}"></script>
344
+ </body>
345
+
346
+ </html>