Aleksmorshen commited on
Commit
1da0b5c
·
verified ·
1 Parent(s): b21ff9e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +476 -480
app.py CHANGED
@@ -44,14 +44,14 @@ def index():
44
  <style>
45
  :root {
46
  --bg-primary: #000000;
47
- --bg-secondary: #1C1C1E;
48
- --bg-tertiary: #2C2C2E;
49
- --bg-hover: #3A3A3C;
50
  --text-primary: #FFFFFF;
51
  --text-secondary: #8E8E93;
52
  --accent-blue: #007AFF;
53
- --accent-blue-light: #3395FF;
54
- --border-color: #38383A;
55
  --success-color: #34C759;
56
  --error-color: #FF3B30;
57
  --font-family: 'Inter', sans-serif;
@@ -73,19 +73,21 @@ def index():
73
  background-color: var(--bg-primary);
74
  color: var(--text-primary);
75
  overflow: hidden;
76
- height: 100dvh;
 
77
  width: 100vw;
78
  display: flex;
79
  align-items: center;
80
  justify-content: center;
81
  }
82
-
83
- .app-container, .main-container {
84
  width: 100%;
85
  height: 100%;
86
  display: flex;
87
  flex-direction: column;
88
  transition: opacity 0.3s ease;
 
89
  }
90
 
91
  #login-view {
@@ -94,8 +96,6 @@ def index():
94
  align-items: center;
95
  justify-content: center;
96
  text-align: center;
97
- width: 100%;
98
- height: 100%;
99
  background: radial-gradient(circle, #1a2a3a 0%, var(--bg-primary) 70%);
100
  }
101
  #login-view img {
@@ -105,7 +105,7 @@ def index():
105
  filter: drop-shadow(0 0 25px rgba(0, 122, 255, 0.6));
106
  }
107
  #login-view h1 {
108
- font-size: 3rem;
109
  font-weight: 700;
110
  background: linear-gradient(45deg, var(--accent-blue-light), var(--accent-blue));
111
  -webkit-background-clip: text;
@@ -113,19 +113,20 @@ def index():
113
  margin-bottom: 12px;
114
  }
115
  #login-view p {
116
- font-size: 1.1rem;
117
  color: var(--text-secondary);
118
  margin-bottom: 40px;
119
  }
120
 
121
- #app-container {
122
  display: none;
123
  }
124
 
125
- .main-content {
126
  flex-grow: 1;
127
- position: relative;
128
  overflow: hidden;
 
 
129
  }
130
 
131
  .page {
@@ -134,61 +135,56 @@ def index():
134
  left: 0;
135
  width: 100%;
136
  height: 100%;
137
- background-color: var(--bg-primary);
138
- display: none;
139
  flex-direction: column;
 
 
 
 
140
  }
141
  .page.active {
142
- display: flex;
143
- }
144
-
145
- .page-header {
146
- flex-shrink: 0;
147
- padding: 16px;
148
- padding-top: 24px;
149
- background-color: var(--bg-secondary);
150
- border-bottom: 1px solid var(--border-color);
151
- }
152
- .page-header h2 {
153
- font-size: 1.8rem;
154
- font-weight: 700;
155
- }
156
-
157
- .content-area {
158
- flex-grow: 1;
159
- overflow-y: auto;
160
- padding: 8px;
161
  }
162
 
163
- #chats-page-content {
164
- display: flex;
165
- width: 100%;
166
- height: 100%;
167
- }
168
 
169
- #chatroom-list-view {
170
  display: flex;
171
  flex-direction: column;
172
  height: 100%;
173
  width: 100%;
174
- background-color: var(--bg-primary);
 
175
  }
176
-
177
- .list-header {
 
 
 
 
 
 
 
 
178
  display: flex;
179
  justify-content: space-between;
180
  align-items: center;
181
- padding: 12px 16px;
182
- border-bottom: 1px solid var(--border-color);
183
- flex-shrink: 0;
184
  }
185
- .list-header h2 {
186
- font-size: 1.5rem;
187
- font-weight: 600;
 
 
 
188
  }
189
 
190
- #chatroom-list { flex-grow: 1; overflow-y: auto; }
191
- .list-item, .chatroom-item {
 
 
 
192
  display: flex;
193
  align-items: center;
194
  gap: 12px;
@@ -197,7 +193,7 @@ def index():
197
  border-bottom: 1px solid var(--border-color);
198
  transition: background-color 0.2s ease;
199
  }
200
- .list-item:hover, .chatroom-item:hover { background-color: var(--bg-hover); }
201
 
202
  .avatar {
203
  width: 48px;
@@ -206,8 +202,8 @@ def index():
206
  display: flex;
207
  align-items: center;
208
  justify-content: center;
209
- font-size: 1.2rem;
210
  font-weight: 600;
 
211
  color: white;
212
  flex-shrink: 0;
213
  }
@@ -216,7 +212,8 @@ def index():
216
  overflow: hidden;
217
  }
218
  .item-name {
219
- font-weight: 500;
 
220
  white-space: nowrap;
221
  overflow: hidden;
222
  text-overflow: ellipsis;
@@ -229,16 +226,16 @@ def index():
229
  text-overflow: ellipsis;
230
  }
231
  .lock-icon {
232
- width: 16px; height: 16px;
 
233
  fill: var(--text-secondary);
234
  flex-shrink: 0;
235
  }
236
-
237
  #chat-window-view {
238
  display: none;
239
  flex-direction: column;
240
- height: 100%;
241
- width: 100%;
242
  background-color: var(--bg-primary);
243
  }
244
 
@@ -247,20 +244,25 @@ def index():
247
  align-items: center;
248
  gap: 12px;
249
  padding: 12px 16px;
250
- background-color: rgba(28, 28, 30, 0.8);
251
- -webkit-backdrop-filter: blur(10px);
252
- backdrop-filter: blur(10px);
253
  border-bottom: 1px solid var(--border-color);
254
  flex-shrink: 0;
255
- position: relative;
256
- z-index: 10;
257
  }
258
  .back-btn {
259
- background: none; border: none; cursor: pointer;
260
- display: flex; align-items: center; justify-content: center;
 
 
 
 
 
 
 
 
 
 
 
261
  }
262
- .back-btn svg { width: 28px; height: 28px; fill: var(--accent-blue); }
263
- #chat-header-title { font-size: 1.1rem; font-weight: 600; }
264
 
265
  #messages-container {
266
  flex-grow: 1;
@@ -271,245 +273,275 @@ def index():
271
  gap: 12px;
272
  }
273
 
274
- .message {
275
- display: flex;
276
- gap: 10px;
277
- max-width: 85%;
278
- align-items: flex-end;
279
- }
280
- .message-content {
281
- display: flex;
282
- flex-direction: column;
283
- }
284
- .message-bubble {
285
- padding: 10px 14px;
286
- border-radius: 20px;
287
- line-height: 1.4;
288
- word-wrap: break-word;
289
- font-size: 1.05rem;
290
- }
291
- .message-sender {
292
- font-size: 0.8rem;
293
- font-weight: 600;
294
- color: var(--text-secondary);
295
- word-break: break-all;
296
- cursor: pointer;
297
- padding: 0 12px 4px;
298
- }
299
-
300
- .message.sent { align-self: flex-end; }
301
- .message.sent .message-bubble {
302
- background: var(--accent-blue);
303
- color: white;
304
- border-bottom-right-radius: 5px;
305
- }
306
-
307
  .message.received { align-self: flex-start; }
308
- .message.received .message-bubble {
309
- background-color: var(--bg-tertiary);
310
- border-bottom-left-radius: 5px;
311
- }
312
- .message.received .message-sender {
313
- color: var(--accent-blue-light);
314
- }
315
 
316
  .chat-placeholder {
317
- display: flex; flex-direction: column; align-items: center; justify-content: center;
318
- height: 100%; text-align: center; color: var(--text-secondary); padding: 20px;
 
 
 
 
 
 
 
 
319
  }
320
  .chat-placeholder img { width: 80px; margin-bottom: 20px; opacity: 0.5; }
321
 
322
  .message-form {
323
- display: flex; padding: 12px; gap: 12px;
 
 
324
  background-color: var(--bg-secondary);
325
  border-top: 1px solid var(--border-color);
326
  flex-shrink: 0;
327
  }
328
  #message-input {
329
- flex-grow: 1; padding: 12px 16px;
330
- border: 1px solid var(--border-color);
 
331
  background-color: var(--bg-tertiary);
332
  color: var(--text-primary);
333
- border-radius: 22px; outline: none; font-size: 1rem;
334
- transition: border-color 0.2s;
 
335
  }
336
- #message-input:focus { border-color: var(--accent-blue); }
337
-
338
- .action-btn {
339
  background: var(--accent-blue);
340
- color: white; border: none; padding: 10px 16px; border-radius: 8px;
341
- cursor: pointer; font-weight: 500; font-size: 1rem;
342
- transition: background-color 0.2s ease, transform 0.2s ease;
343
- display: flex; align-items: center; justify-content: center; gap: 8px;
344
- }
345
- .action-btn:hover { background-color: var(--accent-blue-light); }
346
- .action-btn:active { transform: scale(0.95); }
347
-
348
- .send-btn { width: 44px; height: 44px; border-radius: 50%; flex-shrink: 0; padding: 0; }
349
- .send-btn svg { width: 22px; height: 22px; fill: white; margin-left: 2px; }
350
-
351
- .bottom-nav {
352
- display: flex;
353
  flex-shrink: 0;
354
- background-color: var(--bg-secondary);
355
- border-top: 1px solid var(--border-color);
356
- }
357
- .nav-item {
358
- flex: 1;
359
  display: flex;
360
- flex-direction: column;
361
  align-items: center;
362
  justify-content: center;
363
- padding: 6px 0;
364
- cursor: pointer;
365
- color: var(--text-secondary);
366
- transition: color 0.2s;
367
- }
368
- .nav-item.active { color: var(--accent-blue); }
369
- .nav-item svg { width: 26px; height: 26px; fill: currentColor; }
370
- .nav-item span { font-size: 0.7rem; margin-top: 2px; }
371
- .nav-item#nav-scan-qr {
372
- transform: translateY(-15px);
373
- }
374
- .nav-item#nav-scan-qr .scan-button {
375
- width: 60px; height: 60px; border-radius: 50%;
376
- background: linear-gradient(45deg, var(--accent-blue-light), var(--accent-blue));
377
- display: flex; align-items: center; justify-content: center;
378
- box-shadow: 0 0 20px rgba(0, 122, 255, 0.5);
379
- border: 4px solid var(--bg-primary);
380
  }
381
- .nav-item#nav-scan-qr .scan-button svg { width: 32px; height: 32px; fill: white; }
 
382
 
383
- #browser-view {
384
- background-color: var(--bg-secondary);
385
- }
386
  .browser-header {
387
  display: flex;
388
  gap: 8px;
389
  padding: 12px;
390
  background-color: var(--bg-tertiary);
391
  border-bottom: 1px solid var(--border-color);
 
392
  }
393
- #url-input {
394
- flex-grow: 1; padding: 8px 12px;
395
  background-color: var(--bg-primary);
396
  border: 1px solid var(--border-color);
397
- color: var(--text-primary); border-radius: 6px; font-size: 0.9rem;
 
 
 
398
  }
399
  #browser-frame {
400
  flex-grow: 1;
401
  border: none;
402
- background-color: #fff;
403
  }
404
-
405
- #profile-page .content-area {
 
 
406
  padding: 24px;
407
- display: flex; flex-direction: column; align-items: center;
408
  text-align: center;
409
  }
410
- #profile-page .avatar { width: 100px; height: 100px; font-size: 3rem; margin-bottom: 16px; }
411
- #profile-page-username { font-size: 1.5rem; font-weight: 600; margin-bottom: 8px; }
412
- #profile-page-address { font-size: 0.9rem; color: var(--text-secondary); word-break: break-all; margin-bottom: 24px; }
413
- #profile-page-qr { background: white; padding: 10px; margin-bottom: 24px; border-radius: 8px; }
414
- .username-form { display: flex; gap: 8px; width: 100%; max-width: 300px; margin-top: 20px;}
415
  .username-input {
416
- flex-grow: 1; background-color: var(--bg-tertiary);
417
- border: 1px solid var(--border-color); color: var(--text-primary);
418
- border-radius: 6px; padding: 10px 12px; font-size: 1rem;
 
 
 
 
419
  }
420
  .username-input:focus { outline: none; border-color: var(--accent-blue); }
421
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
  .modal-overlay {
423
- position: fixed; top: 0; left: 0; width: 100%; height: 100%;
424
- background-color: rgba(0,0,0,0.7); display: none;
425
- align-items: center; justify-content: center; z-index: 1000;
426
- -webkit-backdrop-filter: blur(5px); backdrop-filter: blur(5px);
 
 
 
 
 
 
 
427
  }
428
  .modal-content {
429
- background-color: var(--bg-secondary); padding: 24px; border-radius: 12px;
430
- width: 90%; max-width: 400px; border: 1px solid var(--border-color);
 
 
 
 
431
  box-shadow: 0 10px 30px rgba(0,0,0,0.5);
432
  }
433
  .modal-content h3 { margin-bottom: 20px; font-weight: 600; font-size: 1.3rem; }
434
  .modal-content label { display: block; margin-bottom: 8px; font-size: 0.9rem; color: var(--text-secondary); }
435
  .modal-content input {
436
- width: 100%; padding: 12px; margin-bottom: 16px; background-color: var(--bg-tertiary);
437
- border: 1px solid var(--border-color); color: white; border-radius: 6px; font-size: 1rem;
 
 
 
 
 
 
438
  }
439
  .modal-actions { display: flex; justify-content: flex-end; gap: 12px; margin-top: 8px; }
440
  .modal-btn {
441
- padding: 10px 20px; border-radius: 6px; border: none; cursor: pointer;
 
 
 
442
  font-weight: 500;
443
  }
444
  .secondary-btn { background-color: var(--bg-hover); color: white; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
445
 
446
  #status-bar {
447
- position: fixed; bottom: 80px; left: 50%; transform: translateX(-50%);
448
- background-color: var(--bg-tertiary); color: white; padding: 12px 20px;
449
- border-radius: 8px; font-size: 0.9rem; opacity: 0; visibility: hidden;
450
- transition: opacity 0.3s, visibility 0.3s, bottom 0.3s; z-index: 2000;
 
 
 
 
 
 
 
 
 
451
  box-shadow: 0 5px 15px rgba(0,0,0,0.3);
452
  }
453
  #status-bar.success { background-color: var(--success-color); }
454
  #status-bar.error { background-color: var(--error-color); }
455
  #status-bar.visible { opacity: 1; visibility: visible; }
456
 
457
- .side-nav { display: none; }
458
-
459
  @media (min-width: 768px) {
460
- body {
461
- background-color: #0F0F11;
462
- }
463
- .app-container, .main-container {
464
- flex-direction: row;
465
  max-width: 1200px;
466
- max-height: 90vh;
467
  border-radius: 12px;
468
  overflow: hidden;
469
  box-shadow: 0 10px 40px rgba(0,0,0,0.3);
470
  border: 1px solid var(--border-color);
471
  }
472
- .bottom-nav { display: none; }
473
- .side-nav {
474
- display: flex;
475
- flex-direction: column;
476
- width: 70px;
477
- background-color: var(--bg-secondary);
478
- border-right: 1px solid var(--border-color);
479
- padding: 16px 0;
480
- align-items: center;
481
- gap: 16px;
482
- }
483
- .side-nav .nav-item {
484
- width: 100%;
485
- color: var(--text-secondary);
486
- padding: 12px 0;
487
- }
488
- .side-nav .nav-item.active {
489
- color: var(--accent-blue);
490
- background-color: var(--bg-hover);
491
- }
492
- .side-nav .nav-item#nav-scan-qr { transform: none; }
493
- .side-nav .nav-item#nav-scan-qr .scan-button {
494
- width: 48px; height: 48px; border: none;
495
- }
496
- .side-nav .nav-item#nav-scan-qr .scan-button svg { width: 24px; height: 24px; }
497
-
498
- #chats-page-content { flex-direction: row; }
499
  #chatroom-list-view {
500
- width: 320px;
501
  flex-shrink: 0;
502
- border-right: 1px solid var(--border-color);
503
- display: flex !important;
504
  }
505
  #chat-window-view {
506
- width: auto;
507
- flex-grow: 1;
508
- display: flex !important;
 
509
  }
510
  .back-btn { display: none !important; }
 
511
 
512
- #status-bar { bottom: 20px; }
 
 
 
 
 
 
 
 
 
513
  }
514
  </style>
515
  </head>
@@ -521,111 +553,102 @@ def index():
521
  <p>Децентрализованный и анонимный мессенджер</p>
522
  <div id="ton-connect-button"></div>
523
  </div>
524
-
525
- <div id="app-container">
526
- <nav class="side-nav">
527
- <a class="nav-item" data-view="chats">
528
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/></svg>
529
- <span>Чаты</span>
530
- </a>
531
- <a class="nav-item" data-view="users">
532
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>
533
- <span>Люди</span>
534
- </a>
535
- <a class="nav-item" id="nav-scan-qr">
536
- <div class="scan-button"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 11h8V3H3v8zm2-6h4v4H5V5zM3 21h8v-8H3v8zm2-6h4v4H5v-4zm8-12v8h8V3h-8zm6 6h-4V5h4v4zm-2 10a2 2 0 100-4 2 2 0 000 4z"/></svg></div>
537
- </a>
538
- <a class="nav-item" data-view="browser">
539
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></svg>
540
- <span>Браузер</span>
541
- </a>
542
- <a class="nav-item" data-view="profile">
543
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>
544
- <span>Профиль</span>
545
- </a>
546
- </nav>
547
- <div class="main-content">
548
  <div id="chats-page" class="page active">
549
- <div id="chats-page-content">
550
- <div id="chatroom-list-view">
551
- <div class="list-header">
552
  <h2>Чаты</h2>
553
- <button id="create-room-show-modal" class="action-btn">
554
- <svg xmlns="http://www.w3.org/2000/svg" height="18" viewBox="0 0 24 24" width="18" fill="white"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
555
- </button>
556
- </div>
557
- <div id="chatroom-list"></div>
558
- </div>
559
- <div id="chat-window-view">
560
- <div id="chat-placeholder" class="chat-placeholder">
561
- <img src="https://ton.org/download/ton_symbol.svg" alt="TON Symbol">
562
- <h2>Выберите чат</h2>
563
- <p>Начните общение в одном из существующих чатов или создайте свой собственный.</p>
564
- </div>
565
- <div id="active-chat" style="display: none; width: 100%; height: 100%; flex-direction: column;">
566
- <div class="chat-header">
567
- <button class="back-btn" id="back-to-list-btn">
568
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M15.41 7.41 14 6l-6 6 6 6 1.41-1.41L10.83 12z"/></svg>
569
  </button>
570
- <div id="chat-header-avatar" class="avatar"></div>
571
- <span id="chat-header-title"></span>
572
  </div>
573
- <div id="messages-container"></div>
574
- <form id="message-form" class="message-form">
575
- <input type="text" id="message-input" placeholder="Сообщение..." autocomplete="off">
576
- <button type="submit" class="action-btn send-btn" id="send-btn">
577
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
578
- </button>
579
- </form>
580
  </div>
581
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
582
  </div>
583
  </div>
 
584
  <div id="users-page" class="page">
585
- <div class="page-header"><h2>��юди</h2></div>
586
- <div id="users-list-container" class="content-area"></div>
 
 
 
 
 
 
587
  </div>
 
588
  <div id="browser-page" class="page">
589
- <form class="browser-header" id="browser-form">
590
- <input type="text" id="url-input" class="username-input" placeholder="Введите URL или поисковый запрос">
591
- <button type="submit" class="action-btn">Go</button>
592
- </form>
593
- <iframe id="browser-frame" src="https://duckduckgo.com/html"></iframe>
594
  </div>
 
595
  <div id="profile-page" class="page">
596
- <div class="page-header"><h2>Профиль</h2></div>
597
- <div class="content-area">
598
- <div id="profile-page-avatar" class="avatar"></div>
599
- <h3 id="profile-page-username"></h3>
600
- <p id="profile-page-address"></p>
601
- <div id="profile-page-qr"></div>
602
- <form class="username-form" id="username-form">
603
- <input type="text" id="username-input" class="username-input" placeholder="Ваш никнейм" autocomplete="off">
604
- <button type="submit" class="action-btn">✓</button>
605
- </form>
606
- </div>
607
  </div>
 
608
  </div>
609
- <nav class="bottom-nav">
610
- <a class="nav-item" data-view="chats">
611
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/></svg>
 
612
  <span>Чаты</span>
613
- </a>
614
- <a class="nav-item" data-view="users">
615
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>
616
  <span>Люди</span>
617
- </a>
618
- <a class="nav-item" id="nav-scan-qr">
619
- <div class="scan-button"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 11h8V3H3v8zm2-6h4v4H5V5zM3 21h8v-8H3v8zm2-6h4v4H5v-4zm8-12v8h8V3h-8zm6 6h-4V5h4v4zm-2 10a2 2 0 100-4 2 2 0 000 4z"/></svg></div>
620
- </a>
621
- <a class="nav-item" data-view="browser">
622
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></svg>
623
  <span>Браузер</span>
624
- </a>
625
- <a class="nav-item" data-view="profile">
626
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>
627
  <span>Профиль</span>
628
- </a>
629
  </nav>
630
  </div>
631
 
@@ -697,29 +720,29 @@ def index():
697
  let activeChatroomId = null;
698
  let messagePollingInterval = null;
699
  let chatroomsData = {};
 
700
  let html5QrCode = null;
701
  let profileQrCode = null;
702
  let myProfileQrCode = null;
703
 
704
  const loginView = document.getElementById('login-view');
705
- const appContainer = document.getElementById('app-container');
 
706
  const chatroomListView = document.getElementById('chatroom-list-view');
707
  const chatWindowView = document.getElementById('chat-window-view');
708
  const chatPlaceholder = document.getElementById('chat-placeholder');
709
  const activeChat = document.getElementById('active-chat');
710
  const profileModal = document.getElementById('profile-modal');
711
  const scannerModal = document.getElementById('scanner-modal');
712
- const navItems = document.querySelectorAll('.nav-item[data-view]');
713
- const pages = document.querySelectorAll('.page');
714
 
715
  const AVATAR_COLORS = ['#e57373', '#81c784', '#64b5f6', '#ffb74d', '#9575cd', '#4db6ac', '#f06292', '#ff8a65', '#a1887f', '#90a4ae'];
716
 
717
- const getAvatar = (name, sizeClass = '') => {
718
  const initial = (name ? name[0] : '?').toUpperCase();
719
  const charCode = initial.charCodeAt(0);
720
  const color = AVATAR_COLORS[charCode % AVATAR_COLORS.length];
721
  const avatar = document.createElement('div');
722
- avatar.className = 'avatar ' + sizeClass;
723
  avatar.style.backgroundColor = color;
724
  avatar.textContent = initial;
725
  return avatar;
@@ -751,12 +774,42 @@ def index():
751
  }
752
  };
753
 
754
- const truncateAddress = (address) => address ? `${address.substring(0, 6)}...${address.substring(address.length - 6)}` : '';
 
 
 
 
 
 
 
 
755
 
756
- const updateUserInfo = () => {
757
- if (!currentUser.address) return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
758
  document.getElementById('username-input').value = currentUser.username || '';
759
- renderMyProfilePage();
 
 
 
 
 
 
 
 
760
  };
761
 
762
  document.getElementById('username-form').addEventListener('submit', async (e) => {
@@ -773,8 +826,8 @@ def index():
773
  body: JSON.stringify({ address: currentUser.address, username: newUsername })
774
  });
775
  currentUser.username = newUsername;
776
- updateUserInfo();
777
  showStatus('Никнейм успешно обновлен!', 'success');
 
778
  fetchChatrooms();
779
  if (activeChatroomId) fetchMessages(activeChatroomId);
780
  } catch (err) {}
@@ -792,11 +845,11 @@ def index():
792
  } catch (err) {
793
  currentUser.username = null;
794
  }
795
- updateUserInfo();
796
  loginView.style.display = 'none';
797
- appContainer.style.display = 'flex';
798
- switchView('chats');
799
  fetchChatrooms();
 
 
800
  };
801
 
802
  const renderChatrooms = (rooms) => {
@@ -806,7 +859,7 @@ def index():
806
  rooms.forEach(room => {
807
  chatroomsData[room.id] = room;
808
  const item = document.createElement('div');
809
- item.className = 'chatroom-item';
810
  item.dataset.id = room.id;
811
 
812
  item.appendChild(getAvatar(room.name));
@@ -824,7 +877,6 @@ def index():
824
  lockIcon.innerHTML = `<svg class="lock-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zM9 8V6c0-1.66 1.34-3 3-3s3 1.34 3 3v2H9z"/></svg>`;
825
  item.appendChild(lockIcon);
826
  }
827
-
828
  item.addEventListener('click', () => selectChatroom(room.id, room.is_private));
829
  list.appendChild(item);
830
  });
@@ -837,40 +889,73 @@ def index():
837
  } catch (err) {}
838
  };
839
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
840
  const renderMessages = (messages) => {
841
  const container = document.getElementById('messages-container');
842
- const shouldScroll = container.scrollTop + container.clientHeight >= container.scrollHeight - 50;
843
  container.innerHTML = '';
844
  messages.forEach(msg => {
845
- const isSent = msg.sender_address === currentUser.address;
846
  const msgDiv = document.createElement('div');
847
- msgDiv.className = 'message ' + (isSent ? 'sent' : 'received');
848
 
 
 
 
 
849
  const contentDiv = document.createElement('div');
850
  contentDiv.className = 'message-content';
851
 
 
 
 
 
 
852
  const bubbleDiv = document.createElement('div');
853
  bubbleDiv.className = 'message-bubble';
854
  bubbleDiv.textContent = msg.text;
855
 
856
- if (!isSent) {
857
- const senderDiv = document.createElement('div');
858
- senderDiv.className = 'message-sender';
859
- senderDiv.textContent = msg.display_name;
860
- senderDiv.onclick = () => showProfile(msg.sender_address);
861
- contentDiv.appendChild(senderDiv);
862
- }
863
-
864
  contentDiv.appendChild(bubbleDiv);
865
- msgDiv.appendChild(contentDiv);
866
-
867
- if (!isSent) {
868
- const avatar = getAvatar(msg.display_name);
869
- avatar.style.cursor = 'pointer';
870
- avatar.onclick = () => showProfile(msg.sender_address);
871
- msgDiv.appendChild(avatar);
872
- }
873
 
 
 
874
  container.appendChild(msgDiv);
875
  });
876
 
@@ -890,16 +975,13 @@ def index():
890
 
891
  const showChatView = () => {
892
  chatWindowView.style.display = 'flex';
893
- if (window.innerWidth < 768) {
894
- chatroomListView.style.display = 'none';
895
- }
896
  };
897
 
898
  const showListView = () => {
899
- chatroomListView.style.display = 'flex';
900
- if (window.innerWidth < 768) {
901
- chatWindowView.style.display = 'none';
902
- }
903
  };
904
 
905
  const selectChatroom = (roomId, isPrivate) => {
@@ -962,7 +1044,6 @@ def index():
962
  const sendBtn = document.getElementById('send-btn');
963
  const text = input.value.trim();
964
  if (text && activeChatroomId) {
965
- const originalText = text;
966
  input.value = '';
967
  input.disabled = true;
968
  sendBtn.disabled = true;
@@ -978,10 +1059,7 @@ def index():
978
  });
979
  await fetchMessages(activeChatroomId);
980
  document.getElementById('messages-container').scrollTop = document.getElementById('messages-container').scrollHeight;
981
- } catch(err) {
982
- input.value = originalText;
983
- }
984
- finally {
985
  input.disabled = false;
986
  sendBtn.disabled = false;
987
  input.focus();
@@ -1016,6 +1094,10 @@ def index():
1016
  });
1017
 
1018
  const showProfile = async (address) => {
 
 
 
 
1019
  try {
1020
  const userData = await apiCall('/api/user_data', {
1021
  method: 'POST',
@@ -1026,43 +1108,32 @@ def index():
1026
  const username = userData.username || `User ${truncateAddress(address)}`;
1027
  const avatarContainer = document.getElementById('profile-avatar-container');
1028
 
 
1029
  document.getElementById('profile-username').textContent = username;
1030
  document.getElementById('profile-address').textContent = address;
1031
 
1032
  avatarContainer.innerHTML = '';
1033
- avatarContainer.appendChild(getAvatar(username));
1034
 
1035
  const qrCodeEl = document.getElementById('profile-qr-code');
1036
  qrCodeEl.innerHTML = '';
1037
  if (profileQrCode) profileQrCode.clear();
1038
- profileQrCode = new QRCode(qrCodeEl, {
1039
- text: address, width: 150, height: 150,
1040
- colorDark : "#000000", colorLight : "#ffffff",
1041
- correctLevel : QRCode.CorrectLevel.H
1042
- });
1043
 
1044
- const sendTonBtn = document.getElementById('send-ton-btn');
1045
- sendTonBtn.onclick = async () => {
1046
  if (!tonConnectUI.connected) {
1047
- showStatus('Подключите кошелек для отправки TON.', 'error');
1048
- return;
1049
  }
1050
  const amountString = prompt("Введите сумму в TON для отправки:", "0.1");
1051
  if (amountString === null) return;
1052
-
1053
  const amount = parseFloat(amountString);
1054
  if (isNaN(amount) || amount <= 0) {
1055
- showStatus('Неверная сумма.', 'error');
1056
- return;
1057
  }
1058
-
1059
- const amountInNanoTon = Math.floor(amount * 1_000_000_000).toString();
1060
-
1061
  const transaction = {
1062
  validUntil: Math.floor(Date.now() / 1000) + 600,
1063
- messages: [ { address: address, amount: amountInNanoTon } ]
1064
  };
1065
-
1066
  try {
1067
  await tonConnectUI.sendTransaction(transaction);
1068
  showStatus(`Транзакция отправлена успешно!`, 'success');
@@ -1104,111 +1175,36 @@ def index():
1104
  scannerModal.style.display = 'none';
1105
  };
1106
 
1107
- document.querySelectorAll('#nav-scan-qr').forEach(el => el.addEventListener('click', showScanner));
1108
  document.getElementById('profile-close-btn').addEventListener('click', () => profileModal.style.display = 'none');
1109
  document.getElementById('scanner-close-btn').addEventListener('click', hideScanner);
1110
  document.getElementById('back-to-list-btn').addEventListener('click', showListView);
1111
 
1112
- const switchView = (viewName) => {
1113
- pages.forEach(p => p.classList.remove('active'));
1114
- document.getElementById(`${viewName}-page`).classList.add('active');
1115
-
1116
- navItems.forEach(n => n.classList.remove('active'));
1117
- document.querySelectorAll(`.nav-item[data-view="${viewName}"]`).forEach(n => n.classList.add('active'));
1118
-
1119
- if(viewName === 'users') fetchAndRenderUsers();
1120
- if(viewName === 'profile') renderMyProfilePage();
1121
- };
1122
-
1123
- navItems.forEach(item => {
1124
- item.addEventListener('click', () => switchView(item.dataset.view));
1125
- });
1126
-
1127
- const fetchAndRenderUsers = async () => {
1128
- try {
1129
- const data = await apiCall('/api/users');
1130
- const container = document.getElementById('users-list-container');
1131
- container.innerHTML = '';
1132
- data.users.forEach(user => {
1133
- if (user.address === currentUser.address) return;
1134
-
1135
- const item = document.createElement('div');
1136
- item.className = 'list-item';
1137
- item.onclick = () => showProfile(user.address);
1138
-
1139
- item.appendChild(getAvatar(user.username || user.address));
1140
- const infoDiv = document.createElement('div');
1141
- infoDiv.className = 'item-info';
1142
-
1143
- const nameSpan = document.createElement('div');
1144
- nameSpan.className = 'item-name';
1145
- nameSpan.textContent = user.username || 'Без имени';
1146
- infoDiv.appendChild(nameSpan);
1147
-
1148
- const subtextSpan = document.createElement('div');
1149
- subtextSpan.className = 'item-subtext';
1150
- subtextSpan.textContent = truncateAddress(user.address);
1151
- infoDiv.appendChild(subtextSpan);
1152
-
1153
- item.appendChild(infoDiv);
1154
- container.appendChild(item);
1155
- });
1156
- } catch (err) {}
1157
- };
1158
-
1159
- const renderMyProfilePage = () => {
1160
- const name = currentUser.username || 'Без имени';
1161
- document.getElementById('profile-page-avatar').innerHTML = '';
1162
- document.getElementById('profile-page-avatar').appendChild(getAvatar(name));
1163
- document.getElementById('profile-page-username').textContent = name;
1164
- document.getElementById('profile-page-address').textContent = currentUser.address;
1165
-
1166
- const qrEl = document.getElementById('profile-page-qr');
1167
- qrEl.innerHTML = '';
1168
- if (myProfileQrCode) myProfileQrCode.clear();
1169
- myProfileQrCode = new QRCode(qrEl, {
1170
- text: currentUser.address, width: 200, height: 200,
1171
- colorDark : "#000000", colorLight : "#ffffff",
1172
- correctLevel : QRCode.CorrectLevel.H
1173
- });
1174
- };
1175
-
1176
- document.getElementById('browser-form').addEventListener('submit', (e) => {
1177
- e.preventDefault();
1178
- const input = document.getElementById('url-input').value.trim();
1179
- const frame = document.getElementById('browser-frame');
1180
- let url = input;
1181
- if (!url.startsWith('http://') && !url.startsWith('https://')) {
1182
- if(url.includes('.') && !url.includes(' ')) {
1183
- url = 'https://' + url;
1184
- } else {
1185
- url = `https://duckduckgo.com/html?q=${encodeURIComponent(url)}`;
1186
- }
1187
  }
1188
- frame.src = url;
1189
  });
1190
-
1191
  window.addEventListener('resize', () => {
1192
- if (window.innerWidth >= 768) {
1193
- chatroomListView.style.display = 'flex';
1194
- chatWindowView.style.display = 'flex';
1195
- } else {
1196
- if (activeChat.style.display === 'flex') {
1197
- chatroomListView.style.display = 'none';
1198
- } else {
1199
- chatroomListView.style.display = 'flex';
1200
- chatWindowView.style.display = 'none';
1201
- }
1202
  }
1203
  });
1204
-
1205
  tonConnectUI.onStatusChange(wallet => {
1206
  if (wallet) {
1207
  const address = TON_CONNECT_UI.toUserFriendlyAddress(wallet.account.address, false);
1208
  initializeUser(address);
1209
  } else {
1210
  currentUser = { address: null, username: null };
1211
- appContainer.style.display = 'none';
1212
  loginView.style.display = 'flex';
1213
  if (messagePollingInterval) clearInterval(messagePollingInterval);
1214
  activeChatroomId = null;
@@ -1221,17 +1217,6 @@ def index():
1221
  '''
1222
  return Response(html_content, mimetype='text/html')
1223
 
1224
- @app.route('/api/users', methods=['GET'])
1225
- def get_users():
1226
- db = read_db()
1227
- users_list = []
1228
- for address, user_data in db['users'].items():
1229
- users_list.append({
1230
- 'address': address,
1231
- 'username': user_data.get('username')
1232
- })
1233
- users_list.sort(key=lambda u: (u['username'] is None, u['username']))
1234
- return jsonify({'users': users_list})
1235
 
1236
  @app.route('/api/user_data', methods=['POST'])
1237
  def get_user_data():
@@ -1244,6 +1229,17 @@ def get_user_data():
1244
  username = user_info.get('username') if user_info else None
1245
  return jsonify({'username': username})
1246
 
 
 
 
 
 
 
 
 
 
 
 
1247
  @app.route('/api/set_username', methods=['POST'])
1248
  def set_username():
1249
  data = request.get_json()
 
44
  <style>
45
  :root {
46
  --bg-primary: #000000;
47
+ --bg-secondary: #101010;
48
+ --bg-tertiary: #1C1C1D;
49
+ --bg-hover: #2c2c2e;
50
  --text-primary: #FFFFFF;
51
  --text-secondary: #8E8E93;
52
  --accent-blue: #007AFF;
53
+ --accent-blue-light: #3498DB;
54
+ --border-color: #2A2A2A;
55
  --success-color: #34C759;
56
  --error-color: #FF3B30;
57
  --font-family: 'Inter', sans-serif;
 
73
  background-color: var(--bg-primary);
74
  color: var(--text-primary);
75
  overflow: hidden;
76
+ height: 100vh;
77
+ height: -webkit-fill-available;
78
  width: 100vw;
79
  display: flex;
80
  align-items: center;
81
  justify-content: center;
82
  }
83
+
84
+ .main-container {
85
  width: 100%;
86
  height: 100%;
87
  display: flex;
88
  flex-direction: column;
89
  transition: opacity 0.3s ease;
90
+ background-color: var(--bg-primary);
91
  }
92
 
93
  #login-view {
 
96
  align-items: center;
97
  justify-content: center;
98
  text-align: center;
 
 
99
  background: radial-gradient(circle, #1a2a3a 0%, var(--bg-primary) 70%);
100
  }
101
  #login-view img {
 
105
  filter: drop-shadow(0 0 25px rgba(0, 122, 255, 0.6));
106
  }
107
  #login-view h1 {
108
+ font-size: 3.5rem;
109
  font-weight: 700;
110
  background: linear-gradient(45deg, var(--accent-blue-light), var(--accent-blue));
111
  -webkit-background-clip: text;
 
113
  margin-bottom: 12px;
114
  }
115
  #login-view p {
116
+ font-size: 1.2rem;
117
  color: var(--text-secondary);
118
  margin-bottom: 40px;
119
  }
120
 
121
+ #app-view {
122
  display: none;
123
  }
124
 
125
+ #main-content {
126
  flex-grow: 1;
 
127
  overflow: hidden;
128
+ position: relative;
129
+ display: flex;
130
  }
131
 
132
  .page {
 
135
  left: 0;
136
  width: 100%;
137
  height: 100%;
138
+ display: flex;
 
139
  flex-direction: column;
140
+ opacity: 0;
141
+ visibility: hidden;
142
+ transition: opacity 0.2s ease-in-out, visibility 0.2s;
143
+ background-color: var(--bg-primary);
144
  }
145
  .page.active {
146
+ opacity: 1;
147
+ visibility: visible;
148
+ z-index: 10;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  }
150
 
151
+ #chats-page { flex-direction: row; }
 
 
 
 
152
 
153
+ .list-view {
154
  display: flex;
155
  flex-direction: column;
156
  height: 100%;
157
  width: 100%;
158
+ background-color: var(--bg-secondary);
159
+ border-right: 1px solid var(--border-color);
160
  }
161
+ .page-header {
162
+ padding: 16px;
163
+ border-bottom: 1px solid var(--border-color);
164
+ flex-shrink: 0;
165
+ background: rgba(28, 28, 29, 0.8);
166
+ backdrop-filter: blur(10px);
167
+ -webkit-backdrop-filter: blur(10px);
168
+ z-index: 5;
169
+ }
170
+ .page-header-top {
171
  display: flex;
172
  justify-content: space-between;
173
  align-items: center;
 
 
 
174
  }
175
+ .page-header-top h2 {
176
+ font-size: 1.8rem;
177
+ font-weight: 700;
178
+ }
179
+ .header-actions {
180
+ display: flex; align-items: center; gap: 8px;
181
  }
182
 
183
+ .scrollable-content {
184
+ flex-grow: 1;
185
+ overflow-y: auto;
186
+ }
187
+ .list-item {
188
  display: flex;
189
  align-items: center;
190
  gap: 12px;
 
193
  border-bottom: 1px solid var(--border-color);
194
  transition: background-color 0.2s ease;
195
  }
196
+ .list-item:hover { background-color: var(--bg-hover); }
197
 
198
  .avatar {
199
  width: 48px;
 
202
  display: flex;
203
  align-items: center;
204
  justify-content: center;
 
205
  font-weight: 600;
206
+ font-size: 1.2rem;
207
  color: white;
208
  flex-shrink: 0;
209
  }
 
212
  overflow: hidden;
213
  }
214
  .item-name {
215
+ font-weight: 600;
216
+ font-size: 1.1rem;
217
  white-space: nowrap;
218
  overflow: hidden;
219
  text-overflow: ellipsis;
 
226
  text-overflow: ellipsis;
227
  }
228
  .lock-icon {
229
+ width: 18px;
230
+ height: 18px;
231
  fill: var(--text-secondary);
232
  flex-shrink: 0;
233
  }
234
+
235
  #chat-window-view {
236
  display: none;
237
  flex-direction: column;
238
+ flex-grow: 1;
 
239
  background-color: var(--bg-primary);
240
  }
241
 
 
244
  align-items: center;
245
  gap: 12px;
246
  padding: 12px 16px;
247
+ background-color: var(--bg-tertiary);
 
 
248
  border-bottom: 1px solid var(--border-color);
249
  flex-shrink: 0;
 
 
250
  }
251
  .back-btn {
252
+ background: none;
253
+ border: none;
254
+ cursor: pointer;
255
+ display: none;
256
+ }
257
+ .back-btn svg {
258
+ width: 24px;
259
+ height: 24px;
260
+ fill: var(--text-primary);
261
+ }
262
+ #chat-header-title {
263
+ font-size: 1.2rem;
264
+ font-weight: 600;
265
  }
 
 
266
 
267
  #messages-container {
268
  flex-grow: 1;
 
273
  gap: 12px;
274
  }
275
 
276
+ .message { display: flex; gap: 10px; max-width: 80%; }
277
+ .message .avatar { width: 36px; height: 36px; font-size: 1rem; align-self: flex-end; }
278
+ .message-content { display: flex; flex-direction: column; gap: 4px; }
279
+ .message-sender { font-size: 0.9rem; font-weight: 600; color: var(--text-secondary); word-break: break-all; cursor: pointer; }
280
+ .message-bubble { padding: 10px 14px; border-radius: 18px; line-height: 1.4; word-wrap: break-word; font-size: 1rem;}
281
+ .message.sent { align-self: flex-end; flex-direction: row-reverse; }
282
+ .message.sent .message-sender { text-align: right; color: var(--accent-blue-light); }
283
+ .message.sent .message-bubble { background: var(--accent-blue); color: white; border-bottom-right-radius: 4px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
  .message.received { align-self: flex-start; }
285
+ .message.received .message-bubble { background-color: var(--bg-tertiary); border-bottom-left-radius: 4px; }
 
 
 
 
 
 
286
 
287
  .chat-placeholder {
288
+ display: flex;
289
+ flex-direction: column;
290
+ align-items: center;
291
+ justify-content: center;
292
+ height: 100%;
293
+ text-align: center;
294
+ color: var(--text-secondary);
295
+ padding: 20px;
296
+ background-color: var(--bg-primary);
297
+ flex-grow: 1;
298
  }
299
  .chat-placeholder img { width: 80px; margin-bottom: 20px; opacity: 0.5; }
300
 
301
  .message-form {
302
+ display: flex;
303
+ padding: 12px 16px;
304
+ gap: 12px;
305
  background-color: var(--bg-secondary);
306
  border-top: 1px solid var(--border-color);
307
  flex-shrink: 0;
308
  }
309
  #message-input {
310
+ flex-grow: 1;
311
+ padding: 12px 18px;
312
+ border: none;
313
  background-color: var(--bg-tertiary);
314
  color: var(--text-primary);
315
+ border-radius: 22px;
316
+ outline: none;
317
+ font-size: 1rem;
318
  }
319
+ .send-btn {
 
 
320
  background: var(--accent-blue);
321
+ color: white;
322
+ border: none;
323
+ width: 44px;
324
+ height: 44px;
325
+ border-radius: 50%;
326
+ cursor: pointer;
 
 
 
 
 
 
 
327
  flex-shrink: 0;
 
 
 
 
 
328
  display: flex;
 
329
  align-items: center;
330
  justify-content: center;
331
+ transition: transform 0.1s ease;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
332
  }
333
+ .send-btn:active { transform: scale(0.9); }
334
+ .send-btn svg { width: 22px; height: 22px; fill: white; margin-left: 2px; }
335
 
336
+ #browser-page { flex-direction: column; background-color: var(--bg-secondary); }
 
 
337
  .browser-header {
338
  display: flex;
339
  gap: 8px;
340
  padding: 12px;
341
  background-color: var(--bg-tertiary);
342
  border-bottom: 1px solid var(--border-color);
343
+ flex-shrink: 0;
344
  }
345
+ #browser-url-input {
346
+ flex-grow: 1;
347
  background-color: var(--bg-primary);
348
  border: 1px solid var(--border-color);
349
+ color: var(--text-primary);
350
+ border-radius: 8px;
351
+ padding: 10px;
352
+ font-size: 1rem;
353
  }
354
  #browser-frame {
355
  flex-grow: 1;
356
  border: none;
357
+ background-color: white;
358
  }
359
+
360
+ #profile-page {
361
+ align-items: center;
362
+ justify-content: center;
363
  padding: 24px;
364
+ overflow-y: auto;
365
  text-align: center;
366
  }
367
+ #my-profile-avatar { margin-bottom: 16px; }
368
+ #my-profile-username { font-size: 1.5rem; font-weight: 600; margin-bottom: 8px; }
369
+ #my-profile-address { font-size: 0.9rem; color: var(--text-secondary); word-break: break-all; margin-bottom: 24px; }
370
+ #my-profile-qr-code { background: white; padding: 10px; margin: 0 auto 24px; width: fit-content; border-radius: 8px; }
371
+ .username-form { display: flex; gap: 8px; margin-top: 24px; width: 100%; max-width: 400px; }
372
  .username-input {
373
+ flex-grow: 1;
374
+ background-color: var(--bg-tertiary);
375
+ border: 1px solid var(--border-color);
376
+ color: var(--text-primary);
377
+ border-radius: 8px;
378
+ padding: 12px;
379
+ font-size: 1rem;
380
  }
381
  .username-input:focus { outline: none; border-color: var(--accent-blue); }
382
 
383
+ #bottom-nav {
384
+ display: flex;
385
+ justify-content: space-around;
386
+ background-color: var(--bg-tertiary);
387
+ border-top: 1px solid var(--border-color);
388
+ flex-shrink: 0;
389
+ padding: 5px 0;
390
+ z-index: 100;
391
+ }
392
+ .nav-btn {
393
+ background: none;
394
+ border: none;
395
+ color: var(--text-secondary);
396
+ cursor: pointer;
397
+ padding: 8px 12px;
398
+ display: flex;
399
+ flex-direction: column;
400
+ align-items: center;
401
+ gap: 4px;
402
+ font-size: 0.75rem;
403
+ transition: color 0.2s;
404
+ }
405
+ .nav-btn svg {
406
+ width: 24px;
407
+ height: 24px;
408
+ fill: var(--text-secondary);
409
+ transition: fill 0.2s;
410
+ }
411
+ .nav-btn.active { color: var(--accent-blue); }
412
+ .nav-btn.active svg { fill: var(--accent-blue); }
413
+ #nav-scan-btn {
414
+ transform: translateY(-20px);
415
+ background: linear-gradient(45deg, var(--accent-blue-light), var(--accent-blue));
416
+ width: 60px;
417
+ height: 60px;
418
+ border-radius: 50%;
419
+ border: 4px solid var(--bg-primary);
420
+ box-shadow: 0 0 20px rgba(0, 122, 255, 0.5);
421
+ }
422
+ #nav-scan-btn svg {
423
+ fill: white;
424
+ width: 30px;
425
+ height: 30px;
426
+ }
427
+
428
  .modal-overlay {
429
+ position: fixed;
430
+ top: 0;
431
+ left: 0;
432
+ width: 100%;
433
+ height: 100%;
434
+ background-color: rgba(0,0,0,0.7);
435
+ display: none;
436
+ align-items: center;
437
+ justify-content: center;
438
+ z-index: 1000;
439
+ backdrop-filter: blur(5px);
440
  }
441
  .modal-content {
442
+ background-color: var(--bg-secondary);
443
+ padding: 24px;
444
+ border-radius: 12px;
445
+ width: 90%;
446
+ max-width: 400px;
447
+ border: 1px solid var(--border-color);
448
  box-shadow: 0 10px 30px rgba(0,0,0,0.5);
449
  }
450
  .modal-content h3 { margin-bottom: 20px; font-weight: 600; font-size: 1.3rem; }
451
  .modal-content label { display: block; margin-bottom: 8px; font-size: 0.9rem; color: var(--text-secondary); }
452
  .modal-content input {
453
+ width: 100%;
454
+ padding: 12px;
455
+ margin-bottom: 16px;
456
+ background-color: var(--bg-tertiary);
457
+ border: 1px solid var(--border-color);
458
+ color: white;
459
+ border-radius: 8px;
460
+ font-size: 1rem;
461
  }
462
  .modal-actions { display: flex; justify-content: flex-end; gap: 12px; margin-top: 8px; }
463
  .modal-btn {
464
+ padding: 10px 20px;
465
+ border-radius: 8px;
466
+ border: none;
467
+ cursor: pointer;
468
  font-weight: 500;
469
  }
470
  .secondary-btn { background-color: var(--bg-hover); color: white; }
471
+
472
+ .action-btn {
473
+ background: var(--accent-blue);
474
+ color: white;
475
+ border: none;
476
+ padding: 10px 16px;
477
+ border-radius: 8px;
478
+ cursor: pointer;
479
+ font-weight: 500;
480
+ transition: background-color 0.2s ease;
481
+ display: flex;
482
+ align-items: center;
483
+ justify-content: center;
484
+ gap: 8px;
485
+ }
486
+ .action-btn:hover { background-color: var(--accent-blue-light); }
487
+ .action-btn.small {
488
+ padding: 8px 12px;
489
+ font-size: 0.9rem;
490
+ border-radius: 8px;
491
+ }
492
 
493
  #status-bar {
494
+ position: fixed;
495
+ bottom: 80px;
496
+ left: 50%;
497
+ transform: translateX(-50%);
498
+ background-color: var(--bg-tertiary);
499
+ color: white;
500
+ padding: 12px 20px;
501
+ border-radius: 8px;
502
+ font-size: 0.9rem;
503
+ opacity: 0;
504
+ visibility: hidden;
505
+ transition: all 0.3s;
506
+ z-index: 2000;
507
  box-shadow: 0 5px 15px rgba(0,0,0,0.3);
508
  }
509
  #status-bar.success { background-color: var(--success-color); }
510
  #status-bar.error { background-color: var(--error-color); }
511
  #status-bar.visible { opacity: 1; visibility: visible; }
512
 
 
 
513
  @media (min-width: 768px) {
514
+ .main-container {
 
 
 
 
515
  max-width: 1200px;
516
+ max-height: 850px;
517
  border-radius: 12px;
518
  overflow: hidden;
519
  box-shadow: 0 10px 40px rgba(0,0,0,0.3);
520
  border: 1px solid var(--border-color);
521
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
522
  #chatroom-list-view {
523
+ width: 350px;
524
  flex-shrink: 0;
 
 
525
  }
526
  #chat-window-view {
527
+ display: flex;
528
+ }
529
+ #chats-page.chat-active #chatroom-list-view {
530
+ display: flex;
531
  }
532
  .back-btn { display: none !important; }
533
+ }
534
 
535
+ @media (max-width: 767px) {
536
+ #chats-page.chat-active #chatroom-list-view {
537
+ display: none;
538
+ }
539
+ #chat-window-view {
540
+ width: 100%;
541
+ }
542
+ .back-btn {
543
+ display: block;
544
+ }
545
  }
546
  </style>
547
  </head>
 
553
  <p>Децентрализованный и анонимный мессенджер</p>
554
  <div id="ton-connect-button"></div>
555
  </div>
556
+
557
+ <div id="app-view" class="main-container">
558
+ <div id="main-content">
559
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
560
  <div id="chats-page" class="page active">
561
+ <div id="chatroom-list-view" class="list-view">
562
+ <div class="page-header">
563
+ <div class="page-header-top">
564
  <h2>Чаты</h2>
565
+ <div class="header-actions">
566
+ <button id="create-room-show-modal" class="action-btn small">
567
+ <svg xmlns="http://www.w3.org/2000/svg" height="16" viewBox="0 0 24 24" width="16" fill="white"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
 
 
 
 
 
 
 
 
 
 
 
 
 
568
  </button>
 
 
569
  </div>
 
 
 
 
 
 
 
570
  </div>
571
  </div>
572
+ <div id="chatroom-list" class="scrollable-content"></div>
573
+ </div>
574
+
575
+ <div id="chat-window-view">
576
+ <div id="chat-placeholder" class="chat-placeholder">
577
+ <img src="https://ton.org/download/ton_symbol.svg" alt="TON Symbol">
578
+ <h2>Выберите чат</h2>
579
+ <p>Начните общение в одном из существующих чатов или создайте свой.</p>
580
+ </div>
581
+ <div id="active-chat" style="display: none; width: 100%; height: 100%; flex-direction: column;">
582
+ <div class="chat-header">
583
+ <button class="back-btn" id="back-to-list-btn">
584
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
585
+ </button>
586
+ <div id="chat-header-avatar" class="avatar"></div>
587
+ <span id="chat-header-title"></span>
588
+ </div>
589
+ <div id="messages-container"></div>
590
+ <form id="message-form" class="message-form">
591
+ <input type="text" id="message-input" placeholder="Сообщение..." autocomplete="off">
592
+ <button type="submit" class="send-btn" id="send-btn">
593
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
594
+ </button>
595
+ </form>
596
+ </div>
597
  </div>
598
  </div>
599
+
600
  <div id="users-page" class="page">
601
+ <div class="list-view">
602
+ <div class="page-header">
603
+ <div class="page-header-top">
604
+ <h2>Пользователи</h2>
605
+ </div>
606
+ </div>
607
+ <div id="user-list" class="scrollable-content"></div>
608
+ </div>
609
  </div>
610
+
611
  <div id="browser-page" class="page">
612
+ <div class="browser-header">
613
+ <input type="text" id="browser-url-input" placeholder="https://">
614
+ <button id="browser-go-btn" class="action-btn small">Go</button>
615
+ </div>
616
+ <iframe id="browser-frame" src="https://www.google.com/search?igu=1"></iframe>
617
  </div>
618
+
619
  <div id="profile-page" class="page">
620
+ <div id="my-profile-avatar" class="avatar" style="width: 100px; height: 100px; font-size: 3rem;"></div>
621
+ <h3 id="my-profile-username"></h3>
622
+ <p id="my-profile-address"></p>
623
+ <div id="my-profile-qr-code"></div>
624
+ <form class="username-form" id="username-form">
625
+ <input type="text" id="username-input" class="username-input" placeholder="Ваш никнейм" autocomplete="off">
626
+ <button type="submit" class="action-btn small">✓</button>
627
+ </form>
 
 
 
628
  </div>
629
+
630
  </div>
631
+
632
+ <nav id="bottom-nav">
633
+ <button class="nav-btn active" data-page="chats-page">
634
+ <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg>
635
  <span>Чаты</span>
636
+ </button>
637
+ <button class="nav-btn" data-page="users-page">
638
+ <svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px"><g><rect fill="none" height="24" width="24"/></g><g><g><path d="M16,11c1.66,0,2.99-1.34,2.99-3S17.66,5,16,5C14.34,5,13,6.34,13,8S14.34,11,16,11z M8,11C9.66,11,11,9.66,11,8S9.66,5,8,5 C6.34,5,5,6.34,5,8S6.34,11,8,11z M8,13c-2.33,0-7,1.17-7,3.5V19h14v-2.5C15,14.17,10.33,13,8,13z M16,13c-0.29,0-0.62,0.02-0.97,0.05 c1.16,0.84,1.97,1.97,1.97,3.45V19h6v-2.5C23,14.17,18.33,13,16,13z"/></g></g></svg>
639
  <span>Люди</span>
640
+ </button>
641
+ <button class="nav-btn" id="nav-scan-btn">
642
+ <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M3 11h8V3H3v8zm2-6h4v4H5V5zM3 21h8v-8H3v8zm2-6h4v4H5v-4zm8-12v8h8V3h-8zm6 6h-4V5h4v4zm-2 10a2 2 0 100-4 2 2 0 000 4z"/></svg>
643
+ </button>
644
+ <button class="nav-btn" data-page="browser-page">
645
+ <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></svg>
646
  <span>Браузер</span>
647
+ </button>
648
+ <button class="nav-btn" data-page="profile-page">
649
+ <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>
650
  <span>Профиль</span>
651
+ </button>
652
  </nav>
653
  </div>
654
 
 
720
  let activeChatroomId = null;
721
  let messagePollingInterval = null;
722
  let chatroomsData = {};
723
+ let usersData = {};
724
  let html5QrCode = null;
725
  let profileQrCode = null;
726
  let myProfileQrCode = null;
727
 
728
  const loginView = document.getElementById('login-view');
729
+ const appView = document.getElementById('app-view');
730
+ const chatsPage = document.getElementById('chats-page');
731
  const chatroomListView = document.getElementById('chatroom-list-view');
732
  const chatWindowView = document.getElementById('chat-window-view');
733
  const chatPlaceholder = document.getElementById('chat-placeholder');
734
  const activeChat = document.getElementById('active-chat');
735
  const profileModal = document.getElementById('profile-modal');
736
  const scannerModal = document.getElementById('scanner-modal');
 
 
737
 
738
  const AVATAR_COLORS = ['#e57373', '#81c784', '#64b5f6', '#ffb74d', '#9575cd', '#4db6ac', '#f06292', '#ff8a65', '#a1887f', '#90a4ae'];
739
 
740
+ const getAvatar = (name, className = 'avatar') => {
741
  const initial = (name ? name[0] : '?').toUpperCase();
742
  const charCode = initial.charCodeAt(0);
743
  const color = AVATAR_COLORS[charCode % AVATAR_COLORS.length];
744
  const avatar = document.createElement('div');
745
+ avatar.className = className;
746
  avatar.style.backgroundColor = color;
747
  avatar.textContent = initial;
748
  return avatar;
 
774
  }
775
  };
776
 
777
+ const truncateAddress = (address) => address ? `${address.substring(0, 4)}...${address.substring(address.length - 4)}` : '';
778
+
779
+ const showPage = (pageId) => {
780
+ document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
781
+ document.getElementById(pageId)?.classList.add('active');
782
+
783
+ document.querySelectorAll('.nav-btn').forEach(b => b.classList.remove('active'));
784
+ const navButton = document.querySelector(`.nav-btn[data-page="${pageId}"]`);
785
+ if(navButton) navButton.classList.add('active');
786
 
787
+ if(pageId === 'profile-page') renderMyProfile();
788
+ if(pageId === 'users-page') fetchUsers();
789
+ };
790
+
791
+ document.querySelectorAll('.nav-btn[data-page]').forEach(button => {
792
+ button.addEventListener('click', () => showPage(button.dataset.page));
793
+ });
794
+
795
+ const renderMyProfile = () => {
796
+ if(!currentUser.address) return;
797
+
798
+ const avatarContainer = document.getElementById('my-profile-avatar');
799
+ avatarContainer.innerHTML = '';
800
+ avatarContainer.appendChild(getAvatar(currentUser.username || `?`, 'avatar'));
801
+ document.getElementById('my-profile-username').textContent = currentUser.username || 'Никнейм не установлен';
802
+ document.getElementById('my-profile-address').textContent = currentUser.address;
803
  document.getElementById('username-input').value = currentUser.username || '';
804
+
805
+ const qrCodeEl = document.getElementById('my-profile-qr-code');
806
+ qrCodeEl.innerHTML = '';
807
+ if (myProfileQrCode) myProfileQrCode.clear();
808
+ myProfileQrCode = new QRCode(qrCodeEl, {
809
+ text: currentUser.address,
810
+ width: 200, height: 200, colorDark : "#000000", colorLight : "#ffffff",
811
+ correctLevel : QRCode.CorrectLevel.H
812
+ });
813
  };
814
 
815
  document.getElementById('username-form').addEventListener('submit', async (e) => {
 
826
  body: JSON.stringify({ address: currentUser.address, username: newUsername })
827
  });
828
  currentUser.username = newUsername;
 
829
  showStatus('Никнейм успешно обновлен!', 'success');
830
+ renderMyProfile();
831
  fetchChatrooms();
832
  if (activeChatroomId) fetchMessages(activeChatroomId);
833
  } catch (err) {}
 
845
  } catch (err) {
846
  currentUser.username = null;
847
  }
 
848
  loginView.style.display = 'none';
849
+ appView.style.display = 'flex';
 
850
  fetchChatrooms();
851
+ fetchUsers();
852
+ showPage('chats-page');
853
  };
854
 
855
  const renderChatrooms = (rooms) => {
 
859
  rooms.forEach(room => {
860
  chatroomsData[room.id] = room;
861
  const item = document.createElement('div');
862
+ item.className = 'list-item';
863
  item.dataset.id = room.id;
864
 
865
  item.appendChild(getAvatar(room.name));
 
877
  lockIcon.innerHTML = `<svg class="lock-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zM9 8V6c0-1.66 1.34-3 3-3s3 1.34 3 3v2H9z"/></svg>`;
878
  item.appendChild(lockIcon);
879
  }
 
880
  item.addEventListener('click', () => selectChatroom(room.id, room.is_private));
881
  list.appendChild(item);
882
  });
 
889
  } catch (err) {}
890
  };
891
 
892
+ const renderUsers = (users) => {
893
+ const list = document.getElementById('user-list');
894
+ list.innerHTML = '';
895
+ usersData = {};
896
+ users.forEach(user => {
897
+ usersData[user.address] = user;
898
+ const displayName = user.username || `User ${truncateAddress(user.address)}`;
899
+ const item = document.createElement('div');
900
+ item.className = 'list-item';
901
+ item.dataset.address = user.address;
902
+
903
+ item.appendChild(getAvatar(displayName));
904
+
905
+ const infoDiv = document.createElement('div');
906
+ infoDiv.className = 'item-info';
907
+ const nameSpan = document.createElement('div');
908
+ nameSpan.className = 'item-name';
909
+ nameSpan.textContent = displayName;
910
+ infoDiv.appendChild(nameSpan);
911
+
912
+ const subtext = document.createElement('div');
913
+ subtext.className = 'item-subtext';
914
+ subtext.textContent = truncateAddress(user.address);
915
+ infoDiv.appendChild(subtext);
916
+ item.appendChild(infoDiv);
917
+
918
+ item.addEventListener('click', () => showProfile(user.address));
919
+ list.appendChild(item);
920
+ });
921
+ }
922
+
923
+ const fetchUsers = async () => {
924
+ try {
925
+ const data = await apiCall('/api/users');
926
+ renderUsers(data.users);
927
+ } catch (err) {}
928
+ }
929
+
930
  const renderMessages = (messages) => {
931
  const container = document.getElementById('messages-container');
932
+ const shouldScroll = container.scrollTop + container.clientHeight >= container.scrollHeight - 30;
933
  container.innerHTML = '';
934
  messages.forEach(msg => {
 
935
  const msgDiv = document.createElement('div');
936
+ msgDiv.className = 'message ' + (msg.sender_address === currentUser.address ? 'sent' : 'received');
937
 
938
+ const avatar = getAvatar(msg.display_name, 'avatar message-avatar');
939
+ avatar.style.cursor = 'pointer';
940
+ avatar.onclick = () => showProfile(msg.sender_address);
941
+
942
  const contentDiv = document.createElement('div');
943
  contentDiv.className = 'message-content';
944
 
945
+ const senderDiv = document.createElement('div');
946
+ senderDiv.className = 'message-sender';
947
+ senderDiv.textContent = msg.display_name;
948
+ senderDiv.onclick = () => showProfile(msg.sender_address);
949
+
950
  const bubbleDiv = document.createElement('div');
951
  bubbleDiv.className = 'message-bubble';
952
  bubbleDiv.textContent = msg.text;
953
 
954
+ contentDiv.appendChild(senderDiv);
 
 
 
 
 
 
 
955
  contentDiv.appendChild(bubbleDiv);
 
 
 
 
 
 
 
 
956
 
957
+ msgDiv.appendChild(contentDiv);
958
+ msgDiv.insertBefore(avatar, contentDiv);
959
  container.appendChild(msgDiv);
960
  });
961
 
 
975
 
976
  const showChatView = () => {
977
  chatWindowView.style.display = 'flex';
978
+ chatsPage.classList.add('chat-active');
 
 
979
  };
980
 
981
  const showListView = () => {
982
+ chatsPage.classList.remove('chat-active');
983
+ if (messagePollingInterval) clearInterval(messagePollingInterval);
984
+ activeChatroomId = null;
 
985
  };
986
 
987
  const selectChatroom = (roomId, isPrivate) => {
 
1044
  const sendBtn = document.getElementById('send-btn');
1045
  const text = input.value.trim();
1046
  if (text && activeChatroomId) {
 
1047
  input.value = '';
1048
  input.disabled = true;
1049
  sendBtn.disabled = true;
 
1059
  });
1060
  await fetchMessages(activeChatroomId);
1061
  document.getElementById('messages-container').scrollTop = document.getElementById('messages-container').scrollHeight;
1062
+ } finally {
 
 
 
1063
  input.disabled = false;
1064
  sendBtn.disabled = false;
1065
  input.focus();
 
1094
  });
1095
 
1096
  const showProfile = async (address) => {
1097
+ if (address === currentUser.address) {
1098
+ showPage('profile-page');
1099
+ return;
1100
+ }
1101
  try {
1102
  const userData = await apiCall('/api/user_data', {
1103
  method: 'POST',
 
1108
  const username = userData.username || `User ${truncateAddress(address)}`;
1109
  const avatarContainer = document.getElementById('profile-avatar-container');
1110
 
1111
+ document.getElementById('profile-modal-title').textContent = username;
1112
  document.getElementById('profile-username').textContent = username;
1113
  document.getElementById('profile-address').textContent = address;
1114
 
1115
  avatarContainer.innerHTML = '';
1116
+ avatarContainer.appendChild(getAvatar(username, 'avatar'));
1117
 
1118
  const qrCodeEl = document.getElementById('profile-qr-code');
1119
  qrCodeEl.innerHTML = '';
1120
  if (profileQrCode) profileQrCode.clear();
1121
+ profileQrCode = new QRCode(qrCodeEl, { text: address, width: 150, height: 150 });
 
 
 
 
1122
 
1123
+ document.getElementById('send-ton-btn').onclick = async () => {
 
1124
  if (!tonConnectUI.connected) {
1125
+ showStatus('Подключите кошелек для отправки TON.', 'error'); return;
 
1126
  }
1127
  const amountString = prompt("Введите сумму в TON для отправки:", "0.1");
1128
  if (amountString === null) return;
 
1129
  const amount = parseFloat(amountString);
1130
  if (isNaN(amount) || amount <= 0) {
1131
+ showStatus('Неверная сумма.', 'error'); return;
 
1132
  }
 
 
 
1133
  const transaction = {
1134
  validUntil: Math.floor(Date.now() / 1000) + 600,
1135
+ messages: [ { address: address, amount: Math.floor(amount * 1e9).toString() } ]
1136
  };
 
1137
  try {
1138
  await tonConnectUI.sendTransaction(transaction);
1139
  showStatus(`Транзакция отправлена успешно!`, 'success');
 
1175
  scannerModal.style.display = 'none';
1176
  };
1177
 
1178
+ document.getElementById('nav-scan-btn').addEventListener('click', showScanner);
1179
  document.getElementById('profile-close-btn').addEventListener('click', () => profileModal.style.display = 'none');
1180
  document.getElementById('scanner-close-btn').addEventListener('click', hideScanner);
1181
  document.getElementById('back-to-list-btn').addEventListener('click', showListView);
1182
 
1183
+ document.getElementById('browser-go-btn').addEventListener('click', () => {
1184
+ const input = document.getElementById('browser-url-input');
1185
+ let url = input.value.trim();
1186
+ if(url && !url.startsWith('http')) {
1187
+ url = 'https://' + url;
1188
+ }
1189
+ if(url) {
1190
+ document.getElementById('browser-frame').src = url;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1191
  }
 
1192
  });
1193
+
1194
  window.addEventListener('resize', () => {
1195
+ const isDesktop = window.innerWidth >= 768;
1196
+ if(isDesktop) {
1197
+ chatsPage.classList.remove('chat-active');
 
 
 
 
 
 
 
1198
  }
1199
  });
1200
+
1201
  tonConnectUI.onStatusChange(wallet => {
1202
  if (wallet) {
1203
  const address = TON_CONNECT_UI.toUserFriendlyAddress(wallet.account.address, false);
1204
  initializeUser(address);
1205
  } else {
1206
  currentUser = { address: null, username: null };
1207
+ appView.style.display = 'none';
1208
  loginView.style.display = 'flex';
1209
  if (messagePollingInterval) clearInterval(messagePollingInterval);
1210
  activeChatroomId = null;
 
1217
  '''
1218
  return Response(html_content, mimetype='text/html')
1219
 
 
 
 
 
 
 
 
 
 
 
 
1220
 
1221
  @app.route('/api/user_data', methods=['POST'])
1222
  def get_user_data():
 
1229
  username = user_info.get('username') if user_info else None
1230
  return jsonify({'username': username})
1231
 
1232
+ @app.route('/api/users', methods=['GET'])
1233
+ def get_users():
1234
+ db = read_db()
1235
+ users_list = []
1236
+ for address, user_data in db['users'].items():
1237
+ users_list.append({
1238
+ 'address': address,
1239
+ 'username': user_data.get('username')
1240
+ })
1241
+ return jsonify({'users': users_list})
1242
+
1243
  @app.route('/api/set_username', methods=['POST'])
1244
  def set_username():
1245
  data = request.get_json()