root commited on
Commit
da5a45e
·
1 Parent(s): bac0e5d

Refine CLI theme and modernize layout

Browse files
Files changed (3) hide show
  1. public/app.js +13 -4
  2. public/index.html +19 -6
  3. 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
- el.checked = el.value === next;
 
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
- appendItem({ kind: "system", at: Date.now(), text: "暂无历史消息,开始聊天吧。" });
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
- document.getElementById("themeChoices").addEventListener("change", (e) => {
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="发送">发送</button>
 
 
 
 
 
 
 
 
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: #0a0f0c;
58
- --panel: rgba(25, 240, 122, 0.06);
59
- --panel2: rgba(25, 240, 122, 0.04);
60
- --text: rgba(221, 255, 230, 0.92);
61
- --muted: rgba(221, 255, 230, 0.62);
62
  --accent: #19f07a;
63
  --danger: #ff5c7a;
64
  --border: rgba(25, 240, 122, 0.18);
65
- --shadow: 0 18px 55px rgba(0, 0, 0, 0.55);
66
- --bg-pattern: radial-gradient(circle at 20% 10%, rgba(25, 240, 122, 0.06), transparent 52%),
67
- radial-gradient(circle at 70% 30%, rgba(25, 240, 122, 0.04), transparent 55%);
68
- --surface: rgba(25, 240, 122, 0.06);
69
- --surface2: rgba(25, 240, 122, 0.09);
70
- --bubble: rgba(25, 240, 122, 0.06);
71
- --bubble-me: rgba(25, 240, 122, 0.12);
72
- --input: rgba(25, 240, 122, 0.07);
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: 9px;
211
- height: 9px;
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
- padding: 12px 14px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.55);
 
 
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
- margin: 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
643
  flex: 0 0 auto;
644
- width: 16px;
645
- height: 16px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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;