CVNSS commited on
Commit
a48e66f
·
verified ·
1 Parent(s): 93ad274

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +57 -57
index.html CHANGED
@@ -6,21 +6,18 @@
6
  <title>Chat CVNSS4.0</title>
7
  <style>
8
  :root{
9
- /* Light + tham số cắt header/panel của tlk.io */
10
- --bg:#f7f9fc; --card:#ffffff; --text:#1f2937; --muted:#6b7280;
11
  --primary:#1a73e8; --danger:#d93025; --accent:#ffeb3b;
12
- --mask-top: 220px; /* CẮT header (đã tăng để che icon mây + thanh xanh) */
13
- --mask-right: clamp(220px, 24vw, 300px); /* Ẩn panel phải */
 
14
  }
15
  *{box-sizing:border-box}
16
- body{margin:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;
17
- background:var(--bg);color:var(--text);display:flex;flex-direction:column;min-height:100vh}
18
  header{padding:14px 16px;text-align:center;font-weight:700;background:#fff;border-bottom:1px solid #e5e7eb}
19
-
20
- .card{width:min(980px,94vw);margin:22px auto;background:var(--card);
21
- border:1px solid #e5e7eb;border-radius:12px;box-shadow:0 6px 18px rgba(0,0,0,.06);padding:18px}
22
- h2{margin:0 0 6px}
23
- .muted{color:#6b7280}
24
  .row{display:flex;gap:10px;flex-wrap:wrap;align-items:center}
25
  input[type=text]{flex:1 1 260px;padding:12px 14px;font-size:16px;border:1px solid #d1d5db;border-radius:10px}
26
  input[type=text]:focus{outline:none;border-color:var(--primary);box-shadow:0 0 0 3px rgba(26,115,232,.15)}
@@ -30,32 +27,26 @@
30
  .btn-secondary{background:#374151;color:#fff}
31
  .mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
32
 
33
- /* Trạng thái gọn */
34
  .status{display:none;align-items:center;gap:10px;padding:10px 12px;border:1px dashed #d1d5db;border-radius:10px;margin-bottom:10px;background:#f9fafb}
35
  .me-pill{display:inline-flex;align-items:center;gap:6px;background:#fff3cd;color:#7a5b00;border:1px solid #ffe8a1;padding:6px 12px;border-radius:999px;font-weight:800}
36
  .room-pill{display:inline-block;background:#e8f0fe;color:#174ea6;padding:6px 10px;border-radius:999px}
37
 
38
- /* Khung chat: kỹ thuật MASK (không overlay che đầu) */
39
- .chat-wrap{
40
- position:relative; border:1px solid #e5e7eb; border-radius:10px; overflow:hidden; background:#fff;
41
- padding-top: var(--mask-top); /* chừa chỗ phần bị dịch âm */
42
- }
43
- #chatFrame{
44
- display:block; width:100%; height:72vh; min-height:640px; border:0; background:#fff;
45
- transform: translateY(calc(-1 * var(--mask-top))); /* đẩy phần header ra ngoài khung nhìn */
46
- }
47
 
48
- /* Ẩn panel phải (không chặn click khu trung tâm/đáy) */
49
- .mask-right{
50
- position:absolute; top:0; right:0; bottom:0; width:var(--mask-right);
51
- background:#fff; pointer-events:none; box-shadow:-8px 0 10px -10px rgba(0,0,0,.12); z-index:2;
52
- }
53
 
54
  /* Gợi ý vùng nhập ở đáy – không chặn bấm */
55
- .hint-bottom{
56
- position:absolute; left:0; right:var(--mask-right); bottom:0; height:110px; pointer-events:none; z-index:1;
57
- background:radial-gradient(120% 120% at 50% 100%, rgba(26,115,232,.08) 0%, rgba(26,115,232,.03) 40%, rgba(255,255,255,0) 75%);
58
- }
 
 
 
59
 
60
  /* Link phòng tự ẩn */
61
  .links{display:none;gap:8px;align-items:center;flex-wrap:wrap;margin-top:10px}
@@ -68,7 +59,6 @@
68
  .toast{position:fixed;left:50%;bottom:22px;transform:translateX(-50%);background:#111827;color:#f9fafb;border:1px solid #374151;padding:10px 14px;border-radius:10px;font-size:14px;opacity:0;pointer-events:none;transition:opacity .2s}
69
  .toast.show{opacity:1}
70
  footer{margin-top:auto;text-align:center;color:#6b7280;font-size:13px;padding:16px}
71
-
72
  @media (max-width:820px){ :root{ --mask-right:0px } }
73
  </style>
74
  </head>
@@ -77,18 +67,15 @@
77
 
78
  <section id="login" class="card">
79
  <h2>Tham gia phòng chat</h2>
80
- <p class="muted">Nhập tên → <b>Vào Chat</b> → *(tuỳ chọn)* <b>✨ Tạo phòng bí mật</b> để mời người khác.</p>
81
-
82
  <div class="row" style="margin-top:8px">
83
  <input id="nickname" type="text" placeholder="Nhập tên của bạn…" autocomplete="name">
84
  <button id="joinBtn" class="btn-red">Vào Chat</button>
85
  </div>
86
-
87
  <div class="row" style="margin-top:10px">
88
  <button id="newRoomBtn" class="btn-ghost">✨ Tạo phòng bí mật</button>
89
  <span id="roomHint" class="muted"></span>
90
  </div>
91
-
92
  <div id="linkArea" class="links" aria-live="polite">
93
  <span>🔗 Link phòng:</span>
94
  <span id="roomUrl" class="link-badge mono"></span>
@@ -103,10 +90,16 @@
103
  </div>
104
 
105
  <div class="chat-wrap">
 
 
 
 
 
 
 
106
  <div class="mask-right"></div>
107
  <div class="hint-bottom"></div>
108
- <iframe id="chatFrame" title="tlk.io chat" referrerpolicy="no-referrer"
109
- allow="clipboard-read; clipboard-write"></iframe>
110
  </div>
111
 
112
  <div id="errorBox" class="error-box">
@@ -120,9 +113,8 @@
120
 
121
  <script>
122
  (() => {
123
- // ===== State =====
124
- const qs = new URLSearchParams(location.search);
125
- let room = (qs.get("room") || "cvnss4-0").trim().toLowerCase();
126
  const base = `${location.origin}${location.pathname}`;
127
 
128
  const $ = id => document.getElementById(id);
@@ -131,21 +123,29 @@
131
  name: $("nickname"), join: $("joinBtn"), newRoom: $("newRoomBtn"),
132
  linkArea: $("linkArea"), roomUrl: $("roomUrl"), copy: $("copyBtn"),
133
  hint: $("roomHint"), me: $("meName"), pill: $("roomPill"),
134
- status: $("statusBar"), toast: $("toast"), err: $("errorBox"), openNew: $("openNew")
 
135
  };
136
 
137
- // ===== Cho phép tinh chỉnh nhanh qua URL =====
138
- const maskTop = parseInt(qs.get("mask"), 10);
139
- if (!isNaN(maskTop)) document.documentElement.style.setProperty('--mask-top', maskTop + 'px');
140
- const maskRight = parseInt(qs.get("maskr"), 10);
141
- if (!isNaN(maskRight)) document.documentElement.style.setProperty('--mask-right', maskRight + 'px');
142
-
143
  const toast = m => { els.toast.textContent=m; els.toast.classList.add("show"); setTimeout(()=>els.toast.classList.remove("show"),1400); };
144
  const getName = () => (els.name.value || localStorage.getItem("cvnss4_name") || "Guest").trim() || "Guest";
145
  const setName = n => { els.me.textContent=n; localStorage.setItem("cvnss4_name", n); };
146
  const syncRoomUI = () => { els.pill.textContent=room; els.hint.textContent=`(đang ở phòng ${room})`; };
147
  const tlkUrl = n => `https://tlk.io/${encodeURIComponent(room)}?nickname=${encodeURIComponent(n)}#embed`;
148
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  function mountChat(){
150
  const name = getName(); setName(name); syncRoomUI();
151
  els.status.style.display = "flex";
@@ -153,7 +153,6 @@
153
  const url = tlkUrl(name);
154
  els.frame.src = url; els.openNew.href = url; els.err.style.display = "none";
155
 
156
- // Watchdog: nếu 6s vẫn trắng → hiện fallback
157
  const timer = setTimeout(()=>{ els.err.style.display = "flex"; }, 6000);
158
  els.frame.addEventListener("load", ()=> clearTimeout(timer), {once:true});
159
  }
@@ -164,22 +163,17 @@
164
  mountChat();
165
  }
166
 
 
167
  els.join.addEventListener("click", enterChat);
168
  els.name.addEventListener("keydown", e => { if(e.key === "Enter") enterChat(); });
169
 
170
- // Tạo phòng bí mật + Copy tự ẩn
171
  els.newRoom.addEventListener("click", async ()=>{
172
  room = `cvnss4-0-${Math.random().toString(36).slice(2,8)}`;
173
- syncRoomUI();
174
- history.replaceState(null,"",`?room=${room}`);
175
-
176
  const link = `${base}?room=${room}`;
177
- els.roomUrl.textContent = link;
178
- els.linkArea.style.display = "flex";
179
-
180
  try{ await navigator.clipboard?.writeText?.(link); toast("✅ Link đã copy!"); }catch(_){}
181
  setTimeout(()=>{ if(els.linkArea.style.display!=="none") els.linkArea.style.display="none"; }, 9000);
182
-
183
  els.copy.onclick = async ()=>{
184
  try{
185
  await navigator.clipboard?.writeText?.(link);
@@ -190,7 +184,13 @@
190
  };
191
  });
192
 
193
- // Prefill + auto-enter nếu có tên sẵn
 
 
 
 
 
 
194
  const last = localStorage.getItem("cvnss4_name"); if(last) els.name.value = last;
195
  syncRoomUI(); els.name.focus({preventScroll:true});
196
  if (qs.get("room") && (last || els.name.value.trim() !== "")) enterChat();
 
6
  <title>Chat CVNSS4.0</title>
7
  <style>
8
  :root{
9
+ /* Light, tối giản */
10
+ --bg:#f7f9fc; --card:#fff; --text:#1f2937; --muted:#6b7280;
11
  --primary:#1a73e8; --danger:#d93025; --accent:#ffeb3b;
12
+ /* CẮT phần đầu tlk.io + che cột phải */
13
+ --mask-top:220px; /* chỉnh bằng nút ± hoặc ?mask= */
14
+ --mask-right:clamp(220px,24vw,300px); /* cột phải */
15
  }
16
  *{box-sizing:border-box}
17
+ body{margin:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;background:var(--bg);color:var(--text);display:flex;flex-direction:column;min-height:100vh}
 
18
  header{padding:14px 16px;text-align:center;font-weight:700;background:#fff;border-bottom:1px solid #e5e7eb}
19
+ .card{width:min(980px,94vw);margin:22px auto;background:var(--card);border:1px solid #e5e7eb;border-radius:12px;box-shadow:0 6px 18px rgba(0,0,0,.06);padding:18px}
20
+ h2{margin:0 0 6px} .muted{color:var(--muted)}
 
 
 
21
  .row{display:flex;gap:10px;flex-wrap:wrap;align-items:center}
22
  input[type=text]{flex:1 1 260px;padding:12px 14px;font-size:16px;border:1px solid #d1d5db;border-radius:10px}
23
  input[type=text]:focus{outline:none;border-color:var(--primary);box-shadow:0 0 0 3px rgba(26,115,232,.15)}
 
27
  .btn-secondary{background:#374151;color:#fff}
28
  .mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
29
 
30
+ /* Status gọn */
31
  .status{display:none;align-items:center;gap:10px;padding:10px 12px;border:1px dashed #d1d5db;border-radius:10px;margin-bottom:10px;background:#f9fafb}
32
  .me-pill{display:inline-flex;align-items:center;gap:6px;background:#fff3cd;color:#7a5b00;border:1px solid #ffe8a1;padding:6px 12px;border-radius:999px;font-weight:800}
33
  .room-pill{display:inline-block;background:#e8f0fe;color:#174ea6;padding:6px 10px;border-radius:999px}
34
 
35
+ /* Khung chat dùng MASK (không overlay che phần đầu → không giật khi Enter) */
36
+ .chat-wrap{position:relative;border:1px solid #e5e7eb;border-radius:10px;overflow:hidden;background:#fff;padding-top:var(--mask-top)}
37
+ #chatFrame{display:block;width:100%;height:72vh;min-height:640px;border:0;background:#fff;transform:translateY(calc(-1 * var(--mask-top)))}
 
 
 
 
 
 
38
 
39
+ /* Che cột phải (không chặn click phần chat) */
40
+ .mask-right{position:absolute;top:0;right:0;bottom:0;width:var(--mask-right);background:#fff;pointer-events:none;box-shadow:-8px 0 10px -10px rgba(0,0,0,.12);z-index:2}
 
 
 
41
 
42
  /* Gợi ý vùng nhập ở đáy – không chặn bấm */
43
+ .hint-bottom{position:absolute;left:0;right:var(--mask-right);bottom:0;height:110px;pointer-events:none;z-index:1;background:radial-gradient(120% 120% at 50% 100%, rgba(26,115,232,.08) 0%, rgba(26,115,232,.03) 40%, rgba(255,255,255,0) 75%)}
44
+
45
+ /* Điều khiển tinh chỉnh mask-top */
46
+ .mask-ctrl{position:absolute;top:8px;left:8px;display:flex;gap:6px;z-index:5;opacity:.25;transition:opacity .15s}
47
+ .mask-ctrl:hover{opacity:1}
48
+ .mask-ctrl button{padding:6px 10px;border:1px solid #e5e7eb;background:#fff;border-radius:8px;font-weight:700}
49
+ .mask-ctrl .val{padding:6px 8px;border:1px solid #e5e7eb;background:#fff;border-radius:8px;min-width:64px;text-align:center;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
50
 
51
  /* Link phòng tự ẩn */
52
  .links{display:none;gap:8px;align-items:center;flex-wrap:wrap;margin-top:10px}
 
59
  .toast{position:fixed;left:50%;bottom:22px;transform:translateX(-50%);background:#111827;color:#f9fafb;border:1px solid #374151;padding:10px 14px;border-radius:10px;font-size:14px;opacity:0;pointer-events:none;transition:opacity .2s}
60
  .toast.show{opacity:1}
61
  footer{margin-top:auto;text-align:center;color:#6b7280;font-size:13px;padding:16px}
 
62
  @media (max-width:820px){ :root{ --mask-right:0px } }
63
  </style>
64
  </head>
 
67
 
68
  <section id="login" class="card">
69
  <h2>Tham gia phòng chat</h2>
70
+ <p class="muted">Nhập tên → <b>Vào Chat</b> → *(tuỳ chọn)* <b>✨ Tạo phòng bí mật</b>.</p>
 
71
  <div class="row" style="margin-top:8px">
72
  <input id="nickname" type="text" placeholder="Nhập tên của bạn…" autocomplete="name">
73
  <button id="joinBtn" class="btn-red">Vào Chat</button>
74
  </div>
 
75
  <div class="row" style="margin-top:10px">
76
  <button id="newRoomBtn" class="btn-ghost">✨ Tạo phòng bí mật</button>
77
  <span id="roomHint" class="muted"></span>
78
  </div>
 
79
  <div id="linkArea" class="links" aria-live="polite">
80
  <span>🔗 Link phòng:</span>
81
  <span id="roomUrl" class="link-badge mono"></span>
 
90
  </div>
91
 
92
  <div class="chat-wrap">
93
+ <!-- tinh chỉnh mask-top -->
94
+ <div class="mask-ctrl" role="group" aria-label="Trim header">
95
+ <button id="maskMinus" title="Ẩn ít hơn">−10</button>
96
+ <div class="val" id="maskVal">220px</div>
97
+ <button id="maskPlus" title="Ẩn nhiều hơn">+10</button>
98
+ </div>
99
+
100
  <div class="mask-right"></div>
101
  <div class="hint-bottom"></div>
102
+ <iframe id="chatFrame" title="tlk.io chat" referrerpolicy="no-referrer" allow="clipboard-read; clipboard-write"></iframe>
 
103
  </div>
104
 
105
  <div id="errorBox" class="error-box">
 
113
 
114
  <script>
115
  (() => {
116
+ const qs = new URLSearchParams(location.search);
117
+ let room = (qs.get("room") || "cvnss4-0").trim().toLowerCase();
 
118
  const base = `${location.origin}${location.pathname}`;
119
 
120
  const $ = id => document.getElementById(id);
 
123
  name: $("nickname"), join: $("joinBtn"), newRoom: $("newRoomBtn"),
124
  linkArea: $("linkArea"), roomUrl: $("roomUrl"), copy: $("copyBtn"),
125
  hint: $("roomHint"), me: $("meName"), pill: $("roomPill"),
126
+ status: $("statusBar"), toast: $("toast"), err: $("errorBox"), openNew: $("openNew"),
127
+ maskVal: $("maskVal"), maskMinus: $("maskMinus"), maskPlus: $("maskPlus")
128
  };
129
 
 
 
 
 
 
 
130
  const toast = m => { els.toast.textContent=m; els.toast.classList.add("show"); setTimeout(()=>els.toast.classList.remove("show"),1400); };
131
  const getName = () => (els.name.value || localStorage.getItem("cvnss4_name") || "Guest").trim() || "Guest";
132
  const setName = n => { els.me.textContent=n; localStorage.setItem("cvnss4_name", n); };
133
  const syncRoomUI = () => { els.pill.textContent=room; els.hint.textContent=`(đang ở phòng ${room})`; };
134
  const tlkUrl = n => `https://tlk.io/${encodeURIComponent(room)}?nickname=${encodeURIComponent(n)}#embed`;
135
 
136
+ // --- mask-top: từ URL ?mask=, hoặc localStorage, hoặc mặc định ---
137
+ function getMaskTopInit(){
138
+ const q = parseInt(qs.get("mask"),10);
139
+ if(!isNaN(q)) return q;
140
+ const saved = parseInt(localStorage.getItem("cvnss4_maskTop"),10);
141
+ return !isNaN(saved) ? saved : 220;
142
+ }
143
+ function applyMaskTop(px){
144
+ document.documentElement.style.setProperty('--mask-top', px+'px');
145
+ els.maskVal.textContent = px+'px';
146
+ localStorage.setItem("cvnss4_maskTop", String(px));
147
+ }
148
+
149
  function mountChat(){
150
  const name = getName(); setName(name); syncRoomUI();
151
  els.status.style.display = "flex";
 
153
  const url = tlkUrl(name);
154
  els.frame.src = url; els.openNew.href = url; els.err.style.display = "none";
155
 
 
156
  const timer = setTimeout(()=>{ els.err.style.display = "flex"; }, 6000);
157
  els.frame.addEventListener("load", ()=> clearTimeout(timer), {once:true});
158
  }
 
163
  mountChat();
164
  }
165
 
166
+ // Events
167
  els.join.addEventListener("click", enterChat);
168
  els.name.addEventListener("keydown", e => { if(e.key === "Enter") enterChat(); });
169
 
 
170
  els.newRoom.addEventListener("click", async ()=>{
171
  room = `cvnss4-0-${Math.random().toString(36).slice(2,8)}`;
172
+ syncRoomUI(); history.replaceState(null,"",`?room=${room}`);
 
 
173
  const link = `${base}?room=${room}`;
174
+ els.roomUrl.textContent = link; els.linkArea.style.display = "flex";
 
 
175
  try{ await navigator.clipboard?.writeText?.(link); toast("✅ Link đã copy!"); }catch(_){}
176
  setTimeout(()=>{ if(els.linkArea.style.display!=="none") els.linkArea.style.display="none"; }, 9000);
 
177
  els.copy.onclick = async ()=>{
178
  try{
179
  await navigator.clipboard?.writeText?.(link);
 
184
  };
185
  });
186
 
187
+ // Mask controls
188
+ let maskTop = getMaskTopInit();
189
+ applyMaskTop(maskTop);
190
+ els.maskMinus.onclick = ()=>{ maskTop = Math.max(100, maskTop-10); applyMaskTop(maskTop); };
191
+ els.maskPlus.onclick = ()=>{ maskTop = Math.min(400, maskTop+10); applyMaskTop(maskTop); };
192
+
193
+ // Prefill + auto-enter
194
  const last = localStorage.getItem("cvnss4_name"); if(last) els.name.value = last;
195
  syncRoomUI(); els.name.focus({preventScroll:true});
196
  if (qs.get("room") && (last || els.name.value.trim() !== "")) enterChat();