arevedudaa commited on
Commit
2b009c3
Β·
verified Β·
1 Parent(s): a2483df

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +471 -405
templates/index.html CHANGED
@@ -1,406 +1,472 @@
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>URL Shortener</title>
7
- <script src="https://cdn.tailwindcss.com"></script>
8
- <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
9
- <style>
10
- :root {
11
- --bg-primary: #000000;
12
- --text-primary: #ffffff;
13
- --text-secondary: #888888;
14
- --accent: #FF3B30;
15
- }
16
-
17
- body {
18
- font-family: 'Roboto', sans-serif;
19
- background-color: var(--bg-primary);
20
- color: var(--text-primary);
21
- min-height: 100vh;
22
- overflow-x: hidden;
23
- }
24
-
25
- .input-container {
26
- transition: transform 0.3s ease-out;
27
- }
28
-
29
- .input-container.focused {
30
- transform: translateY(-60vh);
31
- }
32
-
33
- .history-item {
34
- transition: all 0.3s ease;
35
- }
36
-
37
- .history-item:hover {
38
- background: rgba(255, 255, 255, 0.05);
39
- }
40
-
41
- @keyframes glowAnimation {
42
- 0% { box-shadow: 0 0 0 rgba(255, 59, 48, 0); }
43
- 50% { box-shadow: 0 0 20px rgba(255, 59, 48, 0.3); }
44
- 100% { box-shadow: 0 0 0 rgba(255, 59, 48, 0); }
45
- }
46
-
47
- .success-animation {
48
- animation: glowAnimation 1s ease-out;
49
- }
50
-
51
- #developerInfo {
52
- transition: all 0.3s ease-out;
53
- }
54
-
55
- #developerInfo > div {
56
- transition: all 0.3s ease-out;
57
- }
58
-
59
- .social-link {
60
- transition: all 0.2s ease;
61
- }
62
-
63
- .social-link:hover {
64
- transform: translateY(-2px);
65
- }
66
- </style>
67
- </head>
68
- <body class="p-0 m-0">
69
- <!-- Time and Location -->
70
- <div class="fixed top-0 left-0 right-0 p-6 z-10">
71
- <div class="text-2xl font-light" id="time"></div>
72
- <div class="text-sm text-gray-400" id="date"></div>
73
- </div>
74
-
75
- <!-- History Section -->
76
- <div class="pt-24 px-6 pb-32">
77
- <div class="text-xs uppercase tracking-wider text-gray-500 mb-4">Recent Links</div>
78
- <div id="history" class="space-y-4">
79
- <!-- History items will be added here -->
80
- </div>
81
- </div>
82
-
83
- <!-- Bottom Input Section -->
84
- <div id="inputContainer" class="input-container fixed bottom-0 left-0 right-0 p-6 bg-black">
85
- <form id="urlForm" class="space-y-4">
86
- <div class="relative">
87
- <input type="text" id="url"
88
- class="w-full bg-white/10 text-white px-4 py-3 rounded-2xl border-0
89
- focus:outline-none focus:ring-2 focus:ring-red-500/30
90
- placeholder-gray-500 text-base"
91
- placeholder="Paste your URL here"
92
- autocomplete="off">
93
- </div>
94
-
95
- <button type="submit"
96
- class="w-full bg-white/10 text-white py-4 rounded-2xl font-medium
97
- active:scale-[0.98] transition-all duration-300
98
- hover:bg-red-500/20 focus:outline-none">
99
- Shorten URL
100
- </button>
101
- </form>
102
-
103
- <!-- Result Section -->
104
- <div id="result" class="mt-4 hidden">
105
- <div class="bg-white/5 rounded-2xl p-4">
106
- <div class="flex items-center justify-between">
107
- <input type="text" id="shortUrl" readonly
108
- class="flex-1 bg-transparent border-none focus:outline-none
109
- text-white font-light">
110
- <button onclick="copyToClipboard()"
111
- class="ml-2 p-2 rounded-xl bg-white/5 text-white/80 hover:bg-white/10
112
- transition-colors duration-200">
113
- Copy
114
- </button>
115
- </div>
116
- </div>
117
- </div>
118
- </div>
119
-
120
- <!-- Developer Info Popup -->
121
- <div id="developerInfo" class="fixed inset-0 z-50 flex items-center justify-center px-4 bg-black/80 backdrop-blur-sm">
122
- <div class="bg-white/10 rounded-3xl p-6 max-w-md w-full backdrop-blur-md border border-white/10">
123
- <div class="flex justify-between items-start mb-6">
124
- <div class="text-xl font-light">About Developer</div>
125
- <button onclick="closeDeveloperInfo()"
126
- class="text-white/60 hover:text-white transition-colors">
127
- <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
128
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
129
- </svg>
130
- </button>
131
- </div>
132
-
133
- <div class="space-y-4">
134
- <p class="text-gray-300 leading-relaxed">
135
- Aditya Devarshi is an AI Engineer passionate about creating innovative projects
136
- and continuous learning. This URL shortener is one of his many projects
137
- showcasing modern design principles and functionality.
138
- </p>
139
-
140
- <div class="space-y-3 mt-6">
141
- <div class="text-sm text-gray-400 uppercase tracking-wider">Connect & Follow</div>
142
- <div class="grid grid-cols-2 gap-3">
143
- <a href="https://www.adityadevarshi.online/" target="_blank" rel="noopener"
144
- class="flex items-center gap-2 px-4 py-3 rounded-xl bg-white/5 hover:bg-white/10
145
- transition-colors text-sm text-white/80 hover:text-white">
146
- <span>🌐</span>
147
- <span>Portfolio</span>
148
- </a>
149
- <a href="https://www.linkedin.com/in/aditya-devarshi/" target="_blank" rel="noopener"
150
- class="flex items-center gap-2 px-4 py-3 rounded-xl bg-white/5 hover:bg-white/10
151
- transition-colors text-sm text-white/80 hover:text-white">
152
- <span>πŸ’Ό</span>
153
- <span>LinkedIn</span>
154
- </a>
155
- <a href="https://github.com/devarshiadi/" target="_blank" rel="noopener"
156
- class="flex items-center gap-2 px-4 py-3 rounded-xl bg-white/5 hover:bg-white/10
157
- transition-colors text-sm text-white/80 hover:text-white">
158
- <span>πŸ“¦</span>
159
- <span>GitHub</span>
160
- </a>
161
- <a href="https://medium.com/@devarshia5" target="_blank" rel="noopener"
162
- class="flex items-center gap-2 px-4 py-3 rounded-xl bg-white/5 hover:bg-white/10
163
- transition-colors text-sm text-white/80 hover:text-white">
164
- <span>πŸ“</span>
165
- <span>Medium</span>
166
- </a>
167
- </div>
168
- </div>
169
-
170
- <div class="mt-6 text-center">
171
- <button onclick="closeDeveloperInfo()"
172
- class="px-6 py-2 rounded-xl bg-white/10 text-white/80 hover:bg-white/20
173
- transition-all duration-300 text-sm">
174
- Close
175
- </button>
176
- </div>
177
- </div>
178
- </div>
179
- </div>
180
-
181
- <script>
182
- // Initialize localStorage history
183
- let urlHistory = JSON.parse(localStorage.getItem('urlHistory') || '[]');
184
- updateHistoryDisplay(); // Show saved history on load
185
-
186
- // Time and Date Update
187
- function updateDateTime() {
188
- const now = new Date();
189
- const timeString = now.toLocaleTimeString('en-US', {
190
- hour12: false,
191
- hour: '2-digit',
192
- minute: '2-digit'
193
- });
194
- const dateString = now.toLocaleDateString('en-US', {
195
- weekday: 'short',
196
- month: 'short',
197
- day: 'numeric'
198
- });
199
-
200
- document.getElementById('time').textContent = timeString;
201
- document.getElementById('date').textContent = dateString;
202
- }
203
-
204
- updateDateTime();
205
- setInterval(updateDateTime, 1000);
206
-
207
- // Input Focus Behavior with improved UX
208
- const inputContainer = document.getElementById('inputContainer');
209
- const urlInput = document.getElementById('url');
210
- const historySection = document.querySelector('.pt-24');
211
-
212
- urlInput.addEventListener('focus', () => {
213
- inputContainer.classList.add('focused');
214
- historySection.style.opacity = '0.3';
215
- navigator.vibrate(1);
216
- });
217
-
218
- document.addEventListener('click', (e) => {
219
- if (!inputContainer.contains(e.target)) {
220
- inputContainer.classList.remove('focused');
221
- historySection.style.opacity = '1';
222
- }
223
- });
224
-
225
- // Clear input on successful submission
226
- function resetForm() {
227
- urlInput.value = '';
228
- document.getElementById('result').classList.add('hidden');
229
- }
230
-
231
- // Form Submission with improved error handling
232
- document.getElementById('urlForm').addEventListener('submit', async (e) => {
233
- e.preventDefault();
234
- const url = urlInput.value.trim();
235
-
236
- if (!url) {
237
- showNotification('Please enter a URL', 'error');
238
- return;
239
- }
240
-
241
- if (!url.startsWith('http://') && !url.startsWith('https://')) {
242
- showNotification('Please enter a valid URL starting with http:// or https://', 'error');
243
- return;
244
- }
245
-
246
- const submitButton = e.target.querySelector('button');
247
- submitButton.disabled = true;
248
- submitButton.innerHTML = '<span class="inline-block animate-pulse">Shortening...</span>';
249
-
250
- try {
251
- const response = await fetch('/shorten/', {
252
- method: 'POST',
253
- headers: { 'Content-Type': 'application/json' },
254
- body: JSON.stringify({ original_url: url })
255
- });
256
-
257
- const data = await response.json();
258
- if (response.ok) {
259
- document.getElementById('result').classList.remove('hidden');
260
- document.getElementById('shortUrl').value = data.shortened_url;
261
-
262
- // Add to history and save to localStorage
263
- addToHistory(url, data.shortened_url);
264
-
265
- // Success animation and feedback
266
- inputContainer.classList.add('success-animation');
267
- setTimeout(() => inputContainer.classList.remove('success-animation'), 1000);
268
- navigator.vibrate([50, 50, 50]);
269
-
270
- // Clear input after short delay
271
- setTimeout(resetForm, 3000);
272
- } else {
273
- showNotification(data.detail || 'Error shortening URL', 'error');
274
- }
275
- } catch (error) {
276
- showNotification('Network error occurred', 'error');
277
- } finally {
278
- submitButton.disabled = false;
279
- submitButton.textContent = 'Shorten URL';
280
- }
281
- });
282
-
283
- function addToHistory(originalUrl, shortUrl) {
284
- const historyItem = {
285
- original: originalUrl,
286
- shortened: shortUrl,
287
- timestamp: new Date().toISOString()
288
- };
289
-
290
- urlHistory.unshift(historyItem);
291
- if (urlHistory.length > 10) urlHistory.pop(); // Keep last 10 items
292
-
293
- // Save to localStorage
294
- localStorage.setItem('urlHistory', JSON.stringify(urlHistory));
295
- updateHistoryDisplay();
296
- }
297
-
298
- function updateHistoryDisplay() {
299
- const historyContainer = document.getElementById('history');
300
- if (urlHistory.length === 0) {
301
- historyContainer.innerHTML = `
302
- <div class="text-center text-gray-500 py-8">
303
- No shortened URLs yet
304
- </div>
305
- `;
306
- return;
307
- }
308
-
309
- historyContainer.innerHTML = urlHistory.map(item => `
310
- <div class="history-item rounded-xl p-4 bg-white/5 backdrop-blur-sm">
311
- <div class="flex justify-between items-start">
312
- <div class="text-sm font-light text-gray-400 truncate flex-1">
313
- ${item.original}
314
- </div>
315
- <div class="text-xs text-gray-500 ml-2">
316
- ${new Date(item.timestamp).toLocaleDateString()}
317
- </div>
318
- </div>
319
- <div class="flex justify-between items-center mt-2">
320
- <div class="text-white truncate flex-1">${item.shortened}</div>
321
- <div class="flex gap-2">
322
- <button onclick="copyUrl('${item.shortened}')"
323
- class="text-xs text-white/60 hover:text-white px-3 py-1 rounded-lg bg-white/10 transition-colors">
324
- Copy
325
- </button>
326
- <button onclick="deleteHistoryItem('${item.timestamp}')"
327
- class="text-xs text-red-400/60 hover:text-red-400 px-3 py-1 rounded-lg bg-red-500/10 transition-colors">
328
- Delete
329
- </button>
330
- </div>
331
- </div>
332
- </div>
333
- `).join('');
334
- }
335
-
336
- function deleteHistoryItem(timestamp) {
337
- urlHistory = urlHistory.filter(item => item.timestamp !== timestamp);
338
- localStorage.setItem('urlHistory', JSON.stringify(urlHistory));
339
- updateHistoryDisplay();
340
- navigator.vibrate(50);
341
- }
342
-
343
- function copyUrl(url) {
344
- navigator.clipboard.writeText(url).then(() => {
345
- showNotification('Copied to clipboard', 'success');
346
- navigator.vibrate(50);
347
- });
348
- }
349
-
350
- function copyToClipboard() {
351
- const shortUrl = document.getElementById('shortUrl');
352
- navigator.clipboard.writeText(shortUrl.value).then(() => {
353
- showNotification('Copied to clipboard', 'success');
354
- navigator.vibrate(50);
355
- });
356
- }
357
-
358
- function showNotification(message, type) {
359
- const notification = document.createElement('div');
360
- notification.className = `fixed bottom-32 left-1/2 transform -translate-x-1/2
361
- px-6 py-3 rounded-2xl text-white text-center text-sm
362
- ${type === 'error' ? 'bg-red-500/90' : 'bg-white/10'}
363
- opacity-0 transition-opacity duration-300 z-50
364
- backdrop-blur-sm shadow-lg`;
365
- notification.textContent = message;
366
-
367
- document.body.appendChild(notification);
368
- requestAnimationFrame(() => notification.classList.add('opacity-100'));
369
- setTimeout(() => {
370
- notification.classList.remove('opacity-100');
371
- setTimeout(() => notification.remove(), 300);
372
- }, 2000);
373
- }
374
-
375
- function closeDeveloperInfo() {
376
- const popup = document.getElementById('developerInfo');
377
- popup.style.opacity = '0';
378
- popup.style.transform = 'scale(0.95)';
379
- setTimeout(() => {
380
- popup.style.display = 'none';
381
- }, 300);
382
-
383
- // Save to localStorage so it doesn't show again in this session
384
- localStorage.setItem('developerInfoShown', 'true');
385
- }
386
-
387
- // Check if we should show the popup
388
- window.addEventListener('DOMContentLoaded', () => {
389
- const popup = document.getElementById('developerInfo');
390
- if (!localStorage.getItem('developerInfoShown')) {
391
- popup.style.display = 'flex';
392
- popup.style.opacity = '0';
393
- popup.style.transform = 'scale(0.95)';
394
-
395
- // Trigger animation
396
- setTimeout(() => {
397
- popup.style.opacity = '1';
398
- popup.style.transform = 'scale(1)';
399
- }, 100);
400
- } else {
401
- popup.style.display = 'none';
402
- }
403
- });
404
- </script>
405
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
406
  </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>URL Shortener</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
9
+ <style>
10
+ :root {
11
+ --bg-primary: #000000;
12
+ --text-primary: #ffffff;
13
+ --text-secondary: #888888;
14
+ --accent: #FF3B30;
15
+ }
16
+
17
+ body {
18
+ font-family: 'Roboto', sans-serif;
19
+ background-color: var(--bg-primary);
20
+ color: var(--text-primary);
21
+ min-height: 100vh;
22
+ overflow-x: hidden;
23
+ }
24
+
25
+ .input-container {
26
+ transition: transform 0.3s ease-out;
27
+ }
28
+
29
+ .input-container.focused {
30
+ transform: translateY(-60vh);
31
+ }
32
+
33
+ .history-item {
34
+ transition: all 0.3s ease;
35
+ }
36
+
37
+ .history-item:hover {
38
+ background: rgba(255, 255, 255, 0.05);
39
+ }
40
+
41
+ @keyframes glowAnimation {
42
+ 0% { box-shadow: 0 0 0 rgba(255, 59, 48, 0); }
43
+ 50% { box-shadow: 0 0 20px rgba(255, 59, 48, 0.3); }
44
+ 100% { box-shadow: 0 0 0 rgba(255, 59, 48, 0); }
45
+ }
46
+
47
+ .success-animation {
48
+ animation: glowAnimation 1s ease-out;
49
+ }
50
+
51
+ #developerInfo {
52
+ transition: all 0.3s ease-out;
53
+ }
54
+
55
+ #developerInfo > div {
56
+ transition: all 0.3s ease-out;
57
+ }
58
+
59
+ .social-link {
60
+ transition: all 0.2s ease;
61
+ }
62
+
63
+ .social-link:hover {
64
+ transform: translateY(-2px);
65
+ }
66
+ </style>
67
+ </head>
68
+ <body class="p-0 m-0">
69
+ <!-- Time and Location -->
70
+ <div class="fixed top-0 left-0 right-0 p-6 z-10">
71
+ <div class="text-2xl font-light" id="time"></div>
72
+ <div class="text-sm text-gray-400" id="date"></div>
73
+ </div>
74
+
75
+ <!-- History Section -->
76
+ <div class="pt-24 px-6 pb-32">
77
+ <div class="text-xs uppercase tracking-wider text-gray-500 mb-4">Recent Links</div>
78
+ <div id="history" class="space-y-4">
79
+ <!-- History items will be added here -->
80
+ </div>
81
+ </div>
82
+
83
+ <!-- Bottom Input Section -->
84
+ <div id="inputContainer" class="input-container fixed bottom-0 left-0 right-0 p-6 bg-black">
85
+ <form id="urlForm" class="space-y-4">
86
+ <div class="relative">
87
+ <input type="text" id="url"
88
+ class="w-full bg-white/10 text-white px-4 py-3 rounded-2xl border-0
89
+ focus:outline-none focus:ring-2 focus:ring-red-500/30
90
+ placeholder-gray-500 text-base"
91
+ placeholder="Paste your URL here"
92
+ autocomplete="off">
93
+ </div>
94
+
95
+ <button type="submit"
96
+ class="w-full bg-white/10 text-white py-4 rounded-2xl font-medium
97
+ active:scale-[0.98] transition-all duration-300
98
+ hover:bg-red-500/20 focus:outline-none">
99
+ Shorten URL
100
+ </button>
101
+ </form>
102
+
103
+ <!-- Result Section -->
104
+ <div id="result" class="mt-4 hidden">
105
+ <div class="bg-white/5 rounded-2xl p-4">
106
+ <div class="flex items-center justify-between">
107
+ <input type="text" id="shortUrl" readonly
108
+ class="flex-1 bg-transparent border-none focus:outline-none
109
+ text-white font-light">
110
+ <button onclick="copyToClipboard()"
111
+ class="ml-2 p-2 rounded-xl bg-white/5 text-white/80 hover:bg-white/10
112
+ transition-colors duration-200">
113
+ Copy
114
+ </button>
115
+ </div>
116
+ </div>
117
+ </div>
118
+ </div>
119
+
120
+ <!-- Developer Info Popup -->
121
+ <div id="developerInfo" class="fixed inset-0 z-50 flex items-center justify-center px-4 bg-black/80 backdrop-blur-sm">
122
+ <div class="bg-white/10 rounded-3xl p-6 max-w-md w-full backdrop-blur-md border border-white/10">
123
+ <div class="flex justify-between items-start mb-6">
124
+ <div class="text-xl font-light">About Developer</div>
125
+ <button onclick="closeDeveloperInfo()"
126
+ class="text-white/60 hover:text-white transition-colors">
127
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
128
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
129
+ </svg>
130
+ </button>
131
+ </div>
132
+
133
+ <div class="space-y-4">
134
+ <p class="text-gray-300 leading-relaxed">
135
+ Aditya Devarshi is an AI Engineer passionate about creating innovative projects
136
+ and continuous learning. This URL shortener is one of his many projects
137
+ showcasing modern design principles and functionality.
138
+ </p>
139
+
140
+ <div class="space-y-3 mt-6">
141
+ <div class="text-sm text-gray-400 uppercase tracking-wider">Connect & Follow</div>
142
+ <div class="grid grid-cols-2 gap-3">
143
+ <a href="https://www.adityadevarshi.online/" target="_blank" rel="noopener"
144
+ class="flex items-center gap-2 px-4 py-3 rounded-xl bg-white/5 hover:bg-white/10
145
+ transition-colors text-sm text-white/80 hover:text-white">
146
+ <span>🌐</span>
147
+ <span>Portfolio</span>
148
+ </a>
149
+ <a href="https://www.linkedin.com/in/aditya-devarshi/" target="_blank" rel="noopener"
150
+ class="flex items-center gap-2 px-4 py-3 rounded-xl bg-white/5 hover:bg-white/10
151
+ transition-colors text-sm text-white/80 hover:text-white">
152
+ <span>πŸ’Ό</span>
153
+ <span>LinkedIn</span>
154
+ </a>
155
+ <a href="https://github.com/devarshiadi/" target="_blank" rel="noopener"
156
+ class="flex items-center gap-2 px-4 py-3 rounded-xl bg-white/5 hover:bg-white/10
157
+ transition-colors text-sm text-white/80 hover:text-white">
158
+ <span>πŸ“¦</span>
159
+ <span>GitHub</span>
160
+ </a>
161
+ <a href="https://medium.com/@devarshia5" target="_blank" rel="noopener"
162
+ class="flex items-center gap-2 px-4 py-3 rounded-xl bg-white/5 hover:bg-white/10
163
+ transition-colors text-sm text-white/80 hover:text-white">
164
+ <span>πŸ“</span>
165
+ <span>Medium</span>
166
+ </a>
167
+ </div>
168
+ </div>
169
+
170
+ <div class="mt-6 text-center">
171
+ <button onclick="closeDeveloperInfo()"
172
+ class="px-6 py-2 rounded-xl bg-white/10 text-white/80 hover:bg-white/20
173
+ transition-all duration-300 text-sm">
174
+ Close
175
+ </button>
176
+ </div>
177
+ </div>
178
+ </div>
179
+ </div>
180
+
181
+ <script>
182
+ // Initialize localStorage history
183
+ let urlHistory = JSON.parse(localStorage.getItem('urlHistory') || '[]');
184
+ updateHistoryDisplay(); // Show saved history on load
185
+
186
+ // Time and Date Update
187
+ function updateDateTime() {
188
+ const now = new Date();
189
+ const timeString = now.toLocaleTimeString('en-US', {
190
+ hour12: false,
191
+ hour: '2-digit',
192
+ minute: '2-digit'
193
+ });
194
+ const dateString = now.toLocaleDateString('en-US', {
195
+ weekday: 'short',
196
+ month: 'short',
197
+ day: 'numeric'
198
+ });
199
+
200
+ document.getElementById('time').textContent = timeString;
201
+ document.getElementById('date').textContent = dateString;
202
+ }
203
+
204
+ updateDateTime();
205
+ setInterval(updateDateTime, 1000);
206
+
207
+ // Input Focus Behavior with improved UX
208
+ const inputContainer = document.getElementById('inputContainer');
209
+ const urlInput = document.getElementById('url');
210
+ const historySection = document.querySelector('.pt-24');
211
+
212
+ urlInput.addEventListener('focus', () => {
213
+ inputContainer.classList.add('focused');
214
+ historySection.style.opacity = '0.3';
215
+ navigator.vibrate(1);
216
+ });
217
+
218
+ document.addEventListener('click', (e) => {
219
+ if (!inputContainer.contains(e.target)) {
220
+ inputContainer.classList.remove('focused');
221
+ historySection.style.opacity = '1';
222
+ }
223
+ });
224
+
225
+ // Clear input on successful submission
226
+ function resetForm() {
227
+ urlInput.value = '';
228
+ document.getElementById('result').classList.add('hidden');
229
+ }
230
+
231
+ // Form Submission with improved error handling
232
+ document.getElementById('urlForm').addEventListener('submit', async (e) => {
233
+ e.preventDefault();
234
+ const url = urlInput.value.trim();
235
+
236
+ if (!url) {
237
+ showNotification('Please enter a URL', 'error');
238
+ return;
239
+ }
240
+
241
+ if (!url.startsWith('http://') && !url.startsWith('https://')) {
242
+ showNotification('Please enter a valid URL starting with http:// or https://', 'error');
243
+ return;
244
+ }
245
+
246
+ const submitButton = e.target.querySelector('button');
247
+ submitButton.disabled = true;
248
+ submitButton.innerHTML = '<span class="inline-block animate-pulse">Shortening...</span>';
249
+
250
+ try {
251
+ const response = await fetch('/shorten/', {
252
+ method: 'POST',
253
+ headers: { 'Content-Type': 'application/json' },
254
+ body: JSON.stringify({ original_url: url })
255
+ });
256
+
257
+ const data = await response.json();
258
+ if (response.ok) {
259
+ document.getElementById('result').classList.remove('hidden');
260
+ document.getElementById('shortUrl').value = data.shortened_url;
261
+
262
+ // Add to history and save to localStorage
263
+ addToHistory(url, data.shortened_url);
264
+
265
+ // Success animation and feedback
266
+ inputContainer.classList.add('success-animation');
267
+ setTimeout(() => inputContainer.classList.remove('success-animation'), 1000);
268
+ navigator.vibrate([50, 50, 50]);
269
+
270
+ // Clear input after short delay
271
+ setTimeout(resetForm, 3000);
272
+ } else {
273
+ showNotification(data.detail || 'Error shortening URL', 'error');
274
+ }
275
+ } catch (error) {
276
+ showNotification('Network error occurred', 'error');
277
+ } finally {
278
+ submitButton.disabled = false;
279
+ submitButton.textContent = 'Shorten URL';
280
+ }
281
+ });
282
+
283
+ function addToHistory(originalUrl, shortUrl) {
284
+ const historyItem = {
285
+ original: originalUrl,
286
+ shortened: shortUrl,
287
+ timestamp: new Date().toISOString()
288
+ };
289
+
290
+ urlHistory.unshift(historyItem);
291
+ if (urlHistory.length > 10) urlHistory.pop(); // Keep last 10 items
292
+
293
+ // Save to localStorage
294
+ localStorage.setItem('urlHistory', JSON.stringify(urlHistory));
295
+ updateHistoryDisplay();
296
+ }
297
+
298
+ function updateHistoryDisplay() {
299
+ const historyContainer = document.getElementById('history');
300
+ if (urlHistory.length === 0) {
301
+ historyContainer.innerHTML = `
302
+ <div class="text-center text-gray-500 py-8">
303
+ No shortened URLs yet
304
+ </div>
305
+ `;
306
+ return;
307
+ }
308
+
309
+ historyContainer.innerHTML = urlHistory.map(item => `
310
+ <div class="history-item rounded-xl p-4 bg-white/5 backdrop-blur-sm">
311
+ <div class="flex justify-between items-start">
312
+ <div class="text-sm font-light text-gray-400 truncate flex-1">
313
+ ${item.original}
314
+ </div>
315
+ <div class="text-xs text-gray-500 ml-2">
316
+ ${new Date(item.timestamp).toLocaleDateString()}
317
+ </div>
318
+ </div>
319
+ <div class="flex justify-between items-center mt-2">
320
+ <div class="text-white truncate flex-1">${item.shortened}</div>
321
+ <div class="flex gap-2">
322
+ <button onclick="copyUrl('${item.shortened}')"
323
+ class="text-xs text-white/60 hover:text-white px-3 py-1 rounded-lg bg-white/10 transition-colors">
324
+ Copy
325
+ </button>
326
+ <button onclick="deleteHistoryItem('${item.timestamp}')"
327
+ class="text-xs text-red-400/60 hover:text-red-400 px-3 py-1 rounded-lg bg-red-500/10 transition-colors">
328
+ Delete
329
+ </button>
330
+ </div>
331
+ </div>
332
+ </div>
333
+ `).join('');
334
+ }
335
+
336
+ function deleteHistoryItem(timestamp) {
337
+ urlHistory = urlHistory.filter(item => item.timestamp !== timestamp);
338
+ localStorage.setItem('urlHistory', JSON.stringify(urlHistory));
339
+ updateHistoryDisplay();
340
+ navigator.vibrate(50);
341
+ }
342
+
343
+ function copyUrl(url) {
344
+ navigator.clipboard.writeText(url).then(() => {
345
+ showNotification('Copied to clipboard', 'success');
346
+ navigator.vibrate(50);
347
+ });
348
+ }
349
+
350
+ function copyToClipboard() {
351
+ const shortUrl = document.getElementById('shortUrl');
352
+ navigator.clipboard.writeText(shortUrl.value).then(() => {
353
+ showNotification('Copied to clipboard', 'success');
354
+ navigator.vibrate(50);
355
+ });
356
+ }
357
+
358
+ function showNotification(message, type) {
359
+ const notification = document.createElement('div');
360
+ notification.className = `fixed bottom-32 left-1/2 transform -translate-x-1/2
361
+ px-6 py-3 rounded-2xl text-white text-center text-sm
362
+ ${type === 'error' ? 'bg-red-500/90' : 'bg-white/10'}
363
+ opacity-0 transition-opacity duration-300 z-50
364
+ backdrop-blur-sm shadow-lg`;
365
+ notification.textContent = message;
366
+
367
+ document.body.appendChild(notification);
368
+ requestAnimationFrame(() => notification.classList.add('opacity-100'));
369
+ setTimeout(() => {
370
+ notification.classList.remove('opacity-100');
371
+ setTimeout(() => notification.remove(), 300);
372
+ }, 2000);
373
+ }
374
+
375
+ function closeDeveloperInfo() {
376
+ const popup = document.getElementById('developerInfo');
377
+ popup.style.opacity = '0';
378
+ popup.style.transform = 'scale(0.95)';
379
+ setTimeout(() => {
380
+ popup.style.display = 'none';
381
+ }, 300);
382
+
383
+ // Save to localStorage so it doesn't show again in this session
384
+ localStorage.setItem('developerInfoShown', 'true');
385
+ }
386
+
387
+ // Check if we should show the popup
388
+ window.addEventListener('DOMContentLoaded', () => {
389
+ const popup = document.getElementById('developerInfo');
390
+ if (!localStorage.getItem('developerInfoShown')) {
391
+ popup.style.display = 'flex';
392
+ popup.style.opacity = '0';
393
+ popup.style.transform = 'scale(0.95)';
394
+
395
+ // Trigger animation
396
+ setTimeout(() => {
397
+ popup.style.opacity = '1';
398
+ popup.style.transform = 'scale(1)';
399
+ }, 100);
400
+ } else {
401
+ popup.style.display = 'none';
402
+ }
403
+
404
+ // Console Developer Info
405
+ console.log(`%c
406
+ ╔══════════════════════════════════════════════════════════════╗
407
+ β•‘ Aditya Devarshi β•‘
408
+ β•‘ AI Engineer & Full Stack Developer β•‘
409
+ ╠══════════════════════════════════════════════════════════════╣
410
+ β•‘ β•‘
411
+ β•‘ 🌐 Portfolio: https://www.adityadevarshi.online β•‘
412
+ β•‘ πŸ’Ό LinkedIn: https://www.linkedin.com/in/aditya-devarshi β•‘
413
+ β•‘ πŸ“¦ GitHub: https://github.com/devarshiadi β•‘
414
+ β•‘ πŸ“ Medium: https://medium.com/@devarshia5 β•‘
415
+ β•‘ β•‘
416
+ ╠══════════════════════════════════════════════════════════════╣
417
+ β•‘ β•‘
418
+ β•‘ πŸš€ Skills: AI/ML, Full Stack Development, Cloud Computing β•‘
419
+ β•‘ πŸ“§ Contact: devarshia5@gmail.com β•‘
420
+ β•‘ β•‘
421
+ β•‘ πŸ’‘ "Building the future with AI and innovation" β•‘
422
+ β•‘ β•‘
423
+ β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
424
+ `, 'font-family: monospace; color: #00ff00; font-size: 12px;');
425
+
426
+ console.log('%cWelcome to URL Shortener!', 'color: #FF3B30; font-size: 20px; font-weight: bold;');
427
+ console.log('%cDeveloped with ❀️ by Aditya Devarshi', 'color: #888; font-size: 14px;');
428
+
429
+ // Tech stack info
430
+ console.log('%cTech Stack:', 'color: #00ff00; font-size: 16px; font-weight: bold;');
431
+ console.table({
432
+ 'Frontend': ['HTML5', 'TailwindCSS', 'JavaScript'],
433
+ 'Backend': ['Python', 'FastAPI', 'SQLite'],
434
+ 'Deployment': ['Docker', 'Cloud Ready'],
435
+ 'Design': ['Nothing OS Inspired', 'Modern Minimal UI']
436
+ });
437
+
438
+ // Contact info as an object
439
+ console.log('%cContact Information:', 'color: #00ff00; font-size: 16px; font-weight: bold;');
440
+ console.table({
441
+ 'Email': 'devarshia5@gmail.com',
442
+ 'LinkedIn': 'https://www.linkedin.com/in/aditya-devarshi',
443
+ 'Portfolio': 'https://www.adityadevarshi.online',
444
+ 'GitHub': 'https://github.com/devarshiadi',
445
+ 'Medium': 'https://medium.com/@devarshia5'
446
+ });
447
+
448
+ // Project info
449
+ console.log('%cProject Information:', 'color: #00ff00; font-size: 16px; font-weight: bold;');
450
+ console.table({
451
+ 'Name': 'Modern URL Shortener',
452
+ 'Version': '1.0.0',
453
+ 'License': 'MIT',
454
+ 'Repository': 'https://github.com/devarshiadi/url-shortener',
455
+ 'Design Inspiration': 'Nothing OS',
456
+ 'Features': [
457
+ 'Modern UI/UX',
458
+ 'URL Shortening',
459
+ 'History Management',
460
+ 'Responsive Design',
461
+ 'Dark Mode',
462
+ 'Local Storage'
463
+ ]
464
+ });
465
+
466
+ // Easter egg message
467
+ console.log('%cπŸ‘‹ Hey there, curious developer!', 'color: #FF3B30; font-size: 16px; font-weight: bold;');
468
+ console.log('%cLooking for opportunities or collaboration? Feel free to reach out!', 'color: #888; font-size: 14px;');
469
+ });
470
+ </script>
471
+ </body>
472
  </html>