uciwild commited on
Commit
2a89258
·
verified ·
1 Parent(s): 17f8a38

app for monitoring redis database

Browse files
Files changed (2) hide show
  1. README.md +8 -5
  2. index.html +469 -18
README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
- title: Redisradar
3
- emoji: 🔥
4
- colorFrom: indigo
5
- colorTo: blue
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
  ---
2
+ title: RedisRadar 🔍
3
+ colorFrom: yellow
4
+ colorTo: yellow
5
+ emoji: 🐳
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite-v3
10
  ---
11
 
12
+ # Welcome to your new DeepSite project!
13
+ This project was created with [DeepSite](https://deepsite.hf.co).
index.html CHANGED
@@ -1,19 +1,470 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>RedisRadar - Real-time Redis Monitoring</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://unpkg.com/feather-icons"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.net.min.js"></script>
11
+ <script>
12
+ tailwind.config = {
13
+ theme: {
14
+ extend: {
15
+ colors: {
16
+ primary: '#d82c20', // Redis red
17
+ secondary: '#4f5b66' // Dark slate
18
+ }
19
+ }
20
+ }
21
+ }
22
+ </script>
23
+ <style>
24
+ .dashboard-card {
25
+ transition: all 0.3s ease;
26
+ }
27
+ .dashboard-card:hover {
28
+ transform: translateY(-5px);
29
+ box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1);
30
+ }
31
+ #vanta-background {
32
+ position: absolute;
33
+ top: 0;
34
+ left: 0;
35
+ width: 100%;
36
+ height: 100%;
37
+ z-index: -1;
38
+ opacity: 0.15;
39
+ }
40
+ </style>
41
+ </head>
42
+ <body class="bg-gray-50 min-h-screen">
43
+ <div id="vanta-background"></div>
44
+
45
+ <!-- Navigation -->
46
+ <nav class="bg-white shadow-sm">
47
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
48
+ <div class="flex justify-between h-16">
49
+ <div class="flex items-center">
50
+ <div class="flex-shrink-0 flex items-center">
51
+ <i data-feather="database" class="text-primary h-6 w-6"></i>
52
+ <span class="ml-2 text-xl font-bold text-primary">RedisRadar</span>
53
+ </div>
54
+ <div class="hidden sm:ml-6 sm:flex sm:space-x-8">
55
+ <a href="#" class="border-primary text-gray-900 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
56
+ Dashboard
57
+ </a>
58
+ <a href="#" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
59
+ Keys
60
+ </a>
61
+ <a href="#" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
62
+ Performance
63
+ </a>
64
+ <a href="#" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
65
+ Configuration
66
+ </a>
67
+ </div>
68
+ </div>
69
+ <div class="hidden sm:ml-6 sm:flex sm:items-center">
70
+ <button type="button" class="bg-white p-1 rounded-full text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary">
71
+ <i data-feather="bell" class="h-6 w-6"></i>
72
+ </button>
73
+ <div class="ml-3 relative">
74
+ <div>
75
+ <button type="button" class="bg-white rounded-full flex text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary" id="user-menu-button" aria-expanded="false" aria-haspopup="true">
76
+ <img class="h-8 w-8 rounded-full" src="http://static.photos/technology/200x200/42" alt="">
77
+ </button>
78
+ </div>
79
+ </div>
80
+ </div>
81
+ <div class="-mr-2 flex items-center sm:hidden">
82
+ <button type="button" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary" aria-controls="mobile-menu" aria-expanded="false">
83
+ <i data-feather="menu" class="block h-6 w-6"></i>
84
+ <i data-feather="x" class="hidden h-6 w-6"></i>
85
+ </button>
86
+ </div>
87
+ </div>
88
+ </div>
89
+
90
+ <!-- Mobile menu, show/hide based on menu state. -->
91
+ <div class="sm:hidden hidden" id="mobile-menu">
92
+ <div class="pt-2 pb-3 space-y-1">
93
+ <a href="#" class="bg-primary-50 border-primary text-primary block pl-3 pr-4 py-2 border-l-4 text-base font-medium">Dashboard</a>
94
+ <a href="#" class="border-transparent text-gray-500 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-700 block pl-3 pr-4 py-2 border-l-4 text-base font-medium">Keys</a>
95
+ <a href="#" class="border-transparent text-gray-500 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-700 block pl-3 pr-4 py-2 border-l-4 text-base font-medium">Performance</a>
96
+ <a href="#" class="border-transparent text-gray-500 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-700 block pl-3 pr-4 py-2 border-l-4 text-base font-medium">Configuration</a>
97
+ </div>
98
+ <div class="pt-4 pb-3 border-t border-gray-200">
99
+ <div class="flex items-center px-4">
100
+ <div class="flex-shrink-0">
101
+ <img class="h-10 w-10 rounded-full" src="http://static.photos/technology/200x200/42" alt="">
102
+ </div>
103
+ <div class="ml-3">
104
+ <div class="text-base font-medium text-gray-800">Admin User</div>
105
+ <div class="text-sm font-medium text-gray-500">admin@example.com</div>
106
+ </div>
107
+ <button type="button" class="ml-auto bg-white flex-shrink-0 p-1 rounded-full text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary">
108
+ <i data-feather="bell" class="h-6 w-6"></i>
109
+ </button>
110
+ </div>
111
+ </div>
112
+ </div>
113
+ </nav>
114
+
115
+ <!-- Main Content -->
116
+ <div class="py-6">
117
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
118
+ <h1 class="text-2xl font-semibold text-gray-900">Redis Dashboard</h1>
119
+ <div class="py-4">
120
+ <div class="border-b border-gray-200">
121
+ <nav class="-mb-px flex space-x-8">
122
+ <a href="#" class="border-primary text-primary whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm">
123
+ Overview
124
+ </a>
125
+ <a href="#" class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm">
126
+ Memory
127
+ </a>
128
+ <a href="#" class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm">
129
+ Clients
130
+ </a>
131
+ <a href="#" class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm">
132
+ Persistence
133
+ </a>
134
+ <a href="#" class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm">
135
+ Stats
136
+ </a>
137
+ </nav>
138
+ </div>
139
+ </div>
140
+
141
+ <!-- Stats Cards -->
142
+ <div class="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-4 mb-6">
143
+ <div class="bg-white overflow-hidden shadow rounded-lg dashboard-card">
144
+ <div class="px-4 py-5 sm:p-6">
145
+ <div class="flex items-center">
146
+ <div class="flex-shrink-0 bg-primary-100 p-3 rounded-md">
147
+ <i data-feather="activity" class="text-primary h-6 w-6"></i>
148
+ </div>
149
+ <div class="ml-5 w-0 flex-1">
150
+ <dl>
151
+ <dt class="text-sm font-medium text-gray-500 truncate">Connected Clients</dt>
152
+ <dd>
153
+ <div class="text-lg font-medium text-gray-900">127</div>
154
+ </dd>
155
+ </dl>
156
+ </div>
157
+ </div>
158
+ </div>
159
+ <div class="bg-gray-50 px-4 py-4 sm:px-6">
160
+ <div class="text-sm">
161
+ <a href="#" class="font-medium text-primary hover:text-primary-700"> View all<span class="sr-only"> Connected Clients stats</span></a>
162
+ </div>
163
+ </div>
164
+ </div>
165
+
166
+ <div class="bg-white overflow-hidden shadow rounded-lg dashboard-card">
167
+ <div class="px-4 py-5 sm:p-6">
168
+ <div class="flex items-center">
169
+ <div class="flex-shrink-0 bg-blue-100 p-3 rounded-md">
170
+ <i data-feather="database" class="text-blue-600 h-6 w-6"></i>
171
+ </div>
172
+ <div class="ml-5 w-0 flex-1">
173
+ <dl>
174
+ <dt class="text-sm font-medium text-gray-500 truncate">Total Keys</dt>
175
+ <dd>
176
+ <div class="text-lg font-medium text-gray-900">8,947</div>
177
+ </dd>
178
+ </dl>
179
+ </div>
180
+ </div>
181
+ </div>
182
+ <div class="bg-gray-50 px-4 py-4 sm:px-6">
183
+ <div class="text-sm">
184
+ <a href="#" class="font-medium text-blue-600 hover:text-blue-500"> View all<span class="sr-only"> Total Keys stats</span></a>
185
+ </div>
186
+ </div>
187
+ </div>
188
+
189
+ <div class="bg-white overflow-hidden shadow rounded-lg dashboard-card">
190
+ <div class="px-4 py-5 sm:p-6">
191
+ <div class="flex items-center">
192
+ <div class="flex-shrink-0 bg-green-100 p-3 rounded-md">
193
+ <i data-feather="cpu" class="text-green-600 h-6 w-6"></i>
194
+ </div>
195
+ <div class="ml-5 w-0 flex-1">
196
+ <dl>
197
+ <dt class="text-sm font-medium text-gray-500 truncate">Memory Usage</dt>
198
+ <dd>
199
+ <div class="text-lg font-medium text-gray-900">1.2GB</div>
200
+ </dd>
201
+ </dl>
202
+ </div>
203
+ </div>
204
+ </div>
205
+ <div class="bg-gray-50 px-4 py-4 sm:px-6">
206
+ <div class="text-sm">
207
+ <a href="#" class="font-medium text-green-600 hover:text-green-500"> View details<span class="sr-only"> Memory Usage stats</span></a>
208
+ </div>
209
+ </div>
210
+ </div>
211
+
212
+ <div class="bg-white overflow-hidden shadow rounded-lg dashboard-card">
213
+ <div class="px-4 py-5 sm:p-6">
214
+ <div class="flex items-center">
215
+ <div class="flex-shrink-0 bg-purple-100 p-3 rounded-md">
216
+ <i data-feather="zap" class="text-purple-600 h-6 w-6"></i>
217
+ </div>
218
+ <div class="ml-5 w-0 flex-1">
219
+ <dl>
220
+ <dt class="text-sm font-medium text-gray-500 truncate">Ops/sec</dt>
221
+ <dd>
222
+ <div class="text-lg font-medium text-gray-900">2,349</div>
223
+ </dd>
224
+ </dl>
225
+ </div>
226
+ </div>
227
+ </div>
228
+ <div class="bg-gray-50 px-4 py-4 sm:px-6">
229
+ <div class="text-sm">
230
+ <a href="#" class="font-medium text-purple-600 hover:text-purple-500"> View performance<span class="sr-only"> Ops/sec stats</span></a>
231
+ </div>
232
+ </div>
233
+ </div>
234
+ </div>
235
+
236
+ <!-- Charts Section -->
237
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
238
+ <!-- Memory Usage Chart -->
239
+ <div class="bg-white shadow rounded-lg p-6 dashboard-card">
240
+ <h2 class="text-lg leading-6 font-medium text-gray-900 mb-4">Memory Usage</h2>
241
+ <div class="h-64">
242
+ <canvas id="memoryChart"></canvas>
243
+ </div>
244
+ </div>
245
+
246
+ <!-- Commands Per Second Chart -->
247
+ <div class="bg-white shadow rounded-lg p-6 dashboard-card">
248
+ <h2 class="text-lg leading-6 font-medium text-gray-900 mb-4">Commands Per Second</h2>
249
+ <div class="h-64">
250
+ <canvas id="commandsChart"></canvas>
251
+ </div>
252
+ </div>
253
+ </div>
254
+
255
+ <!-- Recent Activity -->
256
+ <div class="bg-white shadow rounded-lg overflow-hidden dashboard-card">
257
+ <div class="px-4 py-5 sm:px-6 border-b border-gray-200">
258
+ <h3 class="text-lg leading-6 font-medium text-gray-900">Recent Activity</h3>
259
+ </div>
260
+ <div class="bg-white overflow-hidden">
261
+ <ul class="divide-y divide-gray-200">
262
+ <li class="px-6 py-4">
263
+ <div class="flex items-center">
264
+ <div class="flex-shrink-0">
265
+ <i data-feather="activity" class="h-5 w-5 text-green-500"></i>
266
+ </div>
267
+ <div class="ml-4">
268
+ <p class="text-sm font-medium text-gray-900">New client connected</p>
269
+ <p class="text-sm text-gray-500">Client ID: 128 from 192.168.1.42</p>
270
+ </div>
271
+ <div class="ml-auto text-sm text-gray-500">
272
+ 2m ago
273
+ </div>
274
+ </div>
275
+ </li>
276
+ <li class="px-6 py-4">
277
+ <div class="flex items-center">
278
+ <div class="flex-shrink-0">
279
+ <i data-feather="database" class="h-5 w-5 text-blue-500"></i>
280
+ </div>
281
+ <div class="ml-4">
282
+ <p class="text-sm font-medium text-gray-900">Key expiration</p>
283
+ <p class="text-sm text-gray-500">Key: user:session:42 expired</p>
284
+ </div>
285
+ <div class="ml-auto text-sm text-gray-500">
286
+ 5m ago
287
+ </div>
288
+ </div>
289
+ </li>
290
+ <li class="px-6 py-4">
291
+ <div class="flex items-center">
292
+ <div class="flex-shrink-0">
293
+ <i data-feather="alert-triangle" class="h-5 w-5 text-yellow-500"></i>
294
+ </div>
295
+ <div class="ml-4">
296
+ <p class="text-sm font-medium text-gray-900">Memory threshold reached</p>
297
+ <p class="text-sm text-gray-500">85% of maxmemory used</p>
298
+ </div>
299
+ <div class="ml-auto text-sm text-gray-500">
300
+ 12m ago
301
+ </div>
302
+ </div>
303
+ </li>
304
+ <li class="px-6 py-4">
305
+ <div class="flex items-center">
306
+ <div class="flex-shrink-0">
307
+ <i data-feather="zap" class="h-5 w-5 text-purple-500"></i>
308
+ </div>
309
+ <div class="ml-4">
310
+ <p class="text-sm font-medium text-gray-900">Performance spike</p>
311
+ <p class="text-sm text-gray-500">5,231 ops/sec detected</p>
312
+ </div>
313
+ <div class="ml-auto text-sm text-gray-500">
314
+ 23m ago
315
+ </div>
316
+ </div>
317
+ </li>
318
+ </ul>
319
+ </div>
320
+ </div>
321
+ </div>
322
+ </div>
323
+
324
+ <script>
325
+ // Initialize Vanta.js background
326
+ VANTA.NET({
327
+ el: "#vanta-background",
328
+ mouseControls: true,
329
+ touchControls: true,
330
+ gyroControls: false,
331
+ minHeight: 200.00,
332
+ minWidth: 200.00,
333
+ scale: 1.00,
334
+ scaleMobile: 1.00,
335
+ color: 0xd82c20,
336
+ backgroundColor: 0xf8fafc,
337
+ points: 10.00,
338
+ maxDistance: 20.00,
339
+ spacing: 15.00
340
+ });
341
+
342
+ // Initialize charts
343
+ document.addEventListener('DOMContentLoaded', function() {
344
+ feather.replace();
345
+
346
+ // Memory Usage Chart
347
+ const memoryCtx = document.getElementById('memoryChart').getContext('2d');
348
+ const memoryChart = new Chart(memoryCtx, {
349
+ type: 'line',
350
+ data: {
351
+ labels: ['00:00', '03:00', '06:00', '09:00', '12:00', '15:00', '18:00', '21:00'],
352
+ datasets: [{
353
+ label: 'Used Memory',
354
+ data: [645, 892, 1123, 1456, 1234, 1567, 1890, 2100],
355
+ backgroundColor: 'rgba(216, 44, 32, 0.1)',
356
+ borderColor: '#d82c20',
357
+ borderWidth: 2,
358
+ tension: 0.1,
359
+ fill: true
360
+ }, {
361
+ label: 'Max Memory',
362
+ data: [2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500],
363
+ backgroundColor: 'rgba(75, 85, 99, 0.05)',
364
+ borderColor: 'rgba(75, 85, 99, 0.5)',
365
+ borderWidth: 1,
366
+ borderDash: [5, 5],
367
+ fill: true
368
+ }]
369
+ },
370
+ options: {
371
+ responsive: true,
372
+ maintainAspectRatio: false,
373
+ plugins: {
374
+ legend: {
375
+ position: 'top',
376
+ },
377
+ tooltip: {
378
+ mode: 'index',
379
+ intersect: false,
380
+ }
381
+ },
382
+ scales: {
383
+ y: {
384
+ beginAtZero: true,
385
+ title: {
386
+ display: true,
387
+ text: 'Memory (MB)'
388
+ }
389
+ }
390
+ }
391
+ }
392
+ });
393
+
394
+ // Commands Per Second Chart
395
+ const commandsCtx = document.getElementById('commandsChart').getContext('2d');
396
+ const commandsChart = new Chart(commandsCtx, {
397
+ type: 'bar',
398
+ data: {
399
+ labels: ['GET', 'SET', 'DEL', 'INCR', 'DECR', 'LPUSH', 'RPUSH', 'LPOP', 'RPOP'],
400
+ datasets: [{
401
+ label: 'Commands Per Second',
402
+ data: [1200, 800, 200, 150, 100, 300, 250, 180, 160],
403
+ backgroundColor: [
404
+ 'rgba(216, 44, 32, 0.7)',
405
+ 'rgba(216, 44, 32, 0.6)',
406
+ 'rgba(216, 44, 32, 0.5)',
407
+ 'rgba(216, 44, 32, 0.4)',
408
+ 'rgba(216, 44, 32, 0.3)',
409
+ 'rgba(216, 44, 32, 0.4)',
410
+ 'rgba(216, 44, 32, 0.5)',
411
+ 'rgba(216, 44, 32, 0.6)',
412
+ 'rgba(216, 44, 32, 0.7)'
413
+ ],
414
+ borderColor: [
415
+ 'rgba(216, 44, 32, 1)',
416
+ 'rgba(216, 44, 32, 1)',
417
+ 'rgba(216, 44, 32, 1)',
418
+ 'rgba(216, 44, 32, 1)',
419
+ 'rgba(216, 44, 32, 1)',
420
+ 'rgba(216, 44, 32, 1)',
421
+ 'rgba(216, 44, 32, 1)',
422
+ 'rgba(216, 44, 32, 1)',
423
+ 'rgba(216, 44, 32, 1)'
424
+ ],
425
+ borderWidth: 1
426
+ }]
427
+ },
428
+ options: {
429
+ responsive: true,
430
+ maintainAspectRatio: false,
431
+ plugins: {
432
+ legend: {
433
+ display: false
434
+ },
435
+ tooltip: {
436
+ mode: 'index',
437
+ intersect: false,
438
+ }
439
+ },
440
+ scales: {
441
+ y: {
442
+ beginAtZero: true,
443
+ title: {
444
+ display: true,
445
+ text: 'Commands/sec'
446
+ }
447
+ }
448
+ }
449
+ }
450
+ });
451
+
452
+ // Mobile menu toggle
453
+ const mobileMenuButton = document.querySelector('[aria-controls="mobile-menu"]');
454
+ const mobileMenu = document.getElementById('mobile-menu');
455
+
456
+ mobileMenuButton.addEventListener('click', function() {
457
+ const expanded = this.getAttribute('aria-expanded') === 'true';
458
+ this.setAttribute('aria-expanded', !expanded);
459
+ mobileMenu.classList.toggle('hidden');
460
+
461
+ // Toggle menu icons
462
+ const menuIcon = this.querySelector('i:first-child');
463
+ const closeIcon = this.querySelector('i:last-child');
464
+ menuIcon.classList.toggle('hidden');
465
+ closeIcon.classList.toggle('hidden');
466
+ });
467
+ });
468
+ </script>
469
+ </body>
470
  </html>