Hectozar commited on
Commit
b497bfa
·
verified ·
1 Parent(s): 63561d1

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1573 -588
index.html CHANGED
@@ -1,612 +1,1597 @@
1
  <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
- <meta charset="UTF-8">
5
- <title>Liquid Chat</title>
6
- <style>
7
- :root {
8
- --glass-bg: rgba(255, 255, 255, 0.15);
9
- --glass-border: rgba(255, 255, 255, 0.4);
10
- --glass-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.2);
11
- --apple-blue: rgba(10, 132, 255, 0.8);
12
- --apple-red: rgba(255, 59, 48, 0.8);
13
- --text-dark: #1d1d1f;
14
- --sidebar-width: 280px;
15
- }
16
- * { box-sizing: border-box; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }
17
- body { background: linear-gradient(135deg, #e0c3fc 0%, #8ec5fc 100%); height: 100vh; display: flex; justify-content: center; align-items: center; color: var(--text-dark); overflow: hidden; }
18
-
19
- /* Layout & Panels */
20
- .glass-panel { background: var(--glass-bg); backdrop-filter: blur(25px); -webkit-backdrop-filter: blur(25px); border-radius: 20px; border: 1px solid var(--glass-border); box-shadow: var(--glass-shadow); width: 98vw; height: 96vh; display: flex; overflow: hidden; position: relative; }
21
- #auth-screen { display: flex; flex-direction: column; width: 100%; height: 100%; justify-content: center; align-items: center; text-align: center; background: rgba(255,255,255,0.2); z-index: 100; position: absolute; top:0; left:0; }
22
- .auth-box { background: rgba(255,255,255,0.4); padding: 40px; border-radius: 20px; border: 1px solid var(--glass-border); width: 400px; box-shadow: 0 10px 30px rgba(0,0,0,0.1); }
23
- #main-app { display: none; width: 100%; height: 100%; flex-direction: row; }
24
-
25
- /* Sidebar */
26
- .sidebar { width: var(--sidebar-width); border-right: 1px solid var(--glass-border); background: rgba(255,255,255,0.1); display: flex; flex-direction: column; padding: 20px; }
27
- .sidebar-header { font-size: 20px; font-weight: bold; margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center; }
28
- .user-profile { display: flex; align-items: center; gap: 10px; margin-bottom: 20px; padding: 10px; background: rgba(255,255,255,0.2); border-radius: 12px; }
29
- .user-profile .avatar { width: 35px; height: 35px; background: var(--apple-blue); color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; }
30
-
31
- /* Changelog */
32
- .changelog-container { background: rgba(255,255,255,0.2); padding: 15px; border-radius: 15px; margin-bottom: 20px; font-size: 13px; border: 1px solid var(--glass-border); max-height: 150px; overflow-y: auto; }
33
- .changelog-container h4 { margin-bottom: 5px; opacity: 0.8; }
34
- #changelog-content { outline: none; white-space: pre-wrap; }
35
- #changelog-content[contenteditable="true"] { background: rgba(255,255,255,0.5); padding: 5px; border-radius: 5px; }
36
-
37
- /* Content Area */
38
- .content-area { flex: 1; display: flex; flex-direction: column; position: relative; }
39
- #setup-screen { display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100%; padding: 40px; text-align: center; }
40
- #setup-screen h2 { margin-bottom: 25px; font-size: 32px; font-weight: 600; color: rgba(0,0,0,0.8); }
41
- .setup-box { width: 100%; max-width: 400px; }
42
-
43
- /* Inputs & Buttons */
44
- input { width: 100%; padding: 16px; margin-bottom: 15px; border-radius: 12px; border: 1px solid var(--glass-border); background: rgba(255, 255, 255, 0.4); font-size: 16px; outline: none; transition: all 0.3s ease; }
45
- input:focus { background: rgba(255, 255, 255, 0.7); box-shadow: 0 0 10px rgba(255, 255, 255, 0.5); }
46
- button { width: 100%; padding: 16px; border-radius: 12px; border: none; background: var(--apple-blue); color: white; font-size: 16px; font-weight: 600; cursor: pointer; transition: 0.2s; box-shadow: 0 4px 15px rgba(10, 132, 255, 0.2); }
47
- button:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(10, 132, 255, 0.4); }
48
- button:active { transform: scale(0.98); }
49
- button.secondary { background: rgba(255,255,255,0.3); color: var(--text-dark); border: 1px solid var(--glass-border); box-shadow: none; }
50
- button.secondary:hover { background: rgba(255,255,255,0.5); }
51
- button.danger { background: var(--apple-red); box-shadow: 0 4px 15px rgba(255, 59, 48, 0.2); }
52
-
53
- /* Recent Rooms */
54
- .recent-list { margin-top: 20px; display: flex; flex-direction: column; gap: 8px; width: 100%; }
55
- .recent-btn { background: rgba(255,255,255,0.3); color: var(--text-dark); text-align: left; padding: 12px 15px; display: flex; justify-content: space-between; align-items: center; border-radius: 10px; box-shadow: none; position: relative; }
56
- .recent-btn:hover { background: rgba(255,255,255,0.6); }
57
- .recent-delete { position: absolute; right: 10px; background: rgba(255,0,0,0.1); color: red; padding: 5px 10px; border-radius: 8px; display: none; font-size: 12px; cursor: pointer; }
58
- .recent-btn:hover .recent-delete { display: block; }
59
- .recent-delete:hover { background: rgba(255,0,0,0.3); }
60
-
61
- /* Chat Screen */
62
- #chat-screen { display: none; flex-direction: column; height: 100%; width: 100%; }
63
- .header { padding: 15px 25px; border-bottom: 1px solid var(--glass-border); display: flex; align-items: center; background: rgba(255, 255, 255, 0.2); justify-content: space-between; }
64
- .header-left { display: flex; align-items: center; gap: 15px; }
65
- .header-title { font-weight: 600; font-size: 20px; }
66
- .settings-btn { background: rgba(0,0,0,0.05); border-radius: 50%; width: 40px; height: 40px; display: flex; justify-content: center; align-items: center; cursor: pointer; border: 1px solid var(--glass-border); font-size: 20px; transition: 0.2s; padding: 0; box-shadow: none; }
67
- .settings-btn:hover { background: rgba(255,255,255,0.5); transform: rotate(45deg); }
68
-
69
- .messages-container { flex: 1; padding: 25px; overflow-y: auto; display: flex; flex-direction: column; gap: 15px; scroll-behavior: smooth; }
70
-
71
- /* Chat Bubbles & Hovers */
72
- .msg-wrapper { display: flex; flex-direction: column; max-width: 75%; position: relative; margin-bottom: 5px; }
73
- .msg-wrapper.received { align-self: flex-start; }
74
- .msg-wrapper.sent { align-self: flex-end; align-items: flex-end; }
75
-
76
- .bubble { padding: 12px 18px; border-radius: 18px; font-size: 15px; line-height: 1.4; backdrop-filter: blur(15px); border: 1px solid var(--glass-border); box-shadow: 0 2px 10px rgba(0,0,0,0.02); word-wrap: break-word; position: relative; }
77
- .msg-wrapper.received .bubble { background: rgba(255, 255, 255, 0.6); border-bottom-left-radius: 4px; }
78
- .msg-wrapper.sent .bubble { background: var(--apple-blue); color: white; border-bottom-right-radius: 4px; border-color: rgba(255,255,255,0.2); }
79
-
80
- /* Message Hover Actions */
81
- .msg-actions { position: absolute; top: -15px; background: white; border-radius: 20px; border: 1px solid #ddd; display: none; gap: 5px; padding: 2px 5px; box-shadow: 0 4px 10px rgba(0,0,0,0.1); z-index: 10; white-space: nowrap; }
82
- .msg-wrapper.received .msg-actions { right: -10px; }
83
- .msg-wrapper.sent .msg-actions { left: -10px; }
84
- .msg-wrapper:hover .msg-actions { display: flex; }
85
- .action-icon { cursor: pointer; padding: 4px 8px; font-size: 13px; border-radius: 10px; transition: 0.2s; opacity: 0.7; }
86
- .action-icon:hover { background: rgba(0,0,0,0.05); opacity: 1; }
87
-
88
- .reactions-bar { display: flex; gap: 5px; margin-top: 2px; }
89
- .reaction { background: rgba(255,255,255,0.5); border: 1px solid var(--glass-border); border-radius: 10px; padding: 2px 6px; font-size: 12px; cursor: pointer; }
90
-
91
- .reply-preview { font-size: 12px; background: rgba(0,0,0,0.05); padding: 5px 10px; border-radius: 10px; margin-bottom: 5px; opacity: 0.8; border-left: 3px solid var(--apple-blue); }
92
-
93
- /* Voting Message */
94
- .vote-msg { align-self: center; background: rgba(255, 59, 48, 0.1); border: 1px solid rgba(255, 59, 48, 0.4); padding: 15px 25px; border-radius: 15px; text-align: center; width: 80%; max-width: 400px; color: #d32f2f; font-weight: bold; margin: 15px 0; }
95
- .vote-buttons { display: flex; gap: 10px; margin-top: 10px; }
96
-
97
- /* Input Area */
98
- .input-wrapper { background: rgba(255, 255, 255, 0.3); border-top: 1px solid var(--glass-border); display: flex; flex-direction: column; }
99
- .active-reply-bar { display: none; background: rgba(255,255,255,0.5); padding: 10px 20px; font-size: 13px; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--glass-border); }
100
- .active-reply-bar span { opacity: 0.7; }
101
- .cancel-reply { cursor: pointer; font-weight: bold; color: var(--apple-red); }
102
-
103
- .input-area { padding: 15px 20px; display: flex; gap: 10px; align-items: center; }
104
- .plus-btn { background: rgba(0,0,0,0.05); border-radius: 50%; width: 45px; height: 45px; display: flex; justify-content: center; align-items: center; cursor: pointer; font-size: 24px; color: var(--text-dark); transition: 0.2s; box-shadow: none; padding:0; flex-shrink:0;}
105
- .plus-btn:hover { background: rgba(0,0,0,0.1); }
106
- .input-area input { margin-bottom: 0; border-radius: 25px; height: 50px; flex: 1; padding: 0 20px; }
107
- .send-btn { width: 50px; height: 50px; border-radius: 50%; padding: 0; display: flex; justify-content: center; align-items: center; flex-shrink: 0; }
108
- .send-icon { fill: white; width: 20px; height: 20px; transform: translateX(2px); }
109
-
110
- /* Custom Modals (No Alerts) */
111
- .modal-overlay { position: absolute; top:0; left:0; width: 100%; height: 100%; background: rgba(0,0,0,0.4); backdrop-filter: blur(5px); display: none; justify-content: center; align-items: center; z-index: 1000; opacity: 0; transition: opacity 0.3s; }
112
- .modal-box { background: rgba(255,255,255,0.9); padding: 30px; border-radius: 20px; width: 350px; text-align: center; box-shadow: 0 10px 40px rgba(0,0,0,0.2); transform: scale(0.9); transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); }
113
- .modal-overlay.active { display: flex; opacity: 1; }
114
- .modal-overlay.active .modal-box { transform: scale(1); }
115
- .modal-title { font-size: 20px; font-weight: bold; margin-bottom: 10px; }
116
- .modal-text { font-size: 15px; opacity: 0.8; margin-bottom: 25px; }
117
- .modal-buttons { display: flex; gap: 10px; }
118
-
119
- /* Settings Menu */
120
- .settings-menu { position: absolute; top: 70px; right: 20px; background: rgba(255,255,255,0.95); border: 1px solid var(--glass-border); border-radius: 15px; box-shadow: 0 10px 30px rgba(0,0,0,0.1); width: 200px; display: none; flex-direction: column; overflow: hidden; z-index: 50; }
121
- .settings-menu.active { display: flex; }
122
- .settings-item { padding: 15px; font-size: 15px; cursor: pointer; border-bottom: 1px solid rgba(0,0,0,0.05); }
123
- .settings-item:hover { background: rgba(0,0,0,0.05); }
124
- .settings-item.danger { color: var(--apple-red); font-weight: bold; }
125
-
126
- /* Attachments Menu */
127
- .attach-menu { position: absolute; bottom: 80px; left: 20px; background: rgba(255,255,255,0.95); border: 1px solid var(--glass-border); border-radius: 15px; box-shadow: 0 10px 30px rgba(0,0,0,0.1); display: none; flex-direction: column; z-index: 50; width: 180px; }
128
- .attach-menu.active { display: flex; }
129
-
130
- .attached-image { max-width: 100%; border-radius: 10px; margin-top: 5px; }
131
-
132
- /* Confetti */
133
- .confetti { position: absolute; width: 10px; height: 10px; background: #f00; animation: fall 3s linear forwards; z-index: 9999; pointer-events: none; }
134
- @keyframes fall { to { transform: translateY(100vh) rotate(720deg); opacity: 0; } }
135
-
136
- ::-webkit-scrollbar { width: 6px; }
137
- ::-webkit-scrollbar-track { background: transparent; }
138
- ::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.15); border-radius: 10px; }
139
- </style>
140
- </head>
141
- <body>
142
 
143
- <div class="glass-panel">
144
-
145
- <!-- Auth Screen -->
146
- <div id="auth-screen">
147
- <div class="auth-box">
148
- <h2 style="margin-bottom: 20px;">Liquid Chat</h2>
149
- <input type="text" id="authName" placeholder="Username" autocomplete="off">
150
- <input type="password" id="authPass" placeholder="Password (Optional)" autocomplete="off">
151
- <button onclick="login()">Log In / Sign Up</button>
152
- </div>
153
- </div>
154
 
155
- <!-- Main App Container -->
156
- <div id="main-app">
157
- <!-- Sidebar -->
158
- <div class="sidebar">
159
- <div class="sidebar-header">
160
- <span>Liquid</span>
161
- <div class="user-profile">
162
- <div class="avatar" id="nav-avatar">U</div>
163
- <span id="nav-name" style="font-size:14px; font-weight:normal;">User</span>
164
- </div>
165
- </div>
166
 
167
- <div class="changelog-container">
168
- <h4>Changelog (Ctrl+Shift+^)</h4>
169
- <div id="changelog-content" contenteditable="false">v2.1
170
- - Added Sidebar & Login
171
- - Added Hover Actions
172
- - Added Reset Chat Voting
173
- - Added Modals (No alerts)</div>
174
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
 
176
- <h4 style="margin-top: 10px; opacity: 0.7;">Recent Rooms</h4>
177
- <div class="recent-list" id="sidebar-recents"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
 
179
- <div style="margin-top: auto;">
180
- <button class="secondary" onclick="logout()" style="padding: 10px; font-size:14px;">Log Out</button>
181
- </div>
182
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
 
184
- <!-- Content Area -->
185
- <div class="content-area">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
 
187
- <!-- Setup Screen -->
188
- <div id="setup-screen">
189
- <div class="setup-box">
190
- <h2>Join a Room</h2>
191
- <input type="text" id="roomCode" placeholder="Secret Chat Code" autocomplete="off">
192
- <button onclick="joinChat()">Enter Chat</button>
193
- </div>
194
- </div>
195
 
196
- <!-- Chat Screen -->
197
- <div id="chat-screen">
198
- <div class="header">
199
- <div class="header-left">
200
- <button class="settings-btn" onclick="goBack()" style="font-size: 16px;">◄</button>
201
- <div>
202
- <div class="header-title" id="chat-title">Room</div>
203
- <div style="font-size: 12px; opacity: 0.6;" id="chat-subtitle">Online</div>
204
- </div>
205
- </div>
206
- <button class="settings-btn" onclick="toggleSettings()">⚙</button>
207
- </div>
208
-
209
- <!-- Settings Dropdown -->
210
- <div class="settings-menu" id="settings-menu">
211
- <div class="settings-item" onclick="showPeople()">👥 See People</div>
212
- <div class="settings-item danger" onclick="initiateReset()">⚠️ Reset Chat</div>
213
- </div>
214
-
215
- <div class="messages-container" id="chat-box"></div>
216
-
217
- <!-- Input Area -->
218
- <div class="input-wrapper">
219
- <div class="active-reply-bar" id="reply-bar">
220
- <span>Replying to <b id="reply-to-name">User</b>: <i id="reply-to-text">Text</i></span>
221
- <div class="cancel-reply" onclick="cancelReply()">✕</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  </div>
 
 
 
 
223
 
224
- <div class="input-area">
225
- <button class="plus-btn" onclick="toggleAttachMenu()">+</button>
226
- <!-- Attach Menu -->
227
- <div class="attach-menu" id="attach-menu">
228
- <div class="settings-item" onclick="attachImage()">📷 Add Image</div>
229
- <div class="settings-item" onclick="attachLink()">🔗 Add Link</div>
230
- </div>
231
-
232
- <input type="text" id="messageInput" placeholder="Type a message..." onkeypress="handleKeyPress(event)" autocomplete="off">
233
- <button class="send-btn" onclick="sendMsg()">
234
- <svg class="send-icon" viewBox="0 0 24 24"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
235
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  </div>
237
- </div>
238
- </div>
239
 
240
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  </div>
242
- </div>
243
-
244
- <!-- Custom Modal Overlay -->
245
- <div class="modal-overlay" id="custom-modal">
246
- <div class="modal-box">
247
- <div class="modal-title" id="modal-title">Title</div>
248
- <div class="modal-text" id="modal-text">Text</div>
249
- <div class="modal-buttons" id="modal-buttons"></div>
250
  </div>
251
- </div>
252
-
253
- <script>
254
- // State Variables
255
- let myName = "";
256
- let myRoom = "";
257
- let savedRooms = [];
258
- let currentReplyTo = null;
259
- let pollInterval;
260
- let attachedImgData = null;
261
- let attachedLinkData = null;
262
-
263
- // --- Mock Backend for Static Hugging Face ---
264
- // If google.script.run is undefined, we use LocalStorage to simulate the backend.
265
- const MockBackend = {
266
- getDB: () => JSON.parse(localStorage.getItem('liquid_mock_db') || '{}'),
267
- saveDB: (db) => localStorage.setItem('liquid_mock_db', JSON.stringify(db)),
268
- sendMessage: (room, sender, text, replyData, imgData, linkData, isVoteMsg = false) => {
269
- let db = MockBackend.getDB();
270
- if (!db[room]) db[room] = { messages: [], users: [], resetVote: null };
271
- if (!db[room].users.includes(sender)) db[room].users.push(sender);
272
-
273
- let msgObj = {
274
- id: Date.now().toString(),
275
- sender, text, replyData, imgData, linkData, isVoteMsg,
276
- time: new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}),
277
- timestamp: Date.now(),
278
- reactions: []
279
- };
280
- db[room].messages.push(msgObj);
281
- MockBackend.saveDB(db);
282
- return db[room].messages;
283
- },
284
- getMessages: (room) => {
285
- let db = MockBackend.getDB();
286
- return db[room] ? db[room].messages : [];
287
- },
288
- deleteMessage: (room, id) => {
289
- let db = MockBackend.getDB();
290
- if(db[room]) {
291
- db[room].messages = db[room].messages.filter(m => m.id !== id);
292
- MockBackend.saveDB(db);
293
- }
294
- },
295
- addReaction: (room, id, emoji) => {
296
- let db = MockBackend.getDB();
297
- let msg = db[room].messages.find(m => m.id === id);
298
- if(msg) { msg.reactions.push(emoji); MockBackend.saveDB(db); }
299
- },
300
- initResetVote: (room, sender) => {
301
- let db = MockBackend.getDB();
302
- if(!db[room]) return;
303
- db[room].resetVote = { initiator: sender, agrees: [sender], totalNeeded: db[room].users.length };
304
- MockBackend.sendMessage(room, 'SYSTEM', `${sender} wants to reset this chat.`, null, null, null, true);
305
- MockBackend.saveDB(db);
306
- },
307
- voteReset: (room, user, agree) => {
308
- let db = MockBackend.getDB();
309
- if(!db[room] || !db[room].resetVote) return;
310
- if(agree && !db[room].resetVote.agrees.includes(user)) {
311
- db[room].resetVote.agrees.push(user);
312
- }
313
- MockBackend.saveDB(db);
314
-
315
- // If everyone agrees
316
- if(db[room].resetVote.agrees.length >= db[room].resetVote.totalNeeded) {
317
- db[room].messages = []; // Clear
318
- db[room].resetVote = null;
319
- MockBackend.saveDB(db);
320
- return true; // Trigger reset UI
321
- }
322
- return false;
323
- },
324
- getPeople: (room) => {
325
- let db = MockBackend.getDB();
326
- return db[room] ? db[room].users : [];
327
- }
328
- };
329
-
330
- // --- Initialization & Auth ---
331
- window.onload = function() {
332
- const storedName = localStorage.getItem('liquid_name');
333
- const cl = localStorage.getItem('liquid_changelog');
334
- if (cl) document.getElementById('changelog-content').innerText = cl;
335
-
336
- if (storedName) {
337
- myName = storedName;
338
- showMainApp();
339
- }
340
-
341
- // Changelog Edit Listener
342
- document.addEventListener('keydown', (e) => {
343
- if (e.ctrlKey && e.shiftKey && (e.key === '^' || e.key === '6')) {
344
- const el = document.getElementById('changelog-content');
345
- const isEditable = el.getAttribute('contenteditable') === 'true';
346
- el.setAttribute('contenteditable', !isEditable);
347
- if (isEditable) {
348
- localStorage.setItem('liquid_changelog', el.innerText);
349
- showModal("Saved", "Changelog updated.", [{text: "OK", class: "secondary"}]);
350
- } else {
351
- el.focus();
352
- }
353
- }
354
- });
355
- };
356
-
357
- function login() {
358
- const name = document.getElementById('authName').value.trim();
359
- if (!name) return;
360
- myName = name;
361
- localStorage.setItem('liquid_name', myName);
362
- showMainApp();
363
- }
364
-
365
- function logout() {
366
- localStorage.removeItem('liquid_name');
367
- location.reload();
368
- }
369
-
370
- function showMainApp() {
371
- document.getElementById('auth-screen').style.display = 'none';
372
- document.getElementById('main-app').style.display = 'flex';
373
- document.getElementById('nav-name').innerText = myName;
374
- document.getElementById('nav-avatar').innerText = myName.charAt(0).toUpperCase();
375
-
376
- const storedRooms = localStorage.getItem('liquid_rooms');
377
- if (storedRooms) savedRooms = JSON.parse(storedRooms);
378
- renderSidebarRecents();
379
- }
380
-
381
- // --- Custom Modal System ---
382
- function showModal(title, text, buttons) {
383
- document.getElementById('modal-title').innerText = title;
384
- document.getElementById('modal-text').innerText = text;
385
-
386
- const btnContainer = document.getElementById('modal-buttons');
387
- btnContainer.innerHTML = '';
388
-
389
- buttons.forEach(btn => {
390
- const b = document.createElement('button');
391
- b.innerText = btn.text;
392
- if(btn.class) b.className = btn.class;
393
- b.onclick = () => {
394
- document.getElementById('custom-modal').classList.remove('active');
395
- if(btn.action) btn.action();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
396
  };
397
- btnContainer.appendChild(b);
398
- });
399
-
400
- document.getElementById('custom-modal').classList.add('active');
401
- }
402
-
403
- // --- Sidebar & Rooms ---
404
- function renderSidebarRecents() {
405
- const list = document.getElementById('sidebar-recents');
406
- list.innerHTML = '';
407
- savedRooms.forEach(room => {
408
- const div = document.createElement('div');
409
- div.className = "recent-btn";
410
- div.innerHTML = `
411
- <span onclick="quickJoin('${room}')" style="cursor:pointer; flex:1;"># ${room}</span>
412
- <span class="recent-delete" onclick="promptDeleteRoom('${room}')">Del</span>
413
- `;
414
- list.appendChild(div);
415
- });
416
- }
417
-
418
- function promptDeleteRoom(room) {
419
- showModal("Delete Room", `Are you sure you want to remove ${room} from your list? You can rejoin later using the code.`, [
420
- { text: "Cancel", class: "secondary" },
421
- { text: "Delete", class: "danger", action: () => {
422
- savedRooms = savedRooms.filter(r => r !== room);
423
- localStorage.setItem('liquid_rooms', JSON.stringify(savedRooms));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
424
  renderSidebarRecents();
425
- }}
426
- ]);
427
- }
428
-
429
- function quickJoin(room) {
430
- document.getElementById('roomCode').value = room;
431
- joinChat();
432
- }
433
-
434
- function joinChat() {
435
- myRoom = document.getElementById('roomCode').value.trim();
436
- if (!myRoom) return showModal("Error", "Enter a room code.", [{text: "OK"}]);
437
-
438
- if (!savedRooms.includes(myRoom)) {
439
- savedRooms.unshift(myRoom);
440
- localStorage.setItem('liquid_rooms', JSON.stringify(savedRooms));
441
- renderSidebarRecents();
442
- }
443
-
444
- document.getElementById('setup-screen').style.display = 'none';
445
- document.getElementById('chat-screen').style.display = 'flex';
446
- document.getElementById('chat-title').innerText = "#" + myRoom;
447
-
448
- document.getElementById('chat-box').innerHTML = '';
449
- pollServer();
450
- pollInterval = setInterval(pollServer, 2000); // Fast polling for demo
451
- }
452
-
453
- function goBack() {
454
- clearInterval(pollInterval);
455
- document.getElementById('chat-screen').style.display = 'none';
456
- document.getElementById('setup-screen').style.display = 'flex';
457
- }
458
-
459
- function toggleSettings() {
460
- const el = document.getElementById('settings-menu');
461
- el.classList.toggle('active');
462
- }
463
-
464
- // --- Features: People & Reset ---
465
- function showPeople() {
466
- toggleSettings();
467
- const people = MockBackend.getPeople(myRoom);
468
- showModal("Room Members", people.join(", ") || "Just you.", [{text: "Close"}]);
469
- }
470
-
471
- function initiateReset() {
472
- toggleSettings();
473
- MockBackend.initResetVote(myRoom, myName);
474
- pollServer();
475
- }
476
-
477
- function handleVote(agree) {
478
- const triggered = MockBackend.voteReset(myRoom, myName, agree);
479
- if(triggered) {
480
- triggerConfetti();
481
- document.getElementById('chat-box').innerHTML = '<div style="text-align:center; color:green; padding:20px;">All clear! Loading...</div>';
482
- }
483
- pollServer();
484
- }
485
-
486
- function triggerConfetti() {
487
- for(let i=0; i<50; i++) {
488
- let c = document.createElement('div');
489
- c.className = 'confetti';
490
- c.style.left = Math.random() * 100 + 'vw';
491
- c.style.backgroundColor = ['#f00','#0f0','#00f','#ff0'][Math.floor(Math.random()*4)];
492
- c.style.animationDuration = (Math.random() * 2 + 2) + 's';
493
- document.body.appendChild(c);
494
- setTimeout(() => c.remove(), 4000);
495
- }
496
- }
497
-
498
- // --- Attachments & Replies ---
499
- function toggleAttachMenu() { document.getElementById('attach-menu').classList.toggle('active'); }
500
- function attachImage() {
501
- toggleAttachMenu();
502
- const url = prompt("Enter Image URL (File upload mock):", "https://via.placeholder.com/150");
503
- if(url) attachedImgData = url;
504
- }
505
- function attachLink() {
506
- toggleAttachMenu();
507
- const url = prompt("Enter Link URL:");
508
- if(url) attachedLinkData = url;
509
- }
510
-
511
- function setReply(id, name, text) {
512
- currentReplyTo = { id, name, text: text.substring(0,30) + "..." };
513
- document.getElementById('reply-bar').style.display = 'flex';
514
- document.getElementById('reply-to-name').innerText = name;
515
- document.getElementById('reply-to-text').innerText = currentReplyTo.text;
516
- document.getElementById('messageInput').focus();
517
- }
518
- function cancelReply() {
519
- currentReplyTo = null;
520
- document.getElementById('reply-bar').style.display = 'none';
521
- }
522
-
523
- // --- Message Hover Actions ---
524
- function handleReact(id, emoji) {
525
- MockBackend.addReaction(myRoom, id, emoji);
526
- pollServer();
527
- }
528
- function handleDeleteMsg(id, timestamp) {
529
- const now = Date.now();
530
- if(now - timestamp > 10 * 60 * 1000) {
531
- showModal("Error", "You can only delete messages within 10 minutes of sending.", [{text:"OK"}]);
532
- return;
533
- }
534
- MockBackend.deleteMessage(myRoom, id);
535
- pollServer();
536
- }
537
-
538
- // --- Sending & Polling ---
539
- function handleKeyPress(e) { if (e.key === 'Enter') sendMsg(); }
540
-
541
- function sendMsg() {
542
- const input = document.getElementById('messageInput');
543
- const text = input.value.trim();
544
- if (!text && !attachedImgData && !attachedLinkData) return;
545
-
546
- MockBackend.sendMessage(myRoom, myName, text, currentReplyTo, attachedImgData, attachedLinkData);
547
-
548
- input.value = '';
549
- attachedImgData = null;
550
- attachedLinkData = null;
551
- cancelReply();
552
- pollServer();
553
- }
554
-
555
- let lastDataString = "";
556
- function pollServer() {
557
- const messages = MockBackend.getMessages(myRoom);
558
- const dataStr = JSON.stringify(messages);
559
- if(dataStr === lastDataString) return; // Prevent unnecessary re-renders
560
- lastDataString = dataStr;
561
-
562
- const box = document.getElementById('chat-box');
563
- const isScrolledToBottom = box.scrollHeight - box.clientHeight <= box.scrollTop + 50;
564
-
565
- box.innerHTML = '';
566
-
567
- messages.forEach(msg => {
568
- const isMe = (msg.sender === myName);
569
- const wrap = document.createElement('div');
570
- wrap.className = `msg-wrapper ${isMe ? 'sent' : 'received'}`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
571
 
572
- let actionsHtml = `<div class="msg-actions">
573
- <span class="action-icon" onclick="setReply('${msg.id}', '${msg.sender}', '${msg.text.replace(/'/g,"")}')">↩️ Reply</span>
574
- <span class="action-icon" onclick="handleReact('${msg.id}', '❤️')">❤️</span>
575
- <span class="action-icon" onclick="handleReact('${msg.id}', '😂')">😂</span>
576
- ${isMe ? `<span class="action-icon" style="color:red;" onclick="handleDeleteMsg('${msg.id}',${msg.timestamp})">🗑️ Del</span>` : ''}
577
- </div>`;
578
-
579
- let bubbleHtml = `<div class="bubble">`;
580
- if(msg.isVoteMsg) {
581
- bubbleHtml = `<div class="vote-msg">
582
- <div>${msg.text}</div>
583
- <div class="vote-buttons">
584
- <button onclick="handleVote(true)">Agree</button>
585
- <button class="secondary" onclick="handleVote(false)">Disagree</button>
586
- </div>
587
- </div>`;
588
- actionsHtml = ''; // No actions on vote msgs
589
- } else {
590
- if (!isMe) bubbleHtml += `<div style="font-size:12px; font-weight:bold; margin-bottom:2px; opacity:0.8;">${msg.sender}</div>`;
591
- if (msg.replyData) bubbleHtml += `<div class="reply-preview">Replying to <b>${msg.replyData.name}</b>: ${msg.replyData.text}</div>`;
592
- if (msg.imgData) bubbleHtml += `<img src="${msg.imgData}" class="attached-image"><br>`;
593
- if (msg.linkData) bubbleHtml += `<a href="${msg.linkData}" target="_blank" style="color:inherit; text-decoration:underline;">${msg.linkData}</a><br>`;
594
- bubbleHtml += `<div>${msg.text}</div>`;
595
- bubbleHtml += `<div style="font-size:10px; margin-top:5px; opacity:0.6; text-align:right;">${msg.time}</div>`;
596
- if(msg.reactions && msg.reactions.length > 0) {
597
- bubbleHtml += `<div class="reactions-bar">${msg.reactions.map(r => `<span class="reaction">${r}</span>`).join('')}</div>`;
598
- }
599
- bubbleHtml += `</div>`;
600
  }
 
 
601
 
602
- wrap.innerHTML = actionsHtml + bubbleHtml;
603
- box.appendChild(wrap);
604
- });
605
-
606
- if (isScrolledToBottom) {
607
- box.scrollTop = box.scrollHeight;
608
- }
609
- }
610
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
611
  </body>
612
  </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>Liquid Chat - Professional Edition</title>
7
+ <style>
8
+ /* ==========================================================================
9
+ CSS VARIABLES & RESET
10
+ ========================================================================== */
11
+ :root {
12
+ --primary-bg: #f4f7f6;
13
+ --glass-bg: rgba(255, 255, 255, 0.65);
14
+ --glass-border: rgba(255, 255, 255, 0.8);
15
+ --glass-shadow: 0 12px 40px 0 rgba(31, 38, 135, 0.1);
16
+ --apple-blue: #007aff;
17
+ --apple-blue-hover: #005ce6;
18
+ --apple-red: #ff3b30;
19
+ --apple-red-hover: #d63028;
20
+ --text-dark: #1d1d1f;
21
+ --text-muted: #86868b;
22
+ --sidebar-width: 300px;
23
+ --gold-gradient: linear-gradient(135deg, #FFD700 0%, #FFA500 50%, #FF8C00 100%);
24
+ --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
25
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
+ * {
28
+ box-sizing: border-box;
29
+ margin: 0;
30
+ padding: 0;
31
+ font-family: var(--font-family);
32
+ }
 
 
 
 
 
33
 
34
+ body {
35
+ background: linear-gradient(135deg, #e0c3fc 0%, #8ec5fc 100%);
36
+ height: 100vh;
37
+ display: flex;
38
+ justify-content: center;
39
+ align-items: center;
40
+ color: var(--text-dark);
41
+ overflow: hidden;
42
+ position: relative;
43
+ }
 
44
 
45
+ /* ==========================================================================
46
+ LOADING SCREEN
47
+ ========================================================================== */
48
+ #loading-screen {
49
+ position: fixed;
50
+ top: 0;
51
+ left: 0;
52
+ width: 100vw;
53
+ height: 100vh;
54
+ background: #ffffff;
55
+ display: flex;
56
+ flex-direction: column;
57
+ justify-content: center;
58
+ align-items: center;
59
+ z-index: 9999;
60
+ transition: opacity 0.8s ease-in-out, visibility 0.8s;
61
+ }
62
+
63
+ .spinner {
64
+ width: 60px;
65
+ height: 60px;
66
+ border: 4px solid rgba(0, 122, 255, 0.2);
67
+ border-top-color: var(--apple-blue);
68
+ border-radius: 50%;
69
+ animation: spin 1s linear infinite;
70
+ margin-bottom: 20px;
71
+ }
72
+
73
+ @keyframes spin {
74
+ to { transform: rotate(360deg); }
75
+ }
76
+
77
+ .loading-text {
78
+ font-size: 18px;
79
+ font-weight: 500;
80
+ color: var(--text-muted);
81
+ letter-spacing: 1px;
82
+ }
83
+
84
+ /* ==========================================================================
85
+ MAIN LAYOUT & PANELS
86
+ ========================================================================== */
87
+ .glass-panel {
88
+ background: var(--glass-bg);
89
+ backdrop-filter: blur(30px);
90
+ -webkit-backdrop-filter: blur(30px);
91
+ border-radius: 24px;
92
+ border: 1px solid var(--glass-border);
93
+ box-shadow: var(--glass-shadow);
94
+ width: 96vw;
95
+ height: 94vh;
96
+ display: flex;
97
+ overflow: hidden;
98
+ position: relative;
99
+ }
100
+
101
+ #auth-screen {
102
+ display: flex;
103
+ flex-direction: column;
104
+ width: 100%;
105
+ height: 100%;
106
+ justify-content: center;
107
+ align-items: center;
108
+ text-align: center;
109
+ background: rgba(255,255,255,0.3);
110
+ z-index: 100;
111
+ position: absolute;
112
+ top: 0;
113
+ left: 0;
114
+ transition: opacity 0.5s ease;
115
+ }
116
+
117
+ .auth-box {
118
+ background: rgba(255, 255, 255, 0.8);
119
+ padding: 50px 40px;
120
+ border-radius: 24px;
121
+ border: 1px solid var(--glass-border);
122
+ width: 420px;
123
+ box-shadow: 0 15px 35px rgba(0,0,0,0.1);
124
+ backdrop-filter: blur(20px);
125
+ }
126
+
127
+ .auth-box h2 {
128
+ margin-bottom: 30px;
129
+ font-size: 28px;
130
+ font-weight: 600;
131
+ color: var(--text-dark);
132
+ }
133
+
134
+ #main-app {
135
+ display: none;
136
+ width: 100%;
137
+ height: 100%;
138
+ flex-direction: row;
139
+ }
140
+
141
+ /* ==========================================================================
142
+ SIDEBAR
143
+ ========================================================================== */
144
+ .sidebar {
145
+ width: var(--sidebar-width);
146
+ border-right: 1px solid rgba(0, 0, 0, 0.08);
147
+ background: rgba(255, 255, 255, 0.4);
148
+ display: flex;
149
+ flex-direction: column;
150
+ padding: 24px;
151
+ box-sizing: border-box;
152
+ }
153
+
154
+ .sidebar-header {
155
+ font-size: 22px;
156
+ font-weight: 700;
157
+ margin-bottom: 24px;
158
+ display: flex;
159
+ justify-content: space-between;
160
+ align-items: center;
161
+ color: var(--text-dark);
162
+ }
163
+
164
+ .user-profile {
165
+ display: flex;
166
+ align-items: center;
167
+ gap: 12px;
168
+ margin-bottom: 24px;
169
+ padding: 12px;
170
+ background: rgba(255, 255, 255, 0.6);
171
+ border-radius: 16px;
172
+ border: 1px solid rgba(255,255,255,0.8);
173
+ box-shadow: 0 4px 10px rgba(0,0,0,0.03);
174
+ }
175
+
176
+ .user-profile .avatar {
177
+ width: 40px;
178
+ height: 40px;
179
+ background: var(--apple-blue);
180
+ color: white;
181
+ border-radius: 50%;
182
+ display: flex;
183
+ align-items: center;
184
+ justify-content: center;
185
+ font-weight: 600;
186
+ font-size: 16px;
187
+ }
188
+
189
+ .gold-name {
190
+ background: var(--gold-gradient);
191
+ -webkit-background-clip: text;
192
+ -webkit-text-fill-color: transparent;
193
+ font-weight: 800 !important;
194
+ text-shadow: 0px 2px 4px rgba(255, 215, 0, 0.3);
195
+ }
196
+
197
+ /* Changelog */
198
+ .changelog-container {
199
+ background: rgba(255, 255, 255, 0.5);
200
+ padding: 16px;
201
+ border-radius: 16px;
202
+ margin-bottom: 24px;
203
+ font-size: 13px;
204
+ border: 1px solid rgba(255,255,255,0.7);
205
+ max-height: 200px;
206
+ overflow-y: auto;
207
+ color: var(--text-muted);
208
+ }
209
+
210
+ .changelog-container h4 {
211
+ margin-bottom: 8px;
212
+ color: var(--text-dark);
213
+ font-size: 14px;
214
+ }
215
 
216
+ #changelog-content {
217
+ outline: none;
218
+ white-space: pre-wrap;
219
+ line-height: 1.5;
220
+ }
221
+
222
+ #changelog-content[contenteditable="true"] {
223
+ background: rgba(255, 255, 255, 0.8);
224
+ padding: 8px;
225
+ border-radius: 8px;
226
+ border: 1px solid var(--apple-blue);
227
+ color: var(--text-dark);
228
+ }
229
+
230
+ /* Content Area */
231
+ .content-area {
232
+ flex: 1;
233
+ display: flex;
234
+ flex-direction: column;
235
+ position: relative;
236
+ background: rgba(255,255,255,0.2);
237
+ }
238
+
239
+ /* ==========================================================================
240
+ FORMS & BUTTONS
241
+ ========================================================================== */
242
+ .setup-container {
243
+ display: flex;
244
+ flex-direction: column;
245
+ justify-content: center;
246
+ align-items: center;
247
+ height: 100%;
248
+ padding: 40px;
249
+ text-align: center;
250
+ }
251
+
252
+ .setup-box {
253
+ width: 100%;
254
+ max-width: 420px;
255
+ background: rgba(255, 255, 255, 0.7);
256
+ padding: 40px;
257
+ border-radius: 24px;
258
+ border: 1px solid var(--glass-border);
259
+ box-shadow: 0 10px 30px rgba(0,0,0,0.05);
260
+ }
261
 
262
+ .setup-box h2 {
263
+ margin-bottom: 25px;
264
+ font-size: 26px;
265
+ color: var(--text-dark);
266
+ }
267
+
268
+ input {
269
+ width: 100%;
270
+ padding: 16px 20px;
271
+ margin-bottom: 16px;
272
+ border-radius: 14px;
273
+ border: 1px solid rgba(0, 0, 0, 0.1);
274
+ background: rgba(255, 255, 255, 0.9);
275
+ font-size: 16px;
276
+ outline: none;
277
+ transition: all 0.3s ease;
278
+ box-shadow: inset 0 2px 4px rgba(0,0,0,0.02);
279
+ }
280
+
281
+ input:focus {
282
+ border-color: var(--apple-blue);
283
+ box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.2);
284
+ background: #ffffff;
285
+ }
286
+
287
+ button {
288
+ width: 100%;
289
+ padding: 16px;
290
+ border-radius: 14px;
291
+ border: none;
292
+ background: var(--apple-blue);
293
+ color: white;
294
+ font-size: 16px;
295
+ font-weight: 600;
296
+ cursor: pointer;
297
+ transition: all 0.2s ease;
298
+ box-shadow: 0 4px 15px rgba(0, 122, 255, 0.2);
299
+ display: flex;
300
+ justify-content: center;
301
+ align-items: center;
302
+ gap: 8px;
303
+ }
304
+
305
+ button:hover {
306
+ background: var(--apple-blue-hover);
307
+ transform: translateY(-2px);
308
+ box-shadow: 0 6px 20px rgba(0, 122, 255, 0.3);
309
+ }
310
+
311
+ button:active {
312
+ transform: scale(0.98);
313
+ }
314
+
315
+ button.secondary {
316
+ background: rgba(255, 255, 255, 0.8);
317
+ color: var(--text-dark);
318
+ border: 1px solid rgba(0,0,0,0.1);
319
+ box-shadow: 0 2px 5px rgba(0,0,0,0.02);
320
+ }
321
+
322
+ button.secondary:hover {
323
+ background: #ffffff;
324
+ border-color: rgba(0,0,0,0.2);
325
+ }
326
+
327
+ button.danger {
328
+ background: var(--apple-red);
329
+ box-shadow: 0 4px 15px rgba(255, 59, 48, 0.2);
330
+ }
331
+
332
+ button.danger:hover {
333
+ background: var(--apple-red-hover);
334
+ }
335
+
336
+ /* SVGs in buttons */
337
+ .btn-icon {
338
+ width: 20px;
339
+ height: 20px;
340
+ fill: currentColor;
341
+ }
342
+
343
+ /* ==========================================================================
344
+ RECENT ROOMS
345
+ ========================================================================== */
346
+ .recent-list {
347
+ margin-top: 10px;
348
+ display: flex;
349
+ flex-direction: column;
350
+ gap: 10px;
351
+ width: 100%;
352
+ overflow-y: auto;
353
+ flex: 1;
354
+ }
355
+
356
+ .recent-btn {
357
+ background: rgba(255, 255, 255, 0.6);
358
+ color: var(--text-dark);
359
+ text-align: left;
360
+ padding: 14px 16px;
361
+ display: flex;
362
+ justify-content: space-between;
363
+ align-items: center;
364
+ border-radius: 12px;
365
+ box-shadow: none;
366
+ border: 1px solid rgba(255,255,255,0.5);
367
+ font-weight: 500;
368
+ }
369
+
370
+ .recent-btn:hover {
371
+ background: rgba(255, 255, 255, 0.9);
372
+ }
373
+
374
+ .recent-delete {
375
+ background: rgba(255, 59, 48, 0.1);
376
+ color: var(--apple-red);
377
+ padding: 6px 12px;
378
+ border-radius: 8px;
379
+ display: none;
380
+ font-size: 12px;
381
+ cursor: pointer;
382
+ font-weight: 600;
383
+ }
384
 
385
+ .recent-btn:hover .recent-delete {
386
+ display: flex;
387
+ align-items: center;
388
+ gap: 4px;
389
+ }
390
+
391
+ .recent-delete:hover {
392
+ background: rgba(255, 59, 48, 0.2);
393
+ }
394
+
395
+ /* ==========================================================================
396
+ CHAT SCREEN
397
+ ========================================================================== */
398
+ #chat-screen {
399
+ display: none;
400
+ flex-direction: column;
401
+ height: 100%;
402
+ width: 100%;
403
+ }
404
+
405
+ .chat-header {
406
+ padding: 16px 24px;
407
+ border-bottom: 1px solid rgba(0,0,0,0.05);
408
+ display: flex;
409
+ align-items: center;
410
+ background: rgba(255, 255, 255, 0.4);
411
+ justify-content: space-between;
412
+ backdrop-filter: blur(10px);
413
+ }
414
+
415
+ .header-left {
416
+ display: flex;
417
+ align-items: center;
418
+ gap: 16px;
419
+ }
420
+
421
+ .header-title {
422
+ font-weight: 600;
423
+ font-size: 20px;
424
+ color: var(--text-dark);
425
+ }
426
+
427
+ .icon-btn {
428
+ background: rgba(255,255,255,0.5);
429
+ border-radius: 50%;
430
+ width: 44px;
431
+ height: 44px;
432
+ display: flex;
433
+ justify-content: center;
434
+ align-items: center;
435
+ cursor: pointer;
436
+ border: 1px solid rgba(0,0,0,0.05);
437
+ transition: all 0.2s;
438
+ padding: 0;
439
+ box-shadow: 0 2px 5px rgba(0,0,0,0.02);
440
+ color: var(--text-dark);
441
+ }
442
+
443
+ .icon-btn:hover {
444
+ background: #ffffff;
445
+ box-shadow: 0 4px 10px rgba(0,0,0,0.05);
446
+ }
447
+
448
+ .icon-svg {
449
+ width: 22px;
450
+ height: 22px;
451
+ fill: currentColor;
452
+ }
453
+
454
+ .messages-container {
455
+ flex: 1;
456
+ padding: 24px;
457
+ overflow-y: auto;
458
+ display: flex;
459
+ flex-direction: column;
460
+ gap: 16px;
461
+ scroll-behavior: smooth;
462
+ }
463
+
464
+ /* ==========================================================================
465
+ CHAT BUBBLES & INTERACTIONS
466
+ ========================================================================== */
467
+ .msg-wrapper {
468
+ display: flex;
469
+ flex-direction: column;
470
+ max-width: 75%;
471
+ position: relative;
472
+ margin-bottom: 8px;
473
+ animation: fadeIn 0.3s ease;
474
+ }
475
+
476
+ @keyframes fadeIn {
477
+ from { opacity: 0; transform: translateY(10px); }
478
+ to { opacity: 1; transform: translateY(0); }
479
+ }
480
+
481
+ .msg-wrapper.received { align-self: flex-start; }
482
+ .msg-wrapper.sent { align-self: flex-end; align-items: flex-end; }
483
+
484
+ .bubble {
485
+ padding: 14px 20px;
486
+ border-radius: 20px;
487
+ font-size: 15px;
488
+ line-height: 1.5;
489
+ box-shadow: 0 4px 15px rgba(0,0,0,0.04);
490
+ word-wrap: break-word;
491
+ position: relative;
492
+ }
493
+
494
+ .msg-wrapper.received .bubble {
495
+ background: #ffffff;
496
+ border-bottom-left-radius: 4px;
497
+ border: 1px solid rgba(0,0,0,0.05);
498
+ }
499
+
500
+ .msg-wrapper.sent .bubble {
501
+ background: var(--apple-blue);
502
+ color: #ffffff;
503
+ border-bottom-right-radius: 4px;
504
+ }
505
+
506
+ .msg-sender {
507
+ font-size: 13px;
508
+ font-weight: 600;
509
+ margin-bottom: 4px;
510
+ opacity: 0.8;
511
+ color: var(--text-dark);
512
+ }
513
+
514
+ .msg-time {
515
+ font-size: 11px;
516
+ margin-top: 6px;
517
+ opacity: 0.6;
518
+ text-align: right;
519
+ }
520
 
521
+ .msg-wrapper.sent .msg-time {
522
+ color: rgba(255,255,255,0.8);
523
+ }
 
 
 
 
 
524
 
525
+ /* Hover Actions Menu */
526
+ .msg-actions {
527
+ position: absolute;
528
+ top: -20px;
529
+ background: #ffffff;
530
+ border-radius: 20px;
531
+ border: 1px solid rgba(0,0,0,0.1);
532
+ display: none;
533
+ gap: 4px;
534
+ padding: 4px;
535
+ box-shadow: 0 4px 15px rgba(0,0,0,0.1);
536
+ z-index: 10;
537
+ white-space: nowrap;
538
+ }
539
+
540
+ .msg-wrapper.received .msg-actions { right: -10px; }
541
+ .msg-wrapper.sent .msg-actions { left: -10px; }
542
+
543
+ .msg-wrapper:hover .msg-actions {
544
+ display: flex;
545
+ animation: popIn 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
546
+ }
547
+
548
+ @keyframes popIn {
549
+ from { transform: scale(0.8); opacity: 0; }
550
+ to { transform: scale(1); opacity: 1; }
551
+ }
552
+
553
+ .action-item {
554
+ cursor: pointer;
555
+ padding: 6px 10px;
556
+ border-radius: 12px;
557
+ transition: all 0.2s;
558
+ display: flex;
559
+ align-items: center;
560
+ gap: 6px;
561
+ font-size: 13px;
562
+ color: var(--text-muted);
563
+ background: transparent;
564
+ border: none;
565
+ }
566
+
567
+ .action-item:hover {
568
+ background: rgba(0,0,0,0.05);
569
+ color: var(--text-dark);
570
+ }
571
+
572
+ .action-item.delete:hover {
573
+ background: rgba(255,59,48,0.1);
574
+ color: var(--apple-red);
575
+ }
576
+
577
+ .action-icon {
578
+ width: 16px;
579
+ height: 16px;
580
+ fill: currentColor;
581
+ }
582
+
583
+ /* Reactions Bar */
584
+ .reactions-bar {
585
+ display: flex;
586
+ gap: 6px;
587
+ margin-top: 6px;
588
+ flex-wrap: wrap;
589
+ }
590
+
591
+ .reaction-badge {
592
+ background: rgba(0,0,0,0.05);
593
+ border: 1px solid rgba(0,0,0,0.05);
594
+ border-radius: 12px;
595
+ padding: 4px 8px;
596
+ font-size: 12px;
597
+ display: flex;
598
+ align-items: center;
599
+ gap: 4px;
600
+ color: var(--text-dark);
601
+ }
602
+
603
+ .msg-wrapper.sent .reaction-badge {
604
+ background: rgba(255,255,255,0.2);
605
+ border-color: rgba(255,255,255,0.3);
606
+ color: white;
607
+ }
608
+
609
+ /* Reply Preview */
610
+ .reply-preview {
611
+ font-size: 13px;
612
+ background: rgba(0,0,0,0.04);
613
+ padding: 8px 12px;
614
+ border-radius: 10px;
615
+ margin-bottom: 8px;
616
+ border-left: 4px solid var(--apple-blue);
617
+ color: var(--text-muted);
618
+ }
619
+
620
+ .msg-wrapper.sent .reply-preview {
621
+ background: rgba(255,255,255,0.15);
622
+ border-left-color: #ffffff;
623
+ color: rgba(255,255,255,0.9);
624
+ }
625
+
626
+ /* Voting Message */
627
+ .vote-msg {
628
+ align-self: center;
629
+ background: #ffffff;
630
+ border: 1px solid rgba(255, 59, 48, 0.3);
631
+ padding: 20px;
632
+ border-radius: 16px;
633
+ text-align: center;
634
+ width: 80%;
635
+ max-width: 450px;
636
+ box-shadow: 0 10px 25px rgba(255,59,48,0.1);
637
+ margin: 20px 0;
638
+ }
639
+
640
+ .vote-title {
641
+ color: var(--apple-red);
642
+ font-weight: 700;
643
+ font-size: 16px;
644
+ margin-bottom: 12px;
645
+ display: flex;
646
+ align-items: center;
647
+ justify-content: center;
648
+ gap: 8px;
649
+ }
650
+
651
+ .vote-buttons {
652
+ display: flex;
653
+ gap: 12px;
654
+ margin-top: 16px;
655
+ }
656
+
657
+ /* ==========================================================================
658
+ INPUT AREA
659
+ ========================================================================== */
660
+ .input-wrapper {
661
+ background: rgba(255, 255, 255, 0.6);
662
+ border-top: 1px solid rgba(0,0,0,0.08);
663
+ display: flex;
664
+ flex-direction: column;
665
+ backdrop-filter: blur(20px);
666
+ }
667
+
668
+ .active-reply-bar {
669
+ display: none;
670
+ background: #ffffff;
671
+ padding: 12px 24px;
672
+ font-size: 14px;
673
+ justify-content: space-between;
674
+ align-items: center;
675
+ border-bottom: 1px solid rgba(0,0,0,0.05);
676
+ color: var(--text-muted);
677
+ }
678
+
679
+ .cancel-reply {
680
+ cursor: pointer;
681
+ color: var(--apple-red);
682
+ padding: 4px;
683
+ }
684
+
685
+ .input-area {
686
+ padding: 16px 24px;
687
+ display: flex;
688
+ gap: 12px;
689
+ align-items: center;
690
+ }
691
+
692
+ .input-area input {
693
+ margin-bottom: 0;
694
+ border-radius: 25px;
695
+ height: 52px;
696
+ flex: 1;
697
+ padding: 0 20px;
698
+ background: #ffffff;
699
+ border: 1px solid rgba(0,0,0,0.1);
700
+ }
701
+
702
+ .send-btn {
703
+ width: 52px;
704
+ height: 52px;
705
+ border-radius: 50%;
706
+ padding: 0;
707
+ display: flex;
708
+ justify-content: center;
709
+ align-items: center;
710
+ flex-shrink: 0;
711
+ }
712
+
713
+ /* ==========================================================================
714
+ MODALS & MENUS
715
+ ========================================================================== */
716
+ .modal-overlay {
717
+ position: absolute;
718
+ top: 0; left: 0; width: 100%; height: 100%;
719
+ background: rgba(0,0,0,0.3);
720
+ backdrop-filter: blur(8px);
721
+ display: none;
722
+ justify-content: center;
723
+ align-items: center;
724
+ z-index: 1000;
725
+ opacity: 0;
726
+ transition: opacity 0.3s ease;
727
+ }
728
+
729
+ .modal-overlay.active {
730
+ display: flex;
731
+ opacity: 1;
732
+ }
733
+
734
+ .modal-box {
735
+ background: #ffffff;
736
+ padding: 32px;
737
+ border-radius: 24px;
738
+ width: 90%;
739
+ max-width: 400px;
740
+ text-align: center;
741
+ box-shadow: 0 20px 50px rgba(0,0,0,0.2);
742
+ transform: scale(0.95);
743
+ transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
744
+ }
745
+
746
+ .modal-overlay.active .modal-box {
747
+ transform: scale(1);
748
+ }
749
+
750
+ .modal-title {
751
+ font-size: 22px;
752
+ font-weight: 700;
753
+ margin-bottom: 12px;
754
+ color: var(--text-dark);
755
+ }
756
+
757
+ .modal-text {
758
+ font-size: 16px;
759
+ color: var(--text-muted);
760
+ margin-bottom: 24px;
761
+ line-height: 1.5;
762
+ }
763
+
764
+ .modal-buttons {
765
+ display: flex;
766
+ gap: 12px;
767
+ }
768
+
769
+ /* Dropdown Menus */
770
+ .dropdown-menu {
771
+ position: absolute;
772
+ background: #ffffff;
773
+ border: 1px solid rgba(0,0,0,0.1);
774
+ border-radius: 16px;
775
+ box-shadow: 0 10px 30px rgba(0,0,0,0.1);
776
+ display: none;
777
+ flex-direction: column;
778
+ overflow: hidden;
779
+ z-index: 50;
780
+ min-width: 200px;
781
+ }
782
+
783
+ .dropdown-menu.active {
784
+ display: flex;
785
+ animation: fadeIn 0.2s ease;
786
+ }
787
+
788
+ #settings-menu {
789
+ top: 70px;
790
+ right: 24px;
791
+ }
792
+
793
+ #attach-menu {
794
+ bottom: 80px;
795
+ left: 24px;
796
+ }
797
+
798
+ .dropdown-item {
799
+ padding: 16px 20px;
800
+ font-size: 15px;
801
+ cursor: pointer;
802
+ border-bottom: 1px solid rgba(0,0,0,0.05);
803
+ display: flex;
804
+ align-items: center;
805
+ gap: 12px;
806
+ color: var(--text-dark);
807
+ transition: background 0.2s;
808
+ }
809
+
810
+ .dropdown-item:last-child {
811
+ border-bottom: none;
812
+ }
813
+
814
+ .dropdown-item:hover {
815
+ background: rgba(0,0,0,0.03);
816
+ }
817
+
818
+ .dropdown-item.danger {
819
+ color: var(--apple-red);
820
+ font-weight: 600;
821
+ }
822
+
823
+ .dropdown-icon {
824
+ width: 18px;
825
+ height: 18px;
826
+ fill: currentColor;
827
+ }
828
+
829
+ .attached-media {
830
+ max-width: 100%;
831
+ border-radius: 12px;
832
+ margin-top: 8px;
833
+ border: 1px solid rgba(0,0,0,0.1);
834
+ }
835
+
836
+ /* ==========================================================================
837
+ SCROLLBARS & CONFETTI
838
+ ========================================================================== */
839
+ ::-webkit-scrollbar { width: 8px; }
840
+ ::-webkit-scrollbar-track { background: transparent; }
841
+ ::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.2); border-radius: 10px; }
842
+ ::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.3); }
843
+
844
+ .confetti {
845
+ position: absolute;
846
+ width: 12px;
847
+ height: 12px;
848
+ background: #f00;
849
+ animation: fall 3s linear forwards;
850
+ z-index: 9999;
851
+ pointer-events: none;
852
+ border-radius: 2px;
853
+ }
854
+
855
+ @keyframes fall {
856
+ 0% { transform: translateY(-10vh) rotate(0deg); opacity: 1; }
857
+ 100% { transform: translateY(110vh) rotate(720deg); opacity: 0; }
858
+ }
859
+ </style>
860
+ </head>
861
+ <body>
862
+
863
+ <!-- LOADING SCREEN -->
864
+ <div id="loading-screen">
865
+ <div class="spinner"></div>
866
+ <div class="loading-text">Loading Liquid Chat...</div>
867
+ </div>
868
+
869
+ <!-- MAIN APP CONTAINER -->
870
+ <div class="glass-panel">
871
+
872
+ <!-- AUTHENTICATION SCREEN -->
873
+ <div id="auth-screen">
874
+ <div class="auth-box">
875
+ <h2>Welcome to Liquid</h2>
876
+ <input type="text" id="authName" placeholder="Username" autocomplete="off">
877
+ <input type="password" id="authPass" placeholder="Password" autocomplete="off">
878
+ <button onclick="login()">
879
+ <svg class="btn-icon" 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>
880
+ Log In / Sign Up
881
+ </button>
882
  </div>
883
+ </div>
884
+
885
+ <!-- INSIDE THE APP -->
886
+ <div id="main-app">
887
 
888
+ <!-- SIDEBAR -->
889
+ <div class="sidebar">
890
+ <div class="sidebar-header">
891
+ <span>Liquid</span>
892
+ </div>
893
+
894
+ <div class="user-profile">
895
+ <div class="avatar" id="nav-avatar">U</div>
896
+ <div style="display:flex; flex-direction:column;">
897
+ <span id="nav-name" style="font-size:16px; font-weight:600;">User</span>
898
+ <span id="nav-role" style="font-size:12px; color:var(--text-muted);">Member</span>
899
+ </div>
900
+ </div>
901
+
902
+ <div class="changelog-container">
903
+ <h4>Changelog (Ctrl+Shift+^)</h4>
904
+ <div id="changelog-content" contenteditable="false">v3.0 - Professional Update
905
+ - Added secure Room Passwords
906
+ - Implemented Loading Screen
907
+ - Upgraded UI to professional SVGs
908
+ - Added Developer authentication
909
+ - Improved layout & animations</div>
910
+ </div>
911
+
912
+ <h4 style="margin-top: 16px; font-size: 14px; color: var(--text-muted);">Saved Rooms</h4>
913
+ <div class="recent-list" id="sidebar-recents"></div>
914
+
915
+ <div style="margin-top: 24px;">
916
+ <button class="secondary" onclick="logout()">
917
+ <svg class="btn-icon" viewBox="0 0 24 24"><path d="M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.58L17 17l5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4V5z"/></svg>
918
+ Log Out
919
+ </button>
920
+ </div>
921
  </div>
 
 
922
 
923
+ <!-- CONTENT AREA -->
924
+ <div class="content-area">
925
+
926
+ <!-- SETUP ROOM SCREEN -->
927
+ <div id="setup-screen" class="setup-container">
928
+ <div class="setup-box">
929
+ <h2>Join a Room</h2>
930
+ <input type="text" id="roomNameInput" placeholder="Room Name" autocomplete="off">
931
+ <input type="password" id="roomPassInput" placeholder="Room Password" autocomplete="off">
932
+ <button onclick="joinChat()">
933
+ <svg class="btn-icon" viewBox="0 0 24 24"><path d="M20 6h-8l-2-2H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 12H4V8h16v10z"/></svg>
934
+ Enter Room
935
+ </button>
936
+ </div>
937
+ </div>
938
+
939
+ <!-- CHAT SCREEN -->
940
+ <div id="chat-screen">
941
+
942
+ <!-- Chat Header -->
943
+ <div class="chat-header">
944
+ <div class="header-left">
945
+ <button class="icon-btn" onclick="goBack()" aria-label="Go Back">
946
+ <svg class="icon-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>
947
+ </button>
948
+ <div>
949
+ <div class="header-title" id="chat-title">Room</div>
950
+ <div style="font-size: 13px; color: var(--text-muted);" id="chat-subtitle">Secure Connection</div>
951
+ </div>
952
+ </div>
953
+ <button class="icon-btn" onclick="toggleMenu('settings-menu')" aria-label="Settings">
954
+ <svg class="icon-svg" viewBox="0 0 24 24"><path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.73 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.06.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .43-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.49-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/></svg>
955
+ </button>
956
+ </div>
957
+
958
+ <!-- Settings Dropdown -->
959
+ <div class="dropdown-menu" id="settings-menu">
960
+ <div class="dropdown-item" onclick="showPeople()">
961
+ <svg class="dropdown-icon" 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>
962
+ View Members
963
+ </div>
964
+ <div class="dropdown-item danger" onclick="initiateReset()">
965
+ <svg class="dropdown-icon" viewBox="0 0 24 24"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>
966
+ Reset Chat
967
+ </div>
968
+ </div>
969
+
970
+ <!-- Chat Messages Container -->
971
+ <div class="messages-container" id="chat-box"></div>
972
+
973
+ <!-- Input Wrapper -->
974
+ <div class="input-wrapper">
975
+
976
+ <!-- Reply Indicator Bar -->
977
+ <div class="active-reply-bar" id="reply-bar">
978
+ <span>Replying to <strong id="reply-to-name" style="color:var(--text-dark);">User</strong>: <i id="reply-to-text">Text</i></span>
979
+ <div class="cancel-reply" onclick="cancelReply()">
980
+ <svg class="icon-svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
981
+ </div>
982
+ </div>
983
+
984
+ <div class="input-area">
985
+ <button class="icon-btn" onclick="toggleMenu('attach-menu')">
986
+ <svg class="icon-svg" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
987
+ </button>
988
+
989
+ <!-- Attachments Dropdown -->
990
+ <div class="dropdown-menu" id="attach-menu">
991
+ <div class="dropdown-item" onclick="attachImage()">
992
+ <svg class="dropdown-icon" viewBox="0 0 24 24"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/></svg>
993
+ Attach Image
994
+ </div>
995
+ <div class="dropdown-item" onclick="attachLink()">
996
+ <svg class="dropdown-icon" viewBox="0 0 24 24"><path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/></svg>
997
+ Attach Link
998
+ </div>
999
+ </div>
1000
+
1001
+ <input type="text" id="messageInput" placeholder="Type a message..." onkeypress="handleKeyPress(event)" autocomplete="off">
1002
+
1003
+ <button class="send-btn" onclick="sendMsg()">
1004
+ <svg class="icon-svg" style="fill:white; width:24px; height:24px; transform:translateX(2px);" viewBox="0 0 24 24"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
1005
+ </button>
1006
+ </div>
1007
+ </div>
1008
+ </div>
1009
+
1010
+ </div>
1011
+ </div>
1012
  </div>
1013
+
1014
+ <!-- CUSTOM MODAL OVERLAY -->
1015
+ <div class="modal-overlay" id="custom-modal">
1016
+ <div class="modal-box">
1017
+ <div class="modal-title" id="modal-title">System Alert</div>
1018
+ <div class="modal-text" id="modal-text">Message goes here.</div>
1019
+ <div class="modal-buttons" id="modal-buttons"></div>
1020
+ </div>
1021
  </div>
1022
+
1023
+ <!-- JAVASCRIPT LOGIC -->
1024
+ <script>
1025
+ /**
1026
+ * State Variables
1027
+ */
1028
+ let myName = "";
1029
+ let isDev = false;
1030
+ let myRoom = "";
1031
+ let savedRooms = [];
1032
+ let currentReplyTo = null;
1033
+ let pollInterval;
1034
+ let attachedImgData = null;
1035
+ let attachedLinkData = null;
1036
+
1037
+ /**
1038
+ * Mock Backend Database Class
1039
+ * Handles secure storage, room passwords, messages, and reactions.
1040
+ */
1041
+ class BackendManager {
1042
+ static getDB() {
1043
+ return JSON.parse(localStorage.getItem('liquid_pro_db') || '{}');
1044
+ }
1045
+
1046
+ static saveDB(db) {
1047
+ localStorage.setItem('liquid_pro_db', JSON.stringify(db));
1048
+ }
1049
+
1050
+ static registerRoom(roomName, password) {
1051
+ let db = this.getDB();
1052
+ if (!db[roomName]) {
1053
+ db[roomName] = {
1054
+ password: password,
1055
+ messages: [],
1056
+ users: [],
1057
+ resetVote: null
1058
+ };
1059
+ this.saveDB(db);
1060
+ return true;
1061
+ } else {
1062
+ return db[roomName].password === password;
1063
+ }
1064
+ }
1065
+
1066
+ static sendMessage(room, sender, senderIsDev, text, replyData, imgData, linkData, isVoteMsg = false) {
1067
+ let db = this.getDB();
1068
+ if (!db[room]) return [];
1069
+
1070
+ // Track unique users for vote counts
1071
+ if (!db[room].users.includes(sender) && sender !== 'SYSTEM') {
1072
+ db[room].users.push(sender);
1073
+ }
1074
+
1075
+ let msgObj = {
1076
+ id: 'msg_' + Date.now().toString() + Math.floor(Math.random()*1000),
1077
+ sender: sender,
1078
+ isDev: senderIsDev,
1079
+ text: text,
1080
+ replyData: replyData,
1081
+ imgData: imgData,
1082
+ linkData: linkData,
1083
+ isVoteMsg: isVoteMsg,
1084
+ time: new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}),
1085
+ timestamp: Date.now(),
1086
+ reactions: []
1087
+ };
1088
+
1089
+ db[room].messages.push(msgObj);
1090
+ this.saveDB(db);
1091
+ return db[room].messages;
1092
+ }
1093
+
1094
+ static getMessages(room) {
1095
+ let db = this.getDB();
1096
+ return db[room] ? db[room].messages : [];
1097
+ }
1098
+
1099
+ static deleteMessage(room, id) {
1100
+ let db = this.getDB();
1101
+ if(db[room]) {
1102
+ db[room].messages = db[room].messages.filter(m => m.id !== id);
1103
+ this.saveDB(db);
1104
+ }
1105
+ }
1106
+
1107
+ static addReaction(room, id, actionName) {
1108
+ let db = this.getDB();
1109
+ if (!db[room]) return;
1110
+ let msg = db[room].messages.find(m => m.id === id);
1111
+ if(msg) {
1112
+ // Store text representations of reactions
1113
+ let badge = actionName === 'like' ? '👍 Like' : '😂 Haha';
1114
+ msg.reactions.push(badge);
1115
+ this.saveDB(db);
1116
+ }
1117
+ }
1118
+
1119
+ static initResetVote(room, sender) {
1120
+ let db = this.getDB();
1121
+ if(!db[room]) return;
1122
+ let totalUsers = db[room].users.length || 1;
1123
+ db[room].resetVote = { initiator: sender, agrees: [sender], totalNeeded: totalUsers };
1124
+ this.saveDB(db);
1125
+ this.sendMessage(room, 'SYSTEM', false, `${sender} has requested to reset this chat history.`, null, null, null, true);
1126
+ }
1127
+
1128
+ static voteReset(room, user, agree) {
1129
+ let db = this.getDB();
1130
+ if(!db[room] || !db[room].resetVote) return false;
1131
+
1132
+ if(agree && !db[room].resetVote.agrees.includes(user)) {
1133
+ db[room].resetVote.agrees.push(user);
1134
+ }
1135
+
1136
+ this.saveDB(db);
1137
+
1138
+ // Execute reset if threshold met
1139
+ if(db[room].resetVote.agrees.length >= db[room].resetVote.totalNeeded) {
1140
+ db[room].messages = [];
1141
+ db[room].resetVote = null;
1142
+ this.saveDB(db);
1143
+ return true;
1144
+ }
1145
+ return false;
1146
+ }
1147
+
1148
+ static getPeople(room) {
1149
+ let db = this.getDB();
1150
+ return db[room] ? db[room].users : [];
1151
+ }
1152
+ }
1153
+
1154
+
1155
+ /**
1156
+ * Initialization & Loading Screen
1157
+ */
1158
+ window.onload = function() {
1159
+ // Simulate smooth loading
1160
+ setTimeout(() => {
1161
+ const loader = document.getElementById('loading-screen');
1162
+ loader.style.opacity = '0';
1163
+ setTimeout(() => loader.style.display = 'none', 800);
1164
+ }, 1200);
1165
+
1166
+ // Load saved data
1167
+ const storedName = localStorage.getItem('liquid_name');
1168
+ const storedDevStatus = localStorage.getItem('liquid_isDev') === 'true';
1169
+ const cl = localStorage.getItem('liquid_changelog');
1170
+
1171
+ if (cl) {
1172
+ document.getElementById('changelog-content').innerText = cl;
1173
+ }
1174
+
1175
+ if (storedName) {
1176
+ myName = storedName;
1177
+ isDev = storedDevStatus;
1178
+ showMainApp();
1179
+ }
1180
+
1181
+ // Global click listener to close dropdowns
1182
+ document.addEventListener('click', (e) => {
1183
+ if(!e.target.closest('.dropdown-menu') && !e.target.closest('.icon-btn')) {
1184
+ document.querySelectorAll('.dropdown-menu').forEach(menu => menu.classList.remove('active'));
1185
+ }
1186
+ });
1187
+
1188
+ // Changelog Edit Listener (Ctrl + Shift + ^)
1189
+ document.addEventListener('keydown', (e) => {
1190
+ if (e.ctrlKey && e.shiftKey && (e.key === '^' || e.key === '6')) {
1191
+ const el = document.getElementById('changelog-content');
1192
+ const isEditable = el.getAttribute('contenteditable') === 'true';
1193
+ el.setAttribute('contenteditable', !isEditable);
1194
+ if (isEditable) {
1195
+ localStorage.setItem('liquid_changelog', el.innerText);
1196
+ showModal("Saved", "Changelog successfully updated.", [{text: "Confirm", class: "secondary"}]);
1197
+ } else {
1198
+ el.focus();
1199
+ }
1200
+ }
1201
+ });
1202
  };
1203
+
1204
+ /**
1205
+ * Authentication Logic
1206
+ */
1207
+ function login() {
1208
+ const nameInput = document.getElementById('authName').value.trim();
1209
+ const passInput = document.getElementById('authPass').value.trim();
1210
+
1211
+ if (!nameInput) {
1212
+ showModal("Validation Error", "Username is required to enter Liquid Chat.", [{text: "Understood", class: "secondary"}]);
1213
+ return;
1214
+ }
1215
+
1216
+ myName = nameInput;
1217
+
1218
+ // Check for Developer credentials
1219
+ if (myName.toLowerCase() === 'milo' && passInput === '123456') {
1220
+ isDev = true;
1221
+ myName = "Milo"; // Enforce casing
1222
+ } else {
1223
+ isDev = false;
1224
+ }
1225
+
1226
+ localStorage.setItem('liquid_name', myName);
1227
+ localStorage.setItem('liquid_isDev', isDev);
1228
+ showMainApp();
1229
+ }
1230
+
1231
+ function logout() {
1232
+ localStorage.removeItem('liquid_name');
1233
+ localStorage.removeItem('liquid_isDev');
1234
+ location.reload();
1235
+ }
1236
+
1237
+ function showMainApp() {
1238
+ document.getElementById('auth-screen').style.opacity = '0';
1239
+ setTimeout(() => {
1240
+ document.getElementById('auth-screen').style.display = 'none';
1241
+ document.getElementById('main-app').style.display = 'flex';
1242
+
1243
+ const navName = document.getElementById('nav-name');
1244
+ navName.innerText = myName;
1245
+ document.getElementById('nav-avatar').innerText = myName.charAt(0).toUpperCase();
1246
+
1247
+ if(isDev) {
1248
+ navName.classList.add('gold-name');
1249
+ document.getElementById('nav-role').innerText = "Developer";
1250
+ document.getElementById('nav-role').classList.add('gold-name');
1251
+ }
1252
+
1253
+ loadSavedRooms();
1254
+ }, 400);
1255
+ }
1256
+
1257
+ /**
1258
+ * Custom Modal System
1259
+ */
1260
+ function showModal(title, text, buttons) {
1261
+ document.getElementById('modal-title').innerText = title;
1262
+ document.getElementById('modal-text').innerText = text;
1263
+
1264
+ const btnContainer = document.getElementById('modal-buttons');
1265
+ btnContainer.innerHTML = '';
1266
+
1267
+ buttons.forEach(btn => {
1268
+ const b = document.createElement('button');
1269
+ b.innerText = btn.text;
1270
+ if(btn.class) b.className = btn.class;
1271
+ b.onclick = () => {
1272
+ document.getElementById('custom-modal').classList.remove('active');
1273
+ if(btn.action) btn.action();
1274
+ };
1275
+ btnContainer.appendChild(b);
1276
+ });
1277
+
1278
+ document.getElementById('custom-modal').classList.add('active');
1279
+ }
1280
+
1281
+ /**
1282
+ * Room Management
1283
+ */
1284
+ function loadSavedRooms() {
1285
+ const storedRooms = localStorage.getItem('liquid_rooms');
1286
+ if (storedRooms) {
1287
+ savedRooms = JSON.parse(storedRooms);
1288
+ }
1289
  renderSidebarRecents();
1290
+ }
1291
+
1292
+ function renderSidebarRecents() {
1293
+ const list = document.getElementById('sidebar-recents');
1294
+ list.innerHTML = '';
1295
+
1296
+ if(savedRooms.length === 0) {
1297
+ list.innerHTML = '<div style="font-size:13px; color:var(--text-muted);">No saved rooms.</div>';
1298
+ return;
1299
+ }
1300
+
1301
+ savedRooms.forEach(roomObj => {
1302
+ const div = document.createElement('div');
1303
+ div.className = "recent-btn";
1304
+
1305
+ // SVG lock icon for secure rooms
1306
+ const lockIcon = `<svg style="width:14px; height:14px; margin-right:8px; fill:var(--text-muted);" 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-2zM9 6c0-1.66 1.34-3 3-3s3 1.34 3 3v2H9V6zm9 14H6V10h12v10zm-6-3c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z"/></svg>`;
1307
+
1308
+ div.innerHTML = `
1309
+ <div onclick="quickJoin('${roomObj.name}', '${roomObj.password}')" style="cursor:pointer; flex:1; display:flex; align-items:center;">
1310
+ ${lockIcon} ${roomObj.name}
1311
+ </div>
1312
+ <div class="recent-delete" onclick="promptDeleteRoom('${roomObj.name}')">
1313
+ <svg class="icon-svg" style="width:14px; height:14px;" viewBox="0 0 24 24"><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>
1314
+ </div>
1315
+ `;
1316
+ list.appendChild(div);
1317
+ });
1318
+ }
1319
+
1320
+ function promptDeleteRoom(roomName) {
1321
+ showModal("Remove Room", `Are you sure you want to remove ${roomName} from your saved list?`, [
1322
+ { text: "Cancel", class: "secondary" },
1323
+ { text: "Remove", class: "danger", action: () => {
1324
+ savedRooms = savedRooms.filter(r => r.name !== roomName);
1325
+ localStorage.setItem('liquid_rooms', JSON.stringify(savedRooms));
1326
+ renderSidebarRecents();
1327
+ }}
1328
+ ]);
1329
+ }
1330
+
1331
+ function quickJoin(name, pass) {
1332
+ document.getElementById('roomNameInput').value = name;
1333
+ document.getElementById('roomPassInput').value = pass;
1334
+ joinChat();
1335
+ }
1336
+
1337
+ function joinChat() {
1338
+ const nameInput = document.getElementById('roomNameInput').value.trim();
1339
+ const passInput = document.getElementById('roomPassInput').value.trim();
1340
+
1341
+ if (!nameInput || !passInput) {
1342
+ return showModal("Access Denied", "Both Room Name and Room Password are required.", [{text: "Understood", class: "secondary"}]);
1343
+ }
1344
+
1345
+ const isAuthorized = BackendManager.registerRoom(nameInput, passInput);
1346
+
1347
+ if (!isAuthorized) {
1348
+ return showModal("Access Denied", "Incorrect password for this room.", [{text: "Retry", class: "danger"}]);
1349
+ }
1350
+
1351
+ myRoom = nameInput;
1352
+
1353
+ // Save to recents if not exists
1354
+ if (!savedRooms.find(r => r.name === myRoom)) {
1355
+ savedRooms.unshift({ name: myRoom, password: passInput });
1356
+ localStorage.setItem('liquid_rooms', JSON.stringify(savedRooms));
1357
+ renderSidebarRecents();
1358
+ }
1359
+
1360
+ // Transition UI
1361
+ document.getElementById('setup-screen').style.display = 'none';
1362
+ document.getElementById('chat-screen').style.display = 'flex';
1363
+ document.getElementById('chat-title').innerText = myRoom;
1364
+
1365
+ // Clear inputs and chat box
1366
+ document.getElementById('roomNameInput').value = '';
1367
+ document.getElementById('roomPassInput').value = '';
1368
+ document.getElementById('chat-box').innerHTML = '';
1369
+
1370
+ // Start Polling
1371
+ pollServer();
1372
+ pollInterval = setInterval(pollServer, 2000);
1373
+ }
1374
+
1375
+ function goBack() {
1376
+ clearInterval(pollInterval);
1377
+ document.getElementById('chat-screen').style.display = 'none';
1378
+ document.getElementById('setup-screen').style.display = 'flex';
1379
+ lastDataString = ""; // Reset cache
1380
+ }
1381
+
1382
+ /**
1383
+ * UI Toggles & Attachments
1384
+ */
1385
+ function toggleMenu(menuId) {
1386
+ const el = document.getElementById(menuId);
1387
+ const isAct = el.classList.contains('active');
1388
+ document.querySelectorAll('.dropdown-menu').forEach(m => m.classList.remove('active'));
1389
+ if(!isAct) {
1390
+ // Small timeout prevents immediate close from document click listener
1391
+ setTimeout(() => el.classList.add('active'), 10);
1392
+ }
1393
+ }
1394
+
1395
+ function attachImage() {
1396
+ const url = prompt("Enter an Image URL:");
1397
+ if(url) {
1398
+ attachedImgData = url;
1399
+ document.getElementById('messageInput').focus();
1400
+ }
1401
+ }
1402
+
1403
+ function attachLink() {
1404
+ const url = prompt("Enter a Website Link:");
1405
+ if(url) {
1406
+ attachedLinkData = url;
1407
+ document.getElementById('messageInput').focus();
1408
+ }
1409
+ }
1410
+
1411
+ /**
1412
+ * Core Features: People & Reset
1413
+ */
1414
+ function showPeople() {
1415
+ const people = BackendManager.getPeople(myRoom);
1416
+ showModal("Room Members", people.length > 0 ? people.join(", ") : "Just you.", [{text: "Close", class: "secondary"}]);
1417
+ }
1418
+
1419
+ function initiateReset() {
1420
+ BackendManager.initResetVote(myRoom, myName);
1421
+ pollServer();
1422
+ }
1423
+
1424
+ function handleVote(agree) {
1425
+ const didReset = BackendManager.voteReset(myRoom, myName, agree);
1426
+ if(didReset) {
1427
+ triggerConfetti();
1428
+ const box = document.getElementById('chat-box');
1429
+ box.innerHTML = `
1430
+ <div style="text-align:center; padding:40px; display:flex; flex-direction:column; align-items:center;">
1431
+ <svg style="width:48px; height:48px; fill:var(--apple-blue); margin-bottom:16px;" 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-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>
1432
+ <h3 style="color:var(--text-dark);">Chat Cleared</h3>
1433
+ <p style="color:var(--text-muted); font-size:14px;">All members agreed. Starting fresh.</p>
1434
+ </div>`;
1435
+ lastDataString = "";
1436
+ }
1437
+ pollServer();
1438
+ }
1439
+
1440
+ function triggerConfetti() {
1441
+ for(let i=0; i<60; i++) {
1442
+ let c = document.createElement('div');
1443
+ c.className = 'confetti';
1444
+ c.style.left = Math.random() * 100 + 'vw';
1445
+ c.style.backgroundColor = ['#007aff', '#ff3b30', '#34c759', '#ffcc00'][Math.floor(Math.random()*4)];
1446
+ c.style.animationDuration = (Math.random() * 2 + 2) + 's';
1447
+ document.body.appendChild(c);
1448
+ setTimeout(() => c.remove(), 4000);
1449
+ }
1450
+ }
1451
+
1452
+ /**
1453
+ * Replies & Actions
1454
+ */
1455
+ function setReply(id, name, text) {
1456
+ currentReplyTo = { id: id, name: name, text: text.substring(0,40) + "..." };
1457
+ document.getElementById('reply-bar').style.display = 'flex';
1458
+ document.getElementById('reply-to-name').innerText = name;
1459
+ document.getElementById('reply-to-text').innerText = currentReplyTo.text;
1460
+ document.getElementById('messageInput').focus();
1461
+ }
1462
+
1463
+ function cancelReply() {
1464
+ currentReplyTo = null;
1465
+ document.getElementById('reply-bar').style.display = 'none';
1466
+ }
1467
+
1468
+ function handleReact(id, type) {
1469
+ BackendManager.addReaction(myRoom, id, type);
1470
+ pollServer();
1471
+ }
1472
+
1473
+ function handleDeleteMsg(id, timestamp) {
1474
+ const now = Date.now();
1475
+ // 10 minute rule: 10 * 60 * 1000 = 600,000 ms
1476
+ if(now - timestamp > 600000) {
1477
+ showModal("Time Limit Exceeded", "Messages can only be deleted within 10 minutes of sending.", [{text:"Okay", class:"secondary"}]);
1478
+ return;
1479
+ }
1480
+ BackendManager.deleteMessage(myRoom, id);
1481
+ pollServer();
1482
+ }
1483
+
1484
+ /**
1485
+ * Message Rendering & Polling
1486
+ */
1487
+ function handleKeyPress(e) {
1488
+ if (e.key === 'Enter') sendMsg();
1489
+ }
1490
 
1491
+ function sendMsg() {
1492
+ const input = document.getElementById('messageInput');
1493
+ const text = input.value.trim();
1494
+
1495
+ if (!text && !attachedImgData && !attachedLinkData) return;
1496
+
1497
+ BackendManager.sendMessage(myRoom, myName, isDev, text, currentReplyTo, attachedImgData, attachedLinkData);
1498
+
1499
+ input.value = '';
1500
+ attachedImgData = null;
1501
+ attachedLinkData = null;
1502
+ cancelReply();
1503
+ pollServer();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1504
  }
1505
+
1506
+ let lastDataString = "";
1507
 
1508
+ function pollServer() {
1509
+ if (!myRoom) return;
1510
+
1511
+ const messages = BackendManager.getMessages(myRoom);
1512
+ const dataStr = JSON.stringify(messages);
1513
+ if(dataStr === lastDataString) return;
1514
+
1515
+ lastDataString = dataStr;
1516
+ const box = document.getElementById('chat-box');
1517
+
1518
+ // Check scroll position before adding new content
1519
+ const isScrolledToBottom = box.scrollHeight - box.clientHeight <= box.scrollTop + 60;
1520
+
1521
+ box.innerHTML = '';
1522
+
1523
+ messages.forEach(msg => {
1524
+ const isMe = (msg.sender === myName);
1525
+ const wrap = document.createElement('div');
1526
+ wrap.className = `msg-wrapper ${isMe ? 'sent' : 'received'}`;
1527
+
1528
+ // Construct Professional SVG Action Menu
1529
+ const replySvg = `<svg class="action-icon" viewBox="0 0 24 24"><path d="M10 9V5l-7 7 7 7v-4.1c5 0 8.5 1.6 11 5.1-1-5-4-10-11-11z"/></svg>`;
1530
+ const likeSvg = `<svg class="action-icon" viewBox="0 0 24 24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>`;
1531
+ const hahaSvg = `<svg class="action-icon" viewBox="0 0 24 24"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm3.5-9c.83 0 1.5-.67 1.5-1.5S16.33 8 15.5 8 14 8.67 14 9.5s.67 1.5 1.5 1.5zm-7 0c.83 0 1.5-.67 1.5-1.5S9.33 8 8.5 8 7 8.67 7 9.5 7.67 11 8.5 11zm3.5 6.5c2.33 0 4.31-1.46 5.11-3.5H6.89c.8 2.04 2.78 3.5 5.11 3.5z"/></svg>`;
1532
+ const delSvg = `<svg class="action-icon" viewBox="0 0 24 24"><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>`;
1533
+
1534
+ let actionsHtml = `<div class="msg-actions">
1535
+ <button class="action-item" onclick="setReply('${msg.id}', '${msg.sender}', '${(msg.text || '').replace(/'/g,"")}')">${replySvg} Reply</button>
1536
+ <button class="action-item" onclick="handleReact('${msg.id}', 'like')">${likeSvg} Like</button>
1537
+ <button class="action-item" onclick="handleReact('${msg.id}', 'haha')">${hahaSvg} Haha</button>
1538
+ ${isMe ? `<button class="action-item delete" onclick="handleDeleteMsg('${msg.id}', ${msg.timestamp})">${delSvg} Delete</button>` : ''}
1539
+ </div>`;
1540
+
1541
+ let bubbleHtml = `<div class="bubble">`;
1542
+
1543
+ // Render Vote Message Type
1544
+ if(msg.isVoteMsg) {
1545
+ const alertSvg = `<svg style="width:24px; height:24px; fill:var(--apple-red);" viewBox="0 0 24 24"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>`;
1546
+ bubbleHtml = `<div class="vote-msg">
1547
+ <div class="vote-title">${alertSvg} System Alert</div>
1548
+ <div style="color:var(--text-dark); margin-bottom:16px;">${msg.text}</div>
1549
+ <div class="vote-buttons">
1550
+ <button onclick="handleVote(true)">Agree</button>
1551
+ <button class="secondary" onclick="handleVote(false)">Disagree</button>
1552
+ </div>
1553
+ </div>`;
1554
+ actionsHtml = ''; // No actions permitted on system vote messages
1555
+ }
1556
+ // Render Standard Message Type
1557
+ else {
1558
+ // Check if sender is developer for gold styling
1559
+ let senderClass = msg.isDev ? "msg-sender gold-name" : "msg-sender";
1560
+
1561
+ if (!isMe) bubbleHtml += `<div class="${senderClass}">${msg.sender}</div>`;
1562
+
1563
+ if (msg.replyData) {
1564
+ bubbleHtml += `<div class="reply-preview">Replying to <strong style="font-weight:600;">${msg.replyData.name}</strong>: ${msg.replyData.text}</div>`;
1565
+ }
1566
+ if (msg.imgData) {
1567
+ bubbleHtml += `<img src="${msg.imgData}" class="attached-media" alt="User Attachment"><br>`;
1568
+ }
1569
+ if (msg.linkData) {
1570
+ bubbleHtml += `<a href="${msg.linkData}" target="_blank" style="color:inherit; text-decoration:underline; font-weight:500;">${msg.linkData}</a><br>`;
1571
+ }
1572
+
1573
+ bubbleHtml += `<div>${msg.text}</div>`;
1574
+ bubbleHtml += `<div class="msg-time">${msg.time}</div>`;
1575
+
1576
+ if(msg.reactions && msg.reactions.length > 0) {
1577
+ bubbleHtml += `<div class="reactions-bar">`;
1578
+ msg.reactions.forEach(r => {
1579
+ bubbleHtml += `<span class="reaction-badge">${r}</span>`;
1580
+ });
1581
+ bubbleHtml += `</div>`;
1582
+ }
1583
+ bubbleHtml += `</div>`;
1584
+ }
1585
+
1586
+ wrap.innerHTML = actionsHtml + bubbleHtml;
1587
+ box.appendChild(wrap);
1588
+ });
1589
+
1590
+ // Maintain scroll position logic
1591
+ if (isScrolledToBottom) {
1592
+ box.scrollTop = box.scrollHeight;
1593
+ }
1594
+ }
1595
+ </script>
1596
  </body>
1597
  </html>