Spaces:
Sleeping
Sleeping
root commited on
Commit ·
da5a45e
1
Parent(s): bac0e5d
Refine CLI theme and modernize layout
Browse files- public/app.js +13 -4
- public/index.html +19 -6
- public/styles.css +133 -21
public/app.js
CHANGED
|
@@ -22,6 +22,7 @@ let historyLoaded = false;
|
|
| 22 |
const THEME_COOKIE = "chatroom_theme";
|
| 23 |
const USERNAME_COOKIE = "chatroom_username";
|
| 24 |
const KNOWN_THEMES = new Set(["cli", "sakura", "stars", "day", "night"]);
|
|
|
|
| 25 |
|
| 26 |
function setStatus(text) {
|
| 27 |
statusEl.textContent = text;
|
|
@@ -70,8 +71,9 @@ function setTheme(theme) {
|
|
| 70 |
colorScheme.setAttribute("content", next === "day" || next === "sakura" ? "light" : "dark");
|
| 71 |
}
|
| 72 |
|
| 73 |
-
for (const el of document.querySelectorAll('input[name="theme"]'))
|
| 74 |
-
|
|
|
|
| 75 |
}
|
| 76 |
}
|
| 77 |
|
|
@@ -135,6 +137,13 @@ function getLastMessageMeta() {
|
|
| 135 |
return { from: last.dataset.from || "", at: lastAt };
|
| 136 |
}
|
| 137 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
function appendItem({ kind, at, from, text }) {
|
| 139 |
const li = document.createElement("li");
|
| 140 |
const isMe = kind === "message" && from && username && from === username;
|
|
@@ -212,7 +221,7 @@ function connect() {
|
|
| 212 |
messagesEl.innerHTML = "";
|
| 213 |
const messages = Array.isArray(data.messages) ? data.messages : [];
|
| 214 |
if (messages.length === 0) {
|
| 215 |
-
|
| 216 |
return;
|
| 217 |
}
|
| 218 |
for (const m of messages) {
|
|
@@ -277,7 +286,7 @@ document.addEventListener("keydown", (e) => {
|
|
| 277 |
if (e.key === "Escape" && !settingsModal.classList.contains("hidden")) closeSettings();
|
| 278 |
});
|
| 279 |
|
| 280 |
-
|
| 281 |
const theme = e.target && e.target.name === "theme" ? e.target.value : null;
|
| 282 |
if (theme) setTheme(theme);
|
| 283 |
});
|
|
|
|
| 22 |
const THEME_COOKIE = "chatroom_theme";
|
| 23 |
const USERNAME_COOKIE = "chatroom_username";
|
| 24 |
const KNOWN_THEMES = new Set(["cli", "sakura", "stars", "day", "night"]);
|
| 25 |
+
const themeChoicesEl = document.getElementById("themeChoices");
|
| 26 |
|
| 27 |
function setStatus(text) {
|
| 28 |
statusEl.textContent = text;
|
|
|
|
| 71 |
colorScheme.setAttribute("content", next === "day" || next === "sakura" ? "light" : "dark");
|
| 72 |
}
|
| 73 |
|
| 74 |
+
for (const el of document.querySelectorAll('input[name="theme"]')) el.checked = el.value === next;
|
| 75 |
+
for (const label of themeChoicesEl.querySelectorAll(".segItem")) {
|
| 76 |
+
label.dataset.selected = label.dataset.themeValue === next ? "true" : "false";
|
| 77 |
}
|
| 78 |
}
|
| 79 |
|
|
|
|
| 137 |
return { from: last.dataset.from || "", at: lastAt };
|
| 138 |
}
|
| 139 |
|
| 140 |
+
function showEmptyState(text) {
|
| 141 |
+
const li = document.createElement("li");
|
| 142 |
+
li.className = "emptyState";
|
| 143 |
+
li.textContent = text;
|
| 144 |
+
messagesEl.appendChild(li);
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
function appendItem({ kind, at, from, text }) {
|
| 148 |
const li = document.createElement("li");
|
| 149 |
const isMe = kind === "message" && from && username && from === username;
|
|
|
|
| 221 |
messagesEl.innerHTML = "";
|
| 222 |
const messages = Array.isArray(data.messages) ? data.messages : [];
|
| 223 |
if (messages.length === 0) {
|
| 224 |
+
showEmptyState("暂无历史消息,开始聊天吧。");
|
| 225 |
return;
|
| 226 |
}
|
| 227 |
for (const m of messages) {
|
|
|
|
| 286 |
if (e.key === "Escape" && !settingsModal.classList.contains("hidden")) closeSettings();
|
| 287 |
});
|
| 288 |
|
| 289 |
+
themeChoicesEl.addEventListener("change", (e) => {
|
| 290 |
const theme = e.target && e.target.name === "theme" ? e.target.value : null;
|
| 291 |
if (theme) setTheme(theme);
|
| 292 |
});
|
public/index.html
CHANGED
|
@@ -57,7 +57,15 @@
|
|
| 57 |
maxlength="500"
|
| 58 |
required
|
| 59 |
/>
|
| 60 |
-
<button type="submit" class="sendBtn" aria-label="发送">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
</form>
|
| 62 |
</div>
|
| 63 |
</section>
|
|
@@ -75,24 +83,29 @@
|
|
| 75 |
<div class="field">
|
| 76 |
<div class="label">主题</div>
|
| 77 |
<div class="seg" id="themeChoices">
|
| 78 |
-
<label class="segItem">
|
| 79 |
<input type="radio" name="theme" value="cli" />
|
|
|
|
| 80 |
<span>命令行主题</span>
|
| 81 |
</label>
|
| 82 |
-
<label class="segItem">
|
| 83 |
<input type="radio" name="theme" value="sakura" />
|
|
|
|
| 84 |
<span>Sakura</span>
|
| 85 |
</label>
|
| 86 |
-
<label class="segItem">
|
| 87 |
<input type="radio" name="theme" value="stars" />
|
|
|
|
| 88 |
<span>星空</span>
|
| 89 |
</label>
|
| 90 |
-
<label class="segItem">
|
| 91 |
<input type="radio" name="theme" value="day" />
|
|
|
|
| 92 |
<span>普通-白日</span>
|
| 93 |
</label>
|
| 94 |
-
<label class="segItem">
|
| 95 |
<input type="radio" name="theme" value="night" />
|
|
|
|
| 96 |
<span>普通-黑夜</span>
|
| 97 |
</label>
|
| 98 |
</div>
|
|
|
|
| 57 |
maxlength="500"
|
| 58 |
required
|
| 59 |
/>
|
| 60 |
+
<button type="submit" class="sendBtn" aria-label="发送">
|
| 61 |
+
<svg viewBox="0 0 24 24" aria-hidden="true" class="sendIcon">
|
| 62 |
+
<path
|
| 63 |
+
d="M3.4 20.2 21 12 3.4 3.8a.9.9 0 0 0-1.25 1.05l1.7 6.45c.1.4.45.7.87.72l7.48.35-7.48.35c-.42.02-.77.32-.87.72l-1.7 6.45a.9.9 0 0 0 1.25 1.05Z"
|
| 64 |
+
fill="currentColor"
|
| 65 |
+
/>
|
| 66 |
+
</svg>
|
| 67 |
+
<span class="srOnly">发送</span>
|
| 68 |
+
</button>
|
| 69 |
</form>
|
| 70 |
</div>
|
| 71 |
</section>
|
|
|
|
| 83 |
<div class="field">
|
| 84 |
<div class="label">主题</div>
|
| 85 |
<div class="seg" id="themeChoices">
|
| 86 |
+
<label class="segItem" data-theme-value="cli">
|
| 87 |
<input type="radio" name="theme" value="cli" />
|
| 88 |
+
<span class="themeIcon" data-theme="cli" aria-hidden="true"></span>
|
| 89 |
<span>命令行主题</span>
|
| 90 |
</label>
|
| 91 |
+
<label class="segItem" data-theme-value="sakura">
|
| 92 |
<input type="radio" name="theme" value="sakura" />
|
| 93 |
+
<span class="themeIcon" data-theme="sakura" aria-hidden="true"></span>
|
| 94 |
<span>Sakura</span>
|
| 95 |
</label>
|
| 96 |
+
<label class="segItem" data-theme-value="stars">
|
| 97 |
<input type="radio" name="theme" value="stars" />
|
| 98 |
+
<span class="themeIcon" data-theme="stars" aria-hidden="true"></span>
|
| 99 |
<span>星空</span>
|
| 100 |
</label>
|
| 101 |
+
<label class="segItem" data-theme-value="day">
|
| 102 |
<input type="radio" name="theme" value="day" />
|
| 103 |
+
<span class="themeIcon" data-theme="day" aria-hidden="true"></span>
|
| 104 |
<span>普通-白日</span>
|
| 105 |
</label>
|
| 106 |
+
<label class="segItem" data-theme-value="night">
|
| 107 |
<input type="radio" name="theme" value="night" />
|
| 108 |
+
<span class="themeIcon" data-theme="night" aria-hidden="true"></span>
|
| 109 |
<span>普通-黑夜</span>
|
| 110 |
</label>
|
| 111 |
</div>
|
public/styles.css
CHANGED
|
@@ -54,28 +54,48 @@ html[data-theme="day"] {
|
|
| 54 |
}
|
| 55 |
|
| 56 |
html[data-theme="cli"] {
|
| 57 |
-
--bg: #
|
| 58 |
-
--panel: rgba(
|
| 59 |
-
--panel2: rgba(
|
| 60 |
-
--text: rgba(
|
| 61 |
-
--muted: rgba(
|
| 62 |
--accent: #19f07a;
|
| 63 |
--danger: #ff5c7a;
|
| 64 |
--border: rgba(25, 240, 122, 0.18);
|
| 65 |
-
--shadow:
|
| 66 |
-
--bg-pattern: radial-gradient(circle at
|
| 67 |
-
radial-gradient(circle at
|
| 68 |
-
--surface: rgba(
|
| 69 |
-
--surface2: rgba(
|
| 70 |
-
--bubble:
|
| 71 |
-
--bubble-me:
|
| 72 |
-
--input:
|
| 73 |
}
|
| 74 |
|
| 75 |
html[data-theme="cli"] body {
|
| 76 |
font-family: var(--font-mono);
|
| 77 |
}
|
| 78 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
html[data-theme="sakura"] {
|
| 80 |
--bg: #fff7fb;
|
| 81 |
--panel: rgba(255, 255, 255, 0.66);
|
|
@@ -207,8 +227,8 @@ body {
|
|
| 207 |
}
|
| 208 |
|
| 209 |
.statusDot {
|
| 210 |
-
width:
|
| 211 |
-
height:
|
| 212 |
border-radius: 999px;
|
| 213 |
background: rgba(255, 255, 255, 0.28);
|
| 214 |
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
|
|
@@ -305,18 +325,28 @@ button:hover {
|
|
| 305 |
background: var(--panel);
|
| 306 |
border: 0;
|
| 307 |
color: var(--text);
|
|
|
|
| 308 |
}
|
| 309 |
|
| 310 |
.iconBtn:hover {
|
| 311 |
background: var(--surface2);
|
| 312 |
}
|
| 313 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 314 |
.ghostBtn {
|
| 315 |
padding: 10px 12px;
|
| 316 |
border-radius: 12px;
|
| 317 |
background: transparent;
|
| 318 |
border: 0;
|
| 319 |
color: var(--muted);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
}
|
| 321 |
|
| 322 |
.ghostBtn:hover {
|
|
@@ -495,8 +525,40 @@ button:hover {
|
|
| 495 |
}
|
| 496 |
|
| 497 |
.sendBtn {
|
|
|
|
|
|
|
|
|
|
| 498 |
border-radius: 999px;
|
| 499 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 500 |
}
|
| 501 |
|
| 502 |
@supports (height: 100dvh) {
|
|
@@ -575,7 +637,9 @@ button:hover {
|
|
| 575 |
.modalBackdrop {
|
| 576 |
position: absolute;
|
| 577 |
inset: 0;
|
| 578 |
-
background: rgba(0, 0, 0, 0.
|
|
|
|
|
|
|
| 579 |
}
|
| 580 |
|
| 581 |
html[data-theme="day"] .modalBackdrop,
|
|
@@ -587,7 +651,7 @@ html[data-theme="sakura"] .modalBackdrop {
|
|
| 587 |
position: relative;
|
| 588 |
width: min(560px, 100%);
|
| 589 |
border-radius: var(--radius2);
|
| 590 |
-
background: var(--bg);
|
| 591 |
box-shadow: var(--shadow);
|
| 592 |
overflow: hidden;
|
| 593 |
}
|
|
@@ -632,17 +696,56 @@ html[data-theme="sakura"] .modalBackdrop {
|
|
| 632 |
background: var(--panel);
|
| 633 |
cursor: pointer;
|
| 634 |
user-select: none;
|
|
|
|
|
|
|
| 635 |
}
|
| 636 |
|
| 637 |
.segItem:hover {
|
| 638 |
background: var(--surface2);
|
| 639 |
}
|
| 640 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 641 |
.segItem input {
|
| 642 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 643 |
flex: 0 0 auto;
|
| 644 |
-
|
| 645 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 646 |
}
|
| 647 |
|
| 648 |
.hint {
|
|
@@ -650,6 +753,15 @@ html[data-theme="sakura"] .modalBackdrop {
|
|
| 650 |
color: var(--muted);
|
| 651 |
}
|
| 652 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 653 |
@media (max-width: 420px) {
|
| 654 |
.seg {
|
| 655 |
grid-template-columns: 1fr;
|
|
|
|
| 54 |
}
|
| 55 |
|
| 56 |
html[data-theme="cli"] {
|
| 57 |
+
--bg: #0f1115;
|
| 58 |
+
--panel: rgba(255, 255, 255, 0.06);
|
| 59 |
+
--panel2: rgba(255, 255, 255, 0.04);
|
| 60 |
+
--text: rgba(211, 255, 230, 0.92);
|
| 61 |
+
--muted: rgba(211, 255, 230, 0.56);
|
| 62 |
--accent: #19f07a;
|
| 63 |
--danger: #ff5c7a;
|
| 64 |
--border: rgba(25, 240, 122, 0.18);
|
| 65 |
+
--shadow: none;
|
| 66 |
+
--bg-pattern: radial-gradient(circle at 10% 15%, rgba(25, 240, 122, 0.05), transparent 46%),
|
| 67 |
+
radial-gradient(circle at 80% 10%, rgba(25, 240, 122, 0.035), transparent 50%);
|
| 68 |
+
--surface: rgba(255, 255, 255, 0.06);
|
| 69 |
+
--surface2: rgba(255, 255, 255, 0.09);
|
| 70 |
+
--bubble: #262626;
|
| 71 |
+
--bubble-me: #004d40;
|
| 72 |
+
--input: #1e2229;
|
| 73 |
}
|
| 74 |
|
| 75 |
html[data-theme="cli"] body {
|
| 76 |
font-family: var(--font-mono);
|
| 77 |
}
|
| 78 |
|
| 79 |
+
html[data-theme="cli"] .topbar {
|
| 80 |
+
background: transparent;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
html[data-theme="cli"] .panel {
|
| 84 |
+
background: transparent;
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
html[data-theme="cli"] .msg.message.me .bubble {
|
| 88 |
+
color: rgba(255, 255, 255, 0.92);
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
html[data-theme="cli"] .name {
|
| 92 |
+
color: rgba(211, 255, 230, 0.62);
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
html[data-theme="cli"] .time {
|
| 96 |
+
color: rgba(211, 255, 230, 0.42);
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
html[data-theme="sakura"] {
|
| 100 |
--bg: #fff7fb;
|
| 101 |
--panel: rgba(255, 255, 255, 0.66);
|
|
|
|
| 227 |
}
|
| 228 |
|
| 229 |
.statusDot {
|
| 230 |
+
width: 10px;
|
| 231 |
+
height: 10px;
|
| 232 |
border-radius: 999px;
|
| 233 |
background: rgba(255, 255, 255, 0.28);
|
| 234 |
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
|
|
|
|
| 325 |
background: var(--panel);
|
| 326 |
border: 0;
|
| 327 |
color: var(--text);
|
| 328 |
+
transition: transform 100ms ease, background 120ms ease;
|
| 329 |
}
|
| 330 |
|
| 331 |
.iconBtn:hover {
|
| 332 |
background: var(--surface2);
|
| 333 |
}
|
| 334 |
|
| 335 |
+
.iconBtn:active {
|
| 336 |
+
transform: scale(0.97);
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
.ghostBtn {
|
| 340 |
padding: 10px 12px;
|
| 341 |
border-radius: 12px;
|
| 342 |
background: transparent;
|
| 343 |
border: 0;
|
| 344 |
color: var(--muted);
|
| 345 |
+
transition: transform 100ms ease, background 120ms ease, color 120ms ease;
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
.ghostBtn:active {
|
| 349 |
+
transform: scale(0.98);
|
| 350 |
}
|
| 351 |
|
| 352 |
.ghostBtn:hover {
|
|
|
|
| 525 |
}
|
| 526 |
|
| 527 |
.sendBtn {
|
| 528 |
+
width: 44px;
|
| 529 |
+
height: 44px;
|
| 530 |
+
padding: 0;
|
| 531 |
border-radius: 999px;
|
| 532 |
+
display: grid;
|
| 533 |
+
place-items: center;
|
| 534 |
+
background: color-mix(in srgb, var(--accent) 26%, transparent);
|
| 535 |
+
transition: transform 100ms ease, background 120ms ease;
|
| 536 |
+
position: relative;
|
| 537 |
+
}
|
| 538 |
+
|
| 539 |
+
.sendBtn:hover {
|
| 540 |
+
background: color-mix(in srgb, var(--accent) 34%, transparent);
|
| 541 |
+
}
|
| 542 |
+
|
| 543 |
+
.sendBtn:active {
|
| 544 |
+
transform: scale(0.95);
|
| 545 |
+
}
|
| 546 |
+
|
| 547 |
+
.sendIcon {
|
| 548 |
+
width: 18px;
|
| 549 |
+
height: 18px;
|
| 550 |
+
}
|
| 551 |
+
|
| 552 |
+
.srOnly {
|
| 553 |
+
position: absolute;
|
| 554 |
+
width: 1px;
|
| 555 |
+
height: 1px;
|
| 556 |
+
padding: 0;
|
| 557 |
+
margin: -1px;
|
| 558 |
+
overflow: hidden;
|
| 559 |
+
clip: rect(0, 0, 0, 0);
|
| 560 |
+
white-space: nowrap;
|
| 561 |
+
border: 0;
|
| 562 |
}
|
| 563 |
|
| 564 |
@supports (height: 100dvh) {
|
|
|
|
| 637 |
.modalBackdrop {
|
| 638 |
position: absolute;
|
| 639 |
inset: 0;
|
| 640 |
+
background: rgba(0, 0, 0, 0.45);
|
| 641 |
+
backdrop-filter: blur(10px);
|
| 642 |
+
-webkit-backdrop-filter: blur(10px);
|
| 643 |
}
|
| 644 |
|
| 645 |
html[data-theme="day"] .modalBackdrop,
|
|
|
|
| 651 |
position: relative;
|
| 652 |
width: min(560px, 100%);
|
| 653 |
border-radius: var(--radius2);
|
| 654 |
+
background: color-mix(in srgb, var(--bg) 82%, transparent);
|
| 655 |
box-shadow: var(--shadow);
|
| 656 |
overflow: hidden;
|
| 657 |
}
|
|
|
|
| 696 |
background: var(--panel);
|
| 697 |
cursor: pointer;
|
| 698 |
user-select: none;
|
| 699 |
+
transition: transform 100ms ease, background 120ms ease;
|
| 700 |
+
position: relative;
|
| 701 |
}
|
| 702 |
|
| 703 |
.segItem:hover {
|
| 704 |
background: var(--surface2);
|
| 705 |
}
|
| 706 |
|
| 707 |
+
.segItem:active {
|
| 708 |
+
transform: scale(0.98);
|
| 709 |
+
}
|
| 710 |
+
|
| 711 |
.segItem input {
|
| 712 |
+
position: absolute;
|
| 713 |
+
opacity: 0;
|
| 714 |
+
pointer-events: none;
|
| 715 |
+
width: 1px;
|
| 716 |
+
height: 1px;
|
| 717 |
+
}
|
| 718 |
+
|
| 719 |
+
.segItem[data-selected="true"] {
|
| 720 |
+
background: color-mix(in srgb, var(--accent) 18%, transparent);
|
| 721 |
+
}
|
| 722 |
+
|
| 723 |
+
.themeIcon {
|
| 724 |
+
width: 14px;
|
| 725 |
+
height: 14px;
|
| 726 |
+
border-radius: 999px;
|
| 727 |
flex: 0 0 auto;
|
| 728 |
+
background: rgba(255, 255, 255, 0.22);
|
| 729 |
+
}
|
| 730 |
+
|
| 731 |
+
.themeIcon[data-theme="cli"] {
|
| 732 |
+
background: #19f07a;
|
| 733 |
+
}
|
| 734 |
+
|
| 735 |
+
.themeIcon[data-theme="sakura"] {
|
| 736 |
+
background: #ec4899;
|
| 737 |
+
}
|
| 738 |
+
|
| 739 |
+
.themeIcon[data-theme="stars"] {
|
| 740 |
+
background: #8ab4ff;
|
| 741 |
+
}
|
| 742 |
+
|
| 743 |
+
.themeIcon[data-theme="day"] {
|
| 744 |
+
background: #2563eb;
|
| 745 |
+
}
|
| 746 |
+
|
| 747 |
+
.themeIcon[data-theme="night"] {
|
| 748 |
+
background: #6ea8fe;
|
| 749 |
}
|
| 750 |
|
| 751 |
.hint {
|
|
|
|
| 753 |
color: var(--muted);
|
| 754 |
}
|
| 755 |
|
| 756 |
+
.emptyState {
|
| 757 |
+
margin: auto;
|
| 758 |
+
padding: 28px 12px;
|
| 759 |
+
text-align: center;
|
| 760 |
+
color: var(--muted);
|
| 761 |
+
opacity: 0.78;
|
| 762 |
+
font-size: 13px;
|
| 763 |
+
}
|
| 764 |
+
|
| 765 |
@media (max-width: 420px) {
|
| 766 |
.seg {
|
| 767 |
grid-template-columns: 1fr;
|