vs4vijay commited on
Commit
e163dc3
·
verified ·
1 Parent(s): 32f47f5

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +708 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Sharepulse
3
- emoji: 🏃
4
- colorFrom: yellow
5
- colorTo: gray
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: sharepulse
3
+ emoji: 🐳
4
+ colorFrom: purple
5
+ colorTo: green
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,708 @@
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>Ntfy Messenger</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/qrcode@1.5.1/build/qrcode.min.js"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/emoji-picker-element@1.13.0/dist/index.min.js"></script>
10
+ <style>
11
+ .message-bubble {
12
+ max-width: 70%;
13
+ word-wrap: break-word;
14
+ }
15
+ .file-preview {
16
+ transition: all 0.3s ease;
17
+ }
18
+ .file-preview:hover {
19
+ transform: scale(1.05);
20
+ }
21
+ .gradient-bg {
22
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
23
+ }
24
+ .typing-indicator span {
25
+ display: inline-block;
26
+ width: 8px;
27
+ height: 8px;
28
+ border-radius: 50%;
29
+ background-color: #8b5cf6;
30
+ margin: 0 2px;
31
+ animation: bounce 1.4s infinite ease-in-out;
32
+ }
33
+ .typing-indicator span:nth-child(2) {
34
+ animation-delay: 0.2s;
35
+ }
36
+ .typing-indicator span:nth-child(3) {
37
+ animation-delay: 0.4s;
38
+ }
39
+ @keyframes bounce {
40
+ 0%, 80%, 100% { transform: translateY(0); }
41
+ 40% { transform: translateY(-10px); }
42
+ }
43
+ .emoji-picker {
44
+ position: absolute;
45
+ bottom: 60px;
46
+ right: 20px;
47
+ z-index: 10;
48
+ }
49
+ </style>
50
+ </head>
51
+ <body class="bg-gray-100 font-sans">
52
+ <div class="flex h-screen">
53
+ <!-- Sidebar -->
54
+ <div class="w-80 bg-white border-r border-gray-200 flex flex-col">
55
+ <div class="p-4 gradient-bg text-white">
56
+ <h1 class="text-xl font-bold">PulseLink</h1>
57
+ <p class="text-sm opacity-80">Secure peer-to-peer messaging</p>
58
+ </div>
59
+
60
+ <div class="p-4 border-b border-gray-200">
61
+ <div class="flex items-center space-x-2">
62
+ <div class="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center">
63
+ <span class="text-indigo-600 font-bold" id="userInitial">U</span>
64
+ </div>
65
+ <div>
66
+ <p class="font-medium" id="username">User</p>
67
+ <p class="text-xs text-gray-500">Online</p>
68
+ </div>
69
+ </div>
70
+ </div>
71
+
72
+ <div class="p-4 border-b border-gray-200">
73
+ <h2 class="text-sm font-semibold text-gray-500 mb-2">CHANNEL CODE</h2>
74
+ <div class="flex items-center justify-between bg-gray-50 p-2 rounded">
75
+ <code class="text-sm font-mono" id="channelCode">Not connected</code>
76
+ <button id="copyCodeBtn" class="text-indigo-600 hover:text-indigo-800">
77
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
78
+ <path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" />
79
+ <path d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z" />
80
+ </svg>
81
+ </button>
82
+ </div>
83
+ <div class="mt-3 flex space-x-2">
84
+ <button id="showQRBtn" class="flex-1 bg-indigo-100 text-indigo-700 py-2 px-3 rounded text-sm font-medium hover:bg-indigo-200 transition">
85
+ Show QR
86
+ </button>
87
+ <button id="shareBtn" class="flex-1 bg-indigo-100 text-indigo-700 py-2 px-3 rounded text-sm font-medium hover:bg-indigo-200 transition">
88
+ Share
89
+ </button>
90
+ </div>
91
+ </div>
92
+
93
+ <div class="p-4 border-b border-gray-200">
94
+ <h2 class="text-sm font-semibold text-gray-500 mb-2">CONNECT TO CHANNEL</h2>
95
+ <div class="flex">
96
+ <input type="text" id="joinCodeInput" placeholder="Enter code" class="flex-1 border border-gray-300 rounded-l px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-indigo-500">
97
+ <button id="joinBtn" class="bg-indigo-600 text-white px-3 py-2 rounded-r text-sm font-medium hover:bg-indigo-700 transition">
98
+ Join
99
+ </button>
100
+ </div>
101
+ </div>
102
+
103
+ <div class="p-4 border-b border-gray-200">
104
+ <h2 class="text-sm font-semibold text-gray-500 mb-2">FILES</h2>
105
+ <div class="space-y-2" id="fileList">
106
+ <p class="text-sm text-gray-500">No files shared yet</p>
107
+ </div>
108
+ </div>
109
+
110
+ <div class="p-4 mt-auto">
111
+ <button id="newChannelBtn" class="w-full bg-indigo-600 text-white py-2 px-3 rounded text-sm font-medium hover:bg-indigo-700 transition">
112
+ Create New Channel
113
+ </button>
114
+ </div>
115
+ </div>
116
+
117
+ <!-- Main Chat Area -->
118
+ <div class="flex-1 flex flex-col">
119
+ <!-- Chat Header -->
120
+ <div class="bg-white border-b border-gray-200 p-4 flex items-center justify-between">
121
+ <div class="flex items-center space-x-3">
122
+ <div class="w-10 h-10 rounded-full bg-purple-100 flex items-center justify-center">
123
+ <span class="text-purple-600 font-bold">F</span>
124
+ </div>
125
+ <div>
126
+ <p class="font-medium">Friend</p>
127
+ <div class="flex items-center">
128
+ <div class="typing-indicator hidden" id="typingIndicator">
129
+ <span></span>
130
+ <span></span>
131
+ <span></span>
132
+ </div>
133
+ <p class="text-xs text-gray-500" id="statusText">Offline</p>
134
+ </div>
135
+ </div>
136
+ </div>
137
+ <div class="flex items-center space-x-2">
138
+ <button class="p-2 text-gray-500 hover:text-gray-700 rounded-full hover:bg-gray-100">
139
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
140
+ <path d="M2 5a2 2 0 012-2h7a2 2 0 012 2v4a2 2 0 01-2 2H9l-3 3v-3H4a2 2 0 01-2-2V5z" />
141
+ <path d="M15 7v2a4 4 0 01-4 4H9.828l-1.766 1.767c.28.149.599.233.938.233h2l3 3v-3h2a2 2 0 002-2V9a2 2 0 00-2-2h-1z" />
142
+ </svg>
143
+ </button>
144
+ <button class="p-2 text-gray-500 hover:text-gray-700 rounded-full hover:bg-gray-100">
145
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
146
+ <path fill-rule="evenodd" d="M11.49 3.17c.38-1.56 2.6-1.56 2.98 0 .36 1.56 1.52 2.82 3.08 3.08 1.56.36 1.56 2.6 0 2.98-1.56.36-2.82 1.52-3.08 3.08-.38 1.56-2.6 1.56-2.98 0-.36-1.56-1.52-2.82-3.08-3.08-1.56-.36-1.56-2.6 0-2.98 1.56-.36 2.82-1.52 3.08-3.08zM7 9a1 1 0 011-1h1a1 1 0 110 2H8a1 1 0 01-1-1zm-2 0a3 3 0 013-3h1a3 3 0 013 3v1a3 3 0 01-3 3H8a3 3 0 01-3-3V9zm6.5 4.5a1.5 1.5 0 100-3 1.5 1.5 0 000 3z" clip-rule="evenodd" />
147
+ </svg>
148
+ </button>
149
+ </div>
150
+ </div>
151
+
152
+ <!-- Messages -->
153
+ <div class="flex-1 overflow-y-auto p-4 bg-gray-50" id="messagesContainer">
154
+ <div class="text-center py-10 text-gray-500" id="emptyState">
155
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
156
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
157
+ </svg>
158
+ <p class="mt-2">No messages yet</p>
159
+ <p class="text-sm">Join or create a channel to start messaging</p>
160
+ </div>
161
+ </div>
162
+
163
+ <!-- Message Input -->
164
+ <div class="bg-white border-t border-gray-200 p-4">
165
+ <div class="relative">
166
+ <div id="filePreview" class="mb-2 flex space-x-2 overflow-x-auto"></div>
167
+ <div class="flex items-end">
168
+ <button id="emojiBtn" class="p-2 text-gray-500 hover:text-gray-700 rounded-full hover:bg-gray-100 mr-1">
169
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
170
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M14.828 14.828a4 4 0 01-5.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
171
+ </svg>
172
+ </button>
173
+ <button id="fileBtn" class="p-2 text-gray-500 hover:text-gray-700 rounded-full hover:bg-gray-100 mr-1">
174
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
175
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13" />
176
+ </svg>
177
+ </button>
178
+ <input type="file" id="fileInput" class="hidden" multiple>
179
+ <div class="flex-1 relative">
180
+ <textarea id="messageInput" rows="1" placeholder="Type a message..." class="w-full border border-gray-300 rounded-full px-4 py-2 pr-10 focus:outline-none focus:ring-1 focus:ring-indigo-500 resize-none" style="min-height: 44px;"></textarea>
181
+ <button id="sendBtn" class="absolute right-2 bottom-2 p-1 text-indigo-600 hover:text-indigo-800 rounded-full">
182
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
183
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
184
+ </svg>
185
+ </button>
186
+ </div>
187
+ </div>
188
+ <emoji-picker class="emoji-picker"></emoji-picker>
189
+ </div>
190
+ </div>
191
+ </div>
192
+ </div>
193
+
194
+ <!-- QR Code Modal -->
195
+ <div id="qrModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
196
+ <div class="bg-white rounded-lg p-6 w-80">
197
+ <div class="flex justify-between items-center mb-4">
198
+ <h3 class="text-lg font-medium">Share Channel</h3>
199
+ <button id="closeQRModal" class="text-gray-500 hover:text-gray-700">
200
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
201
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
202
+ </svg>
203
+ </button>
204
+ </div>
205
+ <div class="flex flex-col items-center">
206
+ <div id="qrCode" class="p-4 bg-white rounded border border-gray-200 mb-4"></div>
207
+ <div class="w-full mb-4">
208
+ <h4 class="text-sm font-medium text-gray-700 mb-2">Or share these words:</h4>
209
+ <div class="bg-gray-50 p-3 rounded text-center">
210
+ <p class="font-mono text-sm" id="seedWords">apple banana cherry dolphin elephant</p>
211
+ </div>
212
+ </div>
213
+ <button id="copySeedBtn" class="w-full bg-indigo-600 text-white py-2 px-3 rounded text-sm font-medium hover:bg-indigo-700 transition">
214
+ Copy Words
215
+ </button>
216
+ </div>
217
+ </div>
218
+ </div>
219
+
220
+ <script>
221
+ // App state
222
+ const state = {
223
+ channelCode: null,
224
+ messages: [],
225
+ files: [],
226
+ isTyping: false,
227
+ username: 'User' + Math.floor(Math.random() * 1000),
228
+ friendName: 'Friend' + Math.floor(Math.random() * 1000),
229
+ eventSource: null
230
+ };
231
+
232
+ // DOM elements
233
+ const elements = {
234
+ messageInput: document.getElementById('messageInput'),
235
+ sendBtn: document.getElementById('sendBtn'),
236
+ messagesContainer: document.getElementById('messagesContainer'),
237
+ emptyState: document.getElementById('emptyState'),
238
+ channelCode: document.getElementById('channelCode'),
239
+ copyCodeBtn: document.getElementById('copyCodeBtn'),
240
+ showQRBtn: document.getElementById('showQRBtn'),
241
+ shareBtn: document.getElementById('shareBtn'),
242
+ joinCodeInput: document.getElementById('joinCodeInput'),
243
+ joinBtn: document.getElementById('joinBtn'),
244
+ newChannelBtn: document.getElementById('newChannelBtn'),
245
+ qrModal: document.getElementById('qrModal'),
246
+ closeQRModal: document.getElementById('closeQRModal'),
247
+ qrCode: document.getElementById('qrCode'),
248
+ seedWords: document.getElementById('seedWords'),
249
+ copySeedBtn: document.getElementById('copySeedBtn'),
250
+ typingIndicator: document.getElementById('typingIndicator'),
251
+ statusText: document.getElementById('statusText'),
252
+ fileInput: document.getElementById('fileInput'),
253
+ fileBtn: document.getElementById('fileBtn'),
254
+ filePreview: document.getElementById('filePreview'),
255
+ fileList: document.getElementById('fileList'),
256
+ emojiBtn: document.getElementById('emojiBtn'),
257
+ userInitial: document.getElementById('userInitial'),
258
+ username: document.getElementById('username')
259
+ };
260
+
261
+ // Initialize
262
+ function init() {
263
+ // Set username
264
+ elements.username.textContent = state.username;
265
+ elements.userInitial.textContent = state.username.charAt(0).toUpperCase();
266
+
267
+ // Event listeners
268
+ elements.sendBtn.addEventListener('click', sendMessage);
269
+ elements.messageInput.addEventListener('keypress', (e) => {
270
+ if (e.key === 'Enter' && !e.shiftKey) {
271
+ e.preventDefault();
272
+ sendMessage();
273
+ }
274
+ });
275
+
276
+ elements.messageInput.addEventListener('input', () => {
277
+ if (elements.messageInput.value.trim() && !state.isTyping) {
278
+ state.isTyping = true;
279
+ simulateTyping();
280
+ } else if (!elements.messageInput.value.trim() && state.isTyping) {
281
+ state.isTyping = false;
282
+ }
283
+ });
284
+
285
+ elements.copyCodeBtn.addEventListener('click', copyChannelCode);
286
+ elements.showQRBtn.addEventListener('click', showQRModal);
287
+ elements.shareBtn.addEventListener('click', shareChannel);
288
+ elements.joinBtn.addEventListener('click', joinChannel);
289
+ elements.newChannelBtn.addEventListener('click', createNewChannel);
290
+ elements.closeQRModal.addEventListener('click', () => elements.qrModal.classList.add('hidden'));
291
+ elements.copySeedBtn.addEventListener('click', copySeedWords);
292
+ elements.fileBtn.addEventListener('click', () => elements.fileInput.click());
293
+ elements.fileInput.addEventListener('change', handleFileSelect);
294
+
295
+ // Emoji picker
296
+ const picker = document.querySelector('emoji-picker');
297
+ picker.addEventListener('emoji-click', (event) => {
298
+ elements.messageInput.value += event.detail.unicode;
299
+ elements.messageInput.focus();
300
+ });
301
+
302
+ elements.emojiBtn.addEventListener('click', () => {
303
+ picker.classList.toggle('hidden');
304
+ });
305
+
306
+ // Generate seed words
307
+ generateSeedWords();
308
+
309
+ // Create initial channel
310
+ createNewChannel();
311
+ }
312
+
313
+ // Generate random seed words
314
+ function generateSeedWords() {
315
+ const words = [
316
+ 'apple', 'banana', 'cherry', 'dolphin', 'elephant',
317
+ 'flamingo', 'giraffe', 'honey', 'igloo', 'jungle',
318
+ 'koala', 'lemon', 'mango', 'narwhal', 'octopus',
319
+ 'penguin', 'quokka', 'raccoon', 'sunflower', 'tiger',
320
+ 'unicorn', 'violet', 'watermelon', 'xylophone', 'yellow', 'zebra'
321
+ ];
322
+
323
+ const selected = [];
324
+ for (let i = 0; i < 5; i++) {
325
+ selected.push(words[Math.floor(Math.random() * words.length)]);
326
+ }
327
+
328
+ state.seedWords = selected.join(' ');
329
+ elements.seedWords.textContent = state.seedWords;
330
+ }
331
+
332
+ // Create new channel
333
+ function createNewChannel() {
334
+ // Generate random channel code
335
+ state.channelCode = 'ntfy-' + Math.random().toString(36).substring(2, 10);
336
+ elements.channelCode.textContent = state.channelCode;
337
+
338
+ // Clear messages
339
+ state.messages = [];
340
+ renderMessages();
341
+
342
+ // Clear files
343
+ state.files = [];
344
+ renderFiles();
345
+
346
+ // Update status
347
+ elements.statusText.textContent = 'Waiting for connection...';
348
+
349
+ // Close any existing connection
350
+ if (state.eventSource) {
351
+ state.eventSource.close();
352
+ }
353
+
354
+ // Connect to ntfy
355
+ connectToNtfy();
356
+
357
+ // Generate new seed words
358
+ generateSeedWords();
359
+ }
360
+
361
+ // Connect to ntfy
362
+ function connectToNtfy() {
363
+ const url = `https://ntfy.sh/${state.channelCode}/json`;
364
+
365
+ state.eventSource = new EventSource(url);
366
+
367
+ state.eventSource.onmessage = (e) => {
368
+ const data = JSON.parse(e.data);
369
+
370
+ if (data.event === 'message') {
371
+ // Check if message is from friend (not from ourselves)
372
+ if (data.message !== `${state.username}: ${elements.messageInput.value.trim()}`) {
373
+ const message = {
374
+ text: data.message,
375
+ sender: 'friend',
376
+ timestamp: new Date(data.time * 1000)
377
+ };
378
+
379
+ state.messages.push(message);
380
+ renderMessages();
381
+
382
+ // Check if message contains a file
383
+ if (data.message.includes('FILE:')) {
384
+ const fileName = data.message.split('FILE:')[1].trim();
385
+ if (!state.files.includes(fileName)) {
386
+ state.files.push(fileName);
387
+ renderFiles();
388
+ }
389
+ }
390
+ }
391
+ }
392
+
393
+ // Update status
394
+ elements.statusText.textContent = 'Online';
395
+ elements.typingIndicator.classList.add('hidden');
396
+ };
397
+
398
+ state.eventSource.onerror = () => {
399
+ elements.statusText.textContent = 'Connection error';
400
+ };
401
+ }
402
+
403
+ // Join existing channel
404
+ function joinChannel() {
405
+ const code = elements.joinCodeInput.value.trim();
406
+ if (!code) return;
407
+
408
+ state.channelCode = code;
409
+ elements.channelCode.textContent = state.channelCode;
410
+ elements.joinCodeInput.value = '';
411
+
412
+ // Clear messages
413
+ state.messages = [];
414
+ renderMessages();
415
+
416
+ // Clear files
417
+ state.files = [];
418
+ renderFiles();
419
+
420
+ // Update status
421
+ elements.statusText.textContent = 'Connecting...';
422
+
423
+ // Close any existing connection
424
+ if (state.eventSource) {
425
+ state.eventSource.close();
426
+ }
427
+
428
+ // Connect to ntfy
429
+ connectToNtfy();
430
+ }
431
+
432
+ // Send message
433
+ function sendMessage() {
434
+ const text = elements.messageInput.value.trim();
435
+ const files = Array.from(elements.fileInput.files);
436
+
437
+ if (!text && files.length === 0) return;
438
+
439
+ // Clear files preview
440
+ elements.filePreview.innerHTML = '';
441
+ elements.fileInput.value = '';
442
+
443
+ // Send text message if exists
444
+ if (text) {
445
+ const message = {
446
+ text: text,
447
+ sender: 'me',
448
+ timestamp: new Date()
449
+ };
450
+
451
+ state.messages.push(message);
452
+ renderMessages();
453
+
454
+ // Send to ntfy
455
+ sendToNtfy(`${state.username}: ${text}`);
456
+
457
+ elements.messageInput.value = '';
458
+ }
459
+
460
+ // Send files if exists
461
+ files.forEach(file => {
462
+ const fileName = `FILE: ${file.name}`;
463
+ const message = {
464
+ text: fileName,
465
+ sender: 'me',
466
+ timestamp: new Date(),
467
+ isFile: true,
468
+ fileName: file.name
469
+ };
470
+
471
+ state.messages.push(message);
472
+ renderMessages();
473
+
474
+ // Send to ntfy
475
+ sendToNtfy(`${state.username}: ${fileName}`);
476
+
477
+ // Add to files list
478
+ if (!state.files.includes(fileName)) {
479
+ state.files.push(fileName);
480
+ renderFiles();
481
+ }
482
+ });
483
+
484
+ // Reset typing state
485
+ state.isTyping = false;
486
+ }
487
+
488
+ // Send data to ntfy
489
+ function sendToNtfy(message) {
490
+ fetch(`https://ntfy.sh/${state.channelCode}`, {
491
+ method: 'POST',
492
+ body: message
493
+ });
494
+ }
495
+
496
+ // Render messages
497
+ function renderMessages() {
498
+ if (state.messages.length === 0) {
499
+ elements.emptyState.classList.remove('hidden');
500
+ elements.messagesContainer.innerHTML = '';
501
+ return;
502
+ }
503
+
504
+ elements.emptyState.classList.add('hidden');
505
+
506
+ let html = '';
507
+ state.messages.forEach((msg, index) => {
508
+ const time = msg.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
509
+
510
+ if (msg.sender === 'me') {
511
+ html += `
512
+ <div class="flex justify-end mb-4" data-index="${index}">
513
+ <div class="message-bubble bg-indigo-100 text-gray-800 rounded-l-xl rounded-tr-xl px-4 py-2">
514
+ ${msg.isFile ?
515
+ `<div class="mb-1">
516
+ <div class="flex items-center text-indigo-600">
517
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
518
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
519
+ </svg>
520
+ <span class="text-xs">${msg.fileName}</span>
521
+ </div>
522
+ </div>` :
523
+ `<p>${msg.text.replace(`${state.username}: `, '')}</p>`
524
+ }
525
+ <p class="text-xs text-gray-500 text-right mt-1">${time}</p>
526
+ </div>
527
+ </div>
528
+ `;
529
+ } else {
530
+ html += `
531
+ <div class="flex justify-start mb-4" data-index="${index}">
532
+ <div class="message-bubble bg-white text-gray-800 rounded-r-xl rounded-tl-xl px-4 py-2 border border-gray-200">
533
+ ${msg.text.includes('FILE:') ?
534
+ `<div class="mb-1">
535
+ <div class="flex items-center text-purple-600">
536
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
537
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
538
+ </svg>
539
+ <span class="text-xs">${msg.text.split('FILE:')[1].trim()}</span>
540
+ </div>
541
+ </div>` :
542
+ `<p>${msg.text.replace(`${state.friendName}: `, '')}</p>`
543
+ }
544
+ <p class="text-xs text-gray-500 mt-1">${time}</p>
545
+ </div>
546
+ </div>
547
+ `;
548
+ }
549
+ });
550
+
551
+ elements.messagesContainer.innerHTML = html;
552
+ elements.messagesContainer.scrollTop = elements.messagesContainer.scrollHeight;
553
+ }
554
+
555
+ // Render files list
556
+ function renderFiles() {
557
+ if (state.files.length === 0) {
558
+ elements.fileList.innerHTML = '<p class="text-sm text-gray-500">No files shared yet</p>';
559
+ return;
560
+ }
561
+
562
+ let html = '';
563
+ state.files.forEach(file => {
564
+ const fileName = file.includes('FILE:') ? file.split('FILE:')[1].trim() : file;
565
+ html += `
566
+ <div class="flex items-center p-2 bg-gray-50 rounded hover:bg-gray-100">
567
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
568
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
569
+ </svg>
570
+ <span class="text-sm truncate flex-1">${fileName}</span>
571
+ </div>
572
+ `;
573
+ });
574
+
575
+ elements.fileList.innerHTML = html;
576
+ }
577
+
578
+ // Handle file selection
579
+ function handleFileSelect() {
580
+ const files = Array.from(elements.fileInput.files);
581
+ if (files.length === 0) return;
582
+
583
+ elements.filePreview.innerHTML = '';
584
+
585
+ files.forEach(file => {
586
+ const preview = document.createElement('div');
587
+ preview.className = 'file-preview bg-white p-2 rounded border border-gray-200 flex-shrink-0 w-24';
588
+
589
+ if (file.type.startsWith('image/')) {
590
+ const reader = new FileReader();
591
+ reader.onload = (e) => {
592
+ preview.innerHTML = `
593
+ <img src="${e.target.result}" class="w-full h-16 object-cover rounded mb-1">
594
+ <p class="text-xs truncate">${file.name}</p>
595
+ `;
596
+ };
597
+ reader.readAsDataURL(file);
598
+ } else {
599
+ preview.innerHTML = `
600
+ <div class="w-full h-16 bg-gray-100 rounded flex items-center justify-center mb-1">
601
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
602
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
603
+ </svg>
604
+ </div>
605
+ <p class="text-xs truncate">${file.name}</p>
606
+ `;
607
+ }
608
+
609
+ elements.filePreview.appendChild(preview);
610
+ });
611
+ }
612
+
613
+ // Copy channel code
614
+ function copyChannelCode() {
615
+ if (!state.channelCode) return;
616
+ navigator.clipboard.writeText(state.channelCode);
617
+
618
+ // Show feedback
619
+ const originalText = elements.copyCodeBtn.innerHTML;
620
+ elements.copyCodeBtn.innerHTML = `
621
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
622
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
623
+ </svg>
624
+ `;
625
+
626
+ setTimeout(() => {
627
+ elements.copyCodeBtn.innerHTML = originalText;
628
+ }, 2000);
629
+ }
630
+
631
+ // Show QR modal
632
+ function showQRModal() {
633
+ if (!state.channelCode) return;
634
+
635
+ // Generate QR code
636
+ QRCode.toCanvas(state.channelCode, {
637
+ width: 200,
638
+ margin: 1,
639
+ color: {
640
+ dark: '#000000',
641
+ light: '#ffffff'
642
+ }
643
+ }, (err, canvas) => {
644
+ if (err) {
645
+ console.error(err);
646
+ return;
647
+ }
648
+
649
+ elements.qrCode.innerHTML = '';
650
+ elements.qrCode.appendChild(canvas);
651
+ });
652
+
653
+ elements.qrModal.classList.remove('hidden');
654
+ }
655
+
656
+ // Share channel
657
+ function shareChannel() {
658
+ if (!state.channelCode) return;
659
+
660
+ if (navigator.share) {
661
+ navigator.share({
662
+ title: 'Join my Ntfy Messenger channel',
663
+ text: `Use this code to join my channel: ${state.channelCode}`,
664
+ url: window.location.href
665
+ }).catch(err => {
666
+ console.log('Error sharing:', err);
667
+ });
668
+ } else {
669
+ // Fallback for browsers that don't support Web Share API
670
+ copyChannelCode();
671
+ alert('Channel code copied to clipboard. Share it with your friend!');
672
+ }
673
+ }
674
+
675
+ // Copy seed words
676
+ function copySeedWords() {
677
+ navigator.clipboard.writeText(state.seedWords);
678
+
679
+ // Show feedback
680
+ const originalText = elements.copySeedBtn.textContent;
681
+ elements.copySeedBtn.textContent = 'Copied!';
682
+
683
+ setTimeout(() => {
684
+ elements.copySeedBtn.textContent = originalText;
685
+ }, 2000);
686
+ }
687
+
688
+ // Simulate friend typing
689
+ function simulateTyping() {
690
+ if (!state.isTyping) return;
691
+
692
+ elements.typingIndicator.classList.remove('hidden');
693
+ elements.statusText.textContent = '';
694
+
695
+ setTimeout(() => {
696
+ if (state.isTyping) {
697
+ elements.typingIndicator.classList.add('hidden');
698
+ elements.statusText.textContent = 'Online';
699
+ state.isTyping = false;
700
+ }
701
+ }, 2000);
702
+ }
703
+
704
+ // Initialize the app
705
+ init();
706
+ </script>
707
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - <a href="https://enzostvs-deepsite.hf.space?remix=vs4vijay/sharepulse" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body>
708
+ </html>