Lashtw commited on
Commit
fcdbdc2
·
verified ·
1 Parent(s): d1095ed

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +254 -150
index.html CHANGED
@@ -3,15 +3,51 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Classroom Hub - 自由畫布倒數看板</title>
7
  <style>
8
  :root {
9
  --bg-gradient: radial-gradient(circle at 15% 15%, #0f172a 0%, #020617 100%);
10
- --card-bg: rgba(30, 41, 59, 0.45);
11
  --card-border: rgba(255, 255, 255, 0.12);
12
  --card-shadow: 0 20px 40px rgba(0, 0, 0, 0.45);
13
  --primary-text: #ffffff;
14
  --secondary-text: #94a3b8;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  }
16
 
17
  * {
@@ -29,12 +65,13 @@
29
  background: var(--bg-gradient);
30
  color: var(--primary-text);
31
  position: relative;
 
32
  }
33
 
34
- /* 頂部輕量化時鐘(固定在上方) */
35
  .top-bar {
36
  position: absolute;
37
- top: 24px;
38
  left: 50%;
39
  transform: translateX(-50%);
40
  text-align: center;
@@ -44,18 +81,17 @@
44
  .live-clock {
45
  font-size: 2.2rem;
46
  font-weight: 700;
 
47
  letter-spacing: -0.5px;
48
- color: #f8fafc;
49
  font-feature-settings: "tnum";
50
- text-shadow: 0 4px 12px rgba(0,0,0,0.5);
51
  }
52
  .live-date {
53
- font-size: 0.9rem;
54
  color: var(--secondary-text);
55
  letter-spacing: 2px;
56
  }
57
 
58
- /* 畫布工作區 */
59
  #canvas-workspace {
60
  width: 100vw;
61
  height: 100vh;
@@ -64,27 +100,29 @@
64
  left: 0;
65
  }
66
 
67
- /* 自由浮動卡片 */
 
 
68
  .board-card {
69
  position: absolute;
70
  background: var(--card-bg);
71
  border: 1px solid var(--card-border);
72
  backdrop-filter: blur(16px);
73
  -webkit-backdrop-filter: blur(16px);
74
- border-radius: 20px;
75
  box-shadow: var(--card-shadow);
76
  display: flex;
77
  flex-direction: column;
78
- padding: 20px;
79
  overflow: hidden;
80
  transition: border 0.2s, box-shadow 0.2s;
 
81
  }
82
 
83
- /* 分類標籤樣式與主色 */
84
- .board-card[data-cat="exam"] { --theme-color: #38bdf8; } /* 學業/考試:青藍 */
85
- .board-card[data-cat="sports"] { --theme-color: #f97316; } /* 體育/競賽:活力橘 */
86
- .board-card[data-cat="event"] { --theme-color: #ec4899; } /* 活動/校慶:桃紅 */
87
- .board-card[data-cat="life"] { --theme-color: #10b981; } /* 生活/常態:翡翠綠 */
88
 
89
  .board-card::before {
90
  content: '';
@@ -92,94 +130,127 @@
92
  top: 0;
93
  left: 0;
94
  right: 0;
95
- height: 4px;
96
- background: var(--theme-color);
 
97
  }
98
 
99
  .board-card.highlight {
100
- border: 1.5px solid #fbbf24;
101
- box-shadow: 0 10px 30px rgba(251, 191, 36, 0.25);
102
  }
103
  .board-card.highlight::before {
104
  background: #fbbf24;
105
- height: 6px;
106
  }
107
 
108
- /* 卡片頭部:圖示與標題 */
109
- .card-head {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  display: flex;
111
  align-items: center;
112
- gap: 10px;
113
- margin-bottom: 6px;
114
  }
 
115
  .icon-wrapper {
116
- width: 32px;
117
- height: 32px;
118
- border-radius: 8px;
119
  background: rgba(255, 255, 255, 0.08);
120
  display: flex;
121
  align-items: center;
122
  justify-content: center;
123
- color: var(--theme-color);
124
  flex-shrink: 0;
125
  }
 
 
 
 
126
  .board-card.highlight .icon-wrapper {
127
  color: #fbbf24;
128
- background: rgba(251, 191, 36, 0.15);
129
  }
 
130
  .card-title {
131
- font-size: 1.15rem;
132
  font-weight: 600;
 
133
  white-space: nowrap;
134
  overflow: hidden;
135
  text-overflow: ellipsis;
 
136
  }
137
 
138
  .card-date {
139
- font-size: 0.8rem;
140
  color: var(--secondary-text);
141
- margin-bottom: auto;
142
  }
143
 
144
- /* 倒數主核心區域 (隨卡片縮放自動適應) */
145
  .countdown-body {
146
  flex: 1;
147
  display: flex;
148
  align-items: baseline;
149
  justify-content: center;
150
- gap: 6px;
151
- margin: 10px 0;
152
  }
 
153
  .countdown-digits {
154
- font-size: 4rem;
155
  font-weight: 800;
156
- line-height: 1;
157
  letter-spacing: -2px;
158
  font-feature-settings: "tnum";
 
159
  }
 
160
  .board-card.highlight .countdown-digits {
161
  color: #fbbf24;
162
- text-shadow: 0 0 20px rgba(251, 191, 36, 0.4);
163
  }
 
164
  .countdown-unit {
165
- font-size: 1.1rem;
166
  color: var(--secondary-text);
 
167
  }
168
 
 
169
  .card-footer-status {
170
- text-align: center;
171
- font-size: 0.75rem;
172
- padding: 4px 10px;
173
  border-radius: 999px;
174
- background: rgba(255, 255, 255, 0.06);
175
  color: var(--secondary-text);
176
- width: fit-content;
177
- margin: 0 auto;
 
 
 
 
178
  }
179
 
180
- /* 編輯模式下的控制把手 */
181
  .is-editing .board-card {
182
- border: 1px dashed rgba(255, 255, 255, 0.4);
183
  cursor: move;
184
  }
185
  .resize-handle {
@@ -187,44 +258,46 @@
187
  position: absolute;
188
  right: 2px;
189
  bottom: 2px;
190
- width: 18px;
191
- height: 18px;
192
  cursor: se-resize;
193
- background: linear-gradient(135deg, transparent 50%, rgba(255, 255, 255, 0.6) 50%);
194
- border-bottom-right-radius: 16px;
 
195
  }
196
  .is-editing .resize-handle {
197
  display: block;
198
  }
199
 
200
- /* 頂部模式提示列 (編輯中才顯示) */
201
  .edit-banner {
202
  display: none;
203
  position: fixed;
204
- top: 15px;
205
  right: 20px;
206
- background: rgba(15, 23, 42, 0.9);
207
  border: 1px solid #38bdf8;
 
208
  padding: 8px 16px;
209
  border-radius: 12px;
210
  z-index: 1000;
211
  align-items: center;
212
  gap: 12px;
213
- box-shadow: 0 10px 25px rgba(0,0,0,0.5);
214
  }
215
  .is-editing .edit-banner {
216
  display: flex;
217
  }
218
 
219
- /* 右下角齒輪按鈕 */
220
  .settings-trigger {
221
  position: fixed;
222
  bottom: 20px;
223
  right: 20px;
224
- width: 44px;
225
- height: 44px;
226
  border-radius: 50%;
227
- background: rgba(15, 23, 42, 0.6);
228
  border: 1px solid rgba(255, 255, 255, 0.15);
229
  backdrop-filter: blur(8px);
230
  display: flex;
@@ -232,7 +305,7 @@
232
  justify-content: center;
233
  cursor: pointer;
234
  color: #94a3b8;
235
- opacity: 0.2;
236
  transition: all 0.2s;
237
  z-index: 500;
238
  }
@@ -242,12 +315,12 @@
242
  color: #fff;
243
  }
244
 
245
- /* 密碼驗證視窗與抽屜管理表單 (同商用規格) */
246
  .modal-backdrop {
247
  display: none;
248
  position: fixed;
249
  inset: 0;
250
- background: rgba(0, 0, 0, 0.7);
251
  backdrop-filter: blur(10px);
252
  z-index: 2000;
253
  align-items: center;
@@ -260,6 +333,7 @@
260
  padding: 28px;
261
  width: 320px;
262
  text-align: center;
 
263
  }
264
  .modal-box input {
265
  width: 100%;
@@ -287,14 +361,13 @@
287
  .btn-success { background: #10b981; color: #fff; }
288
  .btn-danger { background: #ef4444; color: #fff; }
289
 
290
- /* 管理面板抽屜 */
291
  .admin-drawer {
292
  display: none;
293
  position: fixed;
294
  top: 0;
295
  right: 0;
296
- width: 500px;
297
- max-width: 90vw;
298
  height: 100vh;
299
  background: #0f172a;
300
  border-left: 1px solid #334155;
@@ -316,52 +389,62 @@
316
  flex: 1;
317
  display: flex;
318
  flex-direction: column;
319
- gap: 15px;
 
 
 
 
 
 
 
 
 
320
  }
321
  .item-row {
322
  background: #1e293b;
323
  border: 1px solid #334155;
324
- border-radius: 8px;
325
- padding: 12px;
326
  display: flex;
327
  flex-direction: column;
328
- gap: 8px;
329
  }
330
  .item-inputs {
331
  display: grid;
332
  grid-template-columns: 1fr 1fr;
333
  gap: 8px;
334
  }
335
- .item-row input, .item-row select {
336
  background: #0f172a;
337
  border: 1px solid #334155;
338
  color: #fff;
339
- padding: 6px 8px;
340
  border-radius: 6px;
341
  font-size: 0.85rem;
342
  }
343
  </style>
344
  </head>
345
- <body>
346
 
 
347
  <div class="top-bar">
348
  <div class="live-clock" id="liveClock">00:00:00</div>
349
  <div class="live-date" id="liveDate">LOADING CALENDAR...</div>
350
  </div>
351
 
352
- <!-- 自由拖拉與縮放工作區 -->
353
  <div id="canvas-workspace"></div>
354
 
355
- <!-- 編輯狀態浮動列 -->
356
  <div class="edit-banner" id="editBanner">
357
- <span style="font-size:0.85rem; color:#38bdf8;">✦ 自由排版中:可任意拖動卡片或拉動右下角邊緣</span>
358
- <button class="btn btn-secondary" onclick="openAdminDrawer()">項目清單</button>
359
  <button class="btn btn-success" onclick="exitEditMode()">完成並鎖定</button>
360
  </div>
361
 
362
- <!-- 齒輪解鎖點擊 -->
363
- <div class="settings-trigger" onclick="openAuthModal()" title="進入編輯排版">
364
- <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
365
  <path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.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.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.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 .44-.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-.47-.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"/>
366
  </svg>
367
  </div>
@@ -369,7 +452,7 @@
369
  <!-- 密碼 Modal -->
370
  <div class="modal-backdrop" id="authModal">
371
  <div class="modal-box">
372
- <h3>進入排版管理</h3>
373
  <p style="color:#94a3b8; font-size:0.8rem; margin-top:6px;">請輸入管理員密碼</p>
374
  <input type="password" id="authPassword" placeholder="PIN" maxlength="6" onkeydown="if(event.key==='Enter') verifyPassword()">
375
  <div style="display:flex; justify-content:center; gap:8px;">
@@ -379,61 +462,85 @@
379
  </div>
380
  </div>
381
 
382
- <!-- 後台項目抽屜面板 -->
383
  <div class="admin-drawer" id="adminDrawer">
384
  <div class="drawer-header">
385
- <h3>倒數項目庫 (Data Editor)</h3>
386
  <button class="btn btn-secondary" onclick="closeAdminDrawer()">關閉</button>
387
  </div>
 
388
  <div class="drawer-content">
389
- <div style="display:flex; justify-content:space-between; align-items:center;">
390
- <span style="font-size:0.85rem; color:#94a3b8;">編輯項目資訊與樣式類別</span>
 
 
 
 
 
 
 
 
 
 
 
391
  <button class="btn btn-primary" onclick="addNewEvent()">+ 新增卡片</button>
392
  </div>
 
393
  <div id="editorItemList" style="display:flex; flex-direction:column; gap:10px;"></div>
394
  </div>
 
395
  <div style="padding:16px; border-top:1px solid #1e293b; display:flex; justify-content:flex-end;">
396
- <button class="btn btn-success" onclick="saveEditorList()">儲存更新</button>
397
  </div>
398
  </div>
399
 
400
  <script>
401
- // 預設向量圖示集合 (SVG)
402
  const ICONS = {
403
- exam: `<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path></svg>`,
404
- sports: `<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M6 9H4.5a2.5 2.5 0 0 1 0-5H6"></path><path d="M18 9h1.5a2.5 2.5 0 0 0 0-5H18"></path><path d="M4 22h16"></path><path d="M10 14.66V17c0 .55-.45 1-1 1H7v4h10v-4h-2c-.55 0-1-.45-1-1v-2.34c3.08-.63 5.4-3.3 5.4-6.66V4H4v4c0 3.36 2.32 6.03 5.4 6.66z"></path></svg>`,
405
- event: `<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"></path><line x1="4" y1="22" x2="4" y2="15"></line></svg>`,
406
- life: `<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>`
407
  };
408
 
409
- // 初始位置與資料(若無本地快取則套用此預設值)
410
- const DEFAULT_DATA = [
411
- { id: '1', title: "第一次定期評量", date: "2026-10-15", cat: "exam", highlight: false, x: 140, y: 180, w: 290, h: 220 },
412
- { id: '2', title: "校慶運動大會", date: "2026-11-20", cat: "sports", highlight: false, x: 470, y: 180, w: 290, h: 220 },
413
- { id: '3', title: "國中教育會考", date: "2027-05-15", cat: "exam", highlight: true, x: 800, y: 160, w: 320, h: 250 }
414
- ];
 
 
 
415
 
416
- let cards = loadCardsData();
417
  let isEditing = false;
418
 
419
- function loadCardsData() {
420
- const saved = localStorage.getItem("freeform_countdown_cards");
421
  return saved ? JSON.parse(saved) : DEFAULT_DATA;
422
  }
423
 
424
- function saveCardsData() {
425
- localStorage.setItem("freeform_countdown_cards", JSON.stringify(cards));
 
 
 
 
 
 
426
  }
427
 
428
- // 渲染所有卡片
429
  function renderBoard() {
 
430
  const ws = document.getElementById("canvas-workspace");
431
  ws.innerHTML = "";
432
 
433
  const today = new Date();
434
  today.setHours(0,0,0,0);
435
 
436
- cards.forEach(card => {
437
  const target = new Date(card.date + "T00:00:00");
438
  const diffDays = Math.ceil((target - today) / (1000 * 60 * 60 * 24));
439
  const displayDays = diffDays >= 0 ? diffDays : 0;
@@ -448,21 +555,24 @@
448
  el.style.width = `${card.w}px`;
449
  el.style.height = `${card.h}px`;
450
 
451
- // 依據高度微調字體級距
452
- const digitFontSize = Math.max(2.2, Math.min(card.h / 50, 4.8));
453
-
454
  el.innerHTML = `
455
- <div class="card-head">
456
- <div class="icon-wrapper">${ICONS[card.cat] || ICONS.life}</div>
457
- <div class="card-title">${card.title}</div>
 
 
 
 
 
 
 
 
 
 
 
 
458
  </div>
459
- <div class="card-date">${card.date.replace(/-/g, '.')}</div>
460
- <div class="countdown-body">
461
- <span class="countdown-digits" style="font-size: ${digitFontSize}rem;">${displayDays}</span>
462
- <span class="countdown-unit">天</span>
463
- </div>
464
- <div class="card-footer-status">${badgeText}</div>
465
- <div class="resize-handle" data-id="${card.id}"></div>
466
  `;
467
 
468
  makeDraggable(el, card);
@@ -472,7 +582,7 @@
472
  });
473
  }
474
 
475
- // 拖曳定位邏輯 (Drag)
476
  function makeDraggable(el, cardData) {
477
  let isDragging = false;
478
  let startX, startY, initLeft, initTop;
@@ -488,11 +598,8 @@
488
 
489
  function onMouseMove(e) {
490
  if (!isDragging) return;
491
- const dx = e.clientX - startX;
492
- const dy = e.clientY - startY;
493
- const newX = Math.max(0, Math.min(window.innerWidth - el.offsetWidth, initLeft + dx));
494
- const newY = Math.max(0, Math.min(window.innerHeight - el.offsetHeight, initTop + dy));
495
-
496
  el.style.left = `${newX}px`;
497
  el.style.top = `${newY}px`;
498
  cardData.x = newX;
@@ -504,7 +611,7 @@
504
  el.style.zIndex = '';
505
  window.removeEventListener('mousemove', onMouseMove);
506
  window.removeEventListener('mouseup', onMouseUp);
507
- saveCardsData();
508
  }
509
 
510
  window.addEventListener('mousemove', onMouseMove);
@@ -512,7 +619,7 @@
512
  });
513
  }
514
 
515
- // 右下角手柄邊界縮放邏輯 (Resize)
516
  function makeResizable(handle, cardData, cardEl) {
517
  let isResizing = false;
518
  let startW, startH, startX, startY;
@@ -528,26 +635,22 @@
528
 
529
  function onMouseMove(e) {
530
  if (!isResizing) return;
531
- const newW = Math.max(220, startW + (e.clientX - startX));
 
532
  const newH = Math.max(160, startH + (e.clientY - startY));
533
 
534
  cardEl.style.width = `${newW}px`;
535
  cardEl.style.height = `${newH}px`;
536
  cardData.w = newW;
537
  cardData.h = newH;
538
-
539
- // 即時連動數���大小
540
- const digits = cardEl.querySelector('.countdown-digits');
541
- if (digits) {
542
- digits.style.fontSize = `${Math.max(2.2, Math.min(newH / 50, 4.8))}rem`;
543
- }
544
  }
545
 
546
  function onMouseUp() {
547
  isResizing = false;
548
  window.removeEventListener('mousemove', onMouseMove);
549
  window.removeEventListener('mouseup', onMouseUp);
550
- saveCardsData();
551
  }
552
 
553
  window.addEventListener('mousemove', onMouseMove);
@@ -555,7 +658,7 @@
555
  });
556
  }
557
 
558
- // 密碼與編輯模式開關
559
  function openAuthModal() {
560
  document.getElementById("authPassword").value = "";
561
  document.getElementById("authModal").style.display = "flex";
@@ -581,11 +684,12 @@
581
  function exitEditMode() {
582
  isEditing = false;
583
  document.body.classList.remove("is-editing");
584
- saveCardsData();
585
  }
586
 
587
- // 後台抽屜列表管理
588
  function openAdminDrawer() {
 
589
  renderDrawerEditor();
590
  document.getElementById("adminDrawer").style.display = "flex";
591
  }
@@ -596,23 +700,23 @@
596
  function renderDrawerEditor() {
597
  const container = document.getElementById("editorItemList");
598
  container.innerHTML = "";
599
- cards.forEach((item, index) => {
600
  const row = document.createElement("div");
601
  row.className = "item-row";
602
  row.innerHTML = `
603
  <div class="item-inputs">
604
- <input type="text" value="${item.title}" placeholder="名稱" onchange="cards[${index}].title=this.value">
605
- <input type="date" value="${item.date}" onchange="cards[${index}].date=this.value">
606
  </div>
607
  <div style="display:flex; justify-content:space-between; align-items:center;">
608
- <select onchange="cards[${index}].cat=this.value">
609
  <option value="exam" ${item.cat==='exam'?'selected':''}>學業考試 (書本)</option>
610
  <option value="sports" ${item.cat==='sports'?'selected':''}>運動競賽 (獎盃)</option>
611
- <option value="event" ${item.cat==='event'?'selected':''}>慶典活動 (旗幟)</option>
612
- <option value="life" ${item.cat==='life'?'selected':''}>生活常態 (時鐘)</option>
613
  </select>
614
- <label style="font-size:0.8rem; color:#cbd5e1; display:flex; align-items:center; gap:4px;">
615
- <input type="checkbox" ${item.highlight ? 'checked':''} onchange="cards[${index}].highlight=this.checked"> 重要高亮
616
  </label>
617
  <button class="btn btn-danger" style="padding:4px 8px;" onclick="removeCard(${index})">刪除</button>
618
  </div>
@@ -623,32 +727,32 @@
623
 
624
  function addNewEvent() {
625
  const todayStr = new Date().toISOString().split('T')[0];
626
- cards.push({
627
  id: String(Date.now()),
628
  title: "新倒數目標",
629
  date: todayStr,
630
  cat: "exam",
631
  highlight: false,
632
- x: 100 + (cards.length * 30),
633
  y: 200,
634
  w: 280,
635
- h: 210
636
  });
637
  renderDrawerEditor();
638
  }
639
 
640
  function removeCard(idx) {
641
- cards.splice(idx, 1);
642
  renderDrawerEditor();
643
  }
644
 
645
  function saveEditorList() {
646
- saveCardsData();
647
  renderBoard();
648
  closeAdminDrawer();
649
  }
650
 
651
- // 時鐘更新
652
  function updateClock() {
653
  const now = new Date();
654
  const h = String(now.getHours()).padStart(2, '0');
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Classroom Hub - 自適應倒數看板</title>
7
  <style>
8
  :root {
9
  --bg-gradient: radial-gradient(circle at 15% 15%, #0f172a 0%, #020617 100%);
10
+ --card-bg: rgba(30, 41, 59, 0.55);
11
  --card-border: rgba(255, 255, 255, 0.12);
12
  --card-shadow: 0 20px 40px rgba(0, 0, 0, 0.45);
13
  --primary-text: #ffffff;
14
  --secondary-text: #94a3b8;
15
+ --badge-bg: rgba(255, 255, 255, 0.08);
16
+ }
17
+
18
+ /* 全域主題樣式切換 */
19
+ body[data-theme="dark-slate"] {
20
+ --bg-gradient: radial-gradient(circle at 15% 15%, #1e293b 0%, #020617 100%);
21
+ --card-bg: rgba(30, 41, 59, 0.55);
22
+ --card-border: rgba(255, 255, 255, 0.12);
23
+ --primary-text: #ffffff;
24
+ --secondary-text: #94a3b8;
25
+ --badge-bg: rgba(255, 255, 255, 0.08);
26
+ }
27
+ body[data-theme="midnight-purple"] {
28
+ --bg-gradient: radial-gradient(circle at 20% 20%, #2e1065 0%, #0b0217 100%);
29
+ --card-bg: rgba(46, 16, 101, 0.4);
30
+ --card-border: rgba(192, 132, 252, 0.2);
31
+ --primary-text: #ffffff;
32
+ --secondary-text: #d8b4fe;
33
+ --badge-bg: rgba(192, 132, 252, 0.12);
34
+ }
35
+ body[data-theme="forest-calm"] {
36
+ --bg-gradient: radial-gradient(circle at 20% 20%, #064e3b 0%, #021a14 100%);
37
+ --card-bg: rgba(6, 78, 59, 0.4);
38
+ --card-border: rgba(52, 211, 153, 0.2);
39
+ --primary-text: #ffffff;
40
+ --secondary-text: #a7f3d0;
41
+ --badge-bg: rgba(52, 211, 153, 0.12);
42
+ }
43
+ body[data-theme="minimal-light"] {
44
+ --bg-gradient: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
45
+ --card-bg: rgba(255, 255, 255, 0.85);
46
+ --card-border: rgba(203, 213, 225, 0.8);
47
+ --card-shadow: 0 15px 30px rgba(0, 0, 0, 0.08);
48
+ --primary-text: #0f172a;
49
+ --secondary-text: #64748b;
50
+ --badge-bg: rgba(0, 0, 0, 0.06);
51
  }
52
 
53
  * {
 
65
  background: var(--bg-gradient);
66
  color: var(--primary-text);
67
  position: relative;
68
+ transition: background 0.4s ease;
69
  }
70
 
71
+ /* 頂部輕量時鐘 */
72
  .top-bar {
73
  position: absolute;
74
+ top: 20px;
75
  left: 50%;
76
  transform: translateX(-50%);
77
  text-align: center;
 
81
  .live-clock {
82
  font-size: 2.2rem;
83
  font-weight: 700;
84
+ color: var(--primary-text);
85
  letter-spacing: -0.5px;
 
86
  font-feature-settings: "tnum";
87
+ text-shadow: 0 4px 12px rgba(0,0,0,0.4);
88
  }
89
  .live-date {
90
+ font-size: 0.85rem;
91
  color: var(--secondary-text);
92
  letter-spacing: 2px;
93
  }
94
 
 
95
  #canvas-workspace {
96
  width: 100vw;
97
  height: 100vh;
 
100
  left: 0;
101
  }
102
 
103
+ /* =========================================
104
+ 自適應卡片容器 (Container Query 架構)
105
+ ========================================= */
106
  .board-card {
107
  position: absolute;
108
  background: var(--card-bg);
109
  border: 1px solid var(--card-border);
110
  backdrop-filter: blur(16px);
111
  -webkit-backdrop-filter: blur(16px);
112
+ border-radius: 24px;
113
  box-shadow: var(--card-shadow);
114
  display: flex;
115
  flex-direction: column;
 
116
  overflow: hidden;
117
  transition: border 0.2s, box-shadow 0.2s;
118
+ container-type: size; /* 讓內部元件能根據卡片尺寸等比自適應 */
119
  }
120
 
121
+ /* 分類主題色 */
122
+ .board-card[data-cat="exam"] { --cat-color: #38bdf8; }
123
+ .board-card[data-cat="sports"] { --cat-color: #f97316; }
124
+ .board-card[data-cat="event"] { --cat-color: #ec4899; }
125
+ .board-card[data-cat="life"] { --cat-color: #10b981; }
126
 
127
  .board-card::before {
128
  content: '';
 
130
  top: 0;
131
  left: 0;
132
  right: 0;
133
+ height: clamp(4px, 1.8cqh, 8px);
134
+ background: var(--cat-color);
135
+ z-index: 2;
136
  }
137
 
138
  .board-card.highlight {
139
+ border: 1.8px solid #fbbf24;
140
+ box-shadow: 0 15px 35px rgba(251, 191, 36, 0.25);
141
  }
142
  .board-card.highlight::before {
143
  background: #fbbf24;
144
+ height: clamp(5px, 2.2cqh, 10px);
145
  }
146
 
147
+ /* 卡片內部排版:使用容器單位 (cqw, cqh) */
148
+ .card-content-box {
149
+ width: 100%;
150
+ height: 100%;
151
+ padding: clamp(14px, 6cqw, 36px) clamp(16px, 7cqw, 40px);
152
+ display: flex;
153
+ flex-direction: column;
154
+ justify-content: space-between;
155
+ }
156
+
157
+ .card-header-area {
158
+ display: flex;
159
+ flex-direction: column;
160
+ gap: clamp(4px, 1.5cqh, 10px);
161
+ }
162
+
163
+ .card-title-row {
164
  display: flex;
165
  align-items: center;
166
+ gap: clamp(8px, 2.5cqw, 16px);
 
167
  }
168
+
169
  .icon-wrapper {
170
+ width: clamp(28px, 11cqw, 54px);
171
+ height: clamp(28px, 11cqw, 54px);
172
+ border-radius: clamp(8px, 2.5cqw, 14px);
173
  background: rgba(255, 255, 255, 0.08);
174
  display: flex;
175
  align-items: center;
176
  justify-content: center;
177
+ color: var(--cat-color);
178
  flex-shrink: 0;
179
  }
180
+ .icon-wrapper svg {
181
+ width: 60%;
182
+ height: 60%;
183
+ }
184
  .board-card.highlight .icon-wrapper {
185
  color: #fbbf24;
186
+ background: rgba(251, 191, 36, 0.16);
187
  }
188
+
189
  .card-title {
190
+ font-size: clamp(1rem, 6.5cqw, 2.2rem);
191
  font-weight: 600;
192
+ color: var(--primary-text);
193
  white-space: nowrap;
194
  overflow: hidden;
195
  text-overflow: ellipsis;
196
+ line-height: 1.2;
197
  }
198
 
199
  .card-date {
200
+ font-size: clamp(0.75rem, 4.2cqw, 1.15rem);
201
  color: var(--secondary-text);
202
+ letter-spacing: 0.5px;
203
  }
204
 
205
+ /* 倒數主核心:天數動態膨脹填滿中段 */
206
  .countdown-body {
207
  flex: 1;
208
  display: flex;
209
  align-items: baseline;
210
  justify-content: center;
211
+ gap: clamp(4px, 1.5cqw, 12px);
212
+ min-height: 0;
213
  }
214
+
215
  .countdown-digits {
216
+ font-size: clamp(2.8rem, 24cqw, 14rem); /* 隨寬度與高度巨大化放大 */
217
  font-weight: 800;
218
+ line-height: 0.95;
219
  letter-spacing: -2px;
220
  font-feature-settings: "tnum";
221
+ color: var(--primary-text);
222
  }
223
+
224
  .board-card.highlight .countdown-digits {
225
  color: #fbbf24;
226
+ text-shadow: 0 0 clamp(15px, 5cqw, 35px) rgba(251, 191, 36, 0.45);
227
  }
228
+
229
  .countdown-unit {
230
+ font-size: clamp(0.9rem, 5.5cqw, 2.2rem);
231
  color: var(--secondary-text);
232
+ font-weight: 500;
233
  }
234
 
235
+ /* 底部狀態膠囊標籤 */
236
  .card-footer-status {
237
+ align-self: center;
238
+ font-size: clamp(0.7rem, 3.8cqw, 1.05rem);
239
+ padding: clamp(4px, 1.5cqh, 8px) clamp(10px, 4cqw, 24px);
240
  border-radius: 999px;
241
+ background: var(--badge-bg);
242
  color: var(--secondary-text);
243
+ font-weight: 500;
244
+ white-space: nowrap;
245
+ }
246
+ .board-card.highlight .card-footer-status {
247
+ background: rgba(251, 191, 36, 0.15);
248
+ color: #fbbf24;
249
  }
250
 
251
+ /* 編輯排版模式樣式 */
252
  .is-editing .board-card {
253
+ border: 1.5px dashed #38bdf8 !important;
254
  cursor: move;
255
  }
256
  .resize-handle {
 
258
  position: absolute;
259
  right: 2px;
260
  bottom: 2px;
261
+ width: 24px;
262
+ height: 24px;
263
  cursor: se-resize;
264
+ background: linear-gradient(135deg, transparent 50%, rgba(56, 189, 248, 0.8) 50%);
265
+ border-bottom-right-radius: 18px;
266
+ z-index: 10;
267
  }
268
  .is-editing .resize-handle {
269
  display: block;
270
  }
271
 
272
+ /* 編輯浮動狀態條 */
273
  .edit-banner {
274
  display: none;
275
  position: fixed;
276
+ top: 16px;
277
  right: 20px;
278
+ background: rgba(15, 23, 42, 0.95);
279
  border: 1px solid #38bdf8;
280
+ backdrop-filter: blur(8px);
281
  padding: 8px 16px;
282
  border-radius: 12px;
283
  z-index: 1000;
284
  align-items: center;
285
  gap: 12px;
286
+ box-shadow: 0 10px 30px rgba(0,0,0,0.5);
287
  }
288
  .is-editing .edit-banner {
289
  display: flex;
290
  }
291
 
292
+ /* 齒輪圖示 */
293
  .settings-trigger {
294
  position: fixed;
295
  bottom: 20px;
296
  right: 20px;
297
+ width: 46px;
298
+ height: 46px;
299
  border-radius: 50%;
300
+ background: rgba(15, 23, 42, 0.7);
301
  border: 1px solid rgba(255, 255, 255, 0.15);
302
  backdrop-filter: blur(8px);
303
  display: flex;
 
305
  justify-content: center;
306
  cursor: pointer;
307
  color: #94a3b8;
308
+ opacity: 0.25;
309
  transition: all 0.2s;
310
  z-index: 500;
311
  }
 
315
  color: #fff;
316
  }
317
 
318
+ /* 彈窗與抽屜 */
319
  .modal-backdrop {
320
  display: none;
321
  position: fixed;
322
  inset: 0;
323
+ background: rgba(0, 0, 0, 0.75);
324
  backdrop-filter: blur(10px);
325
  z-index: 2000;
326
  align-items: center;
 
333
  padding: 28px;
334
  width: 320px;
335
  text-align: center;
336
+ color: #fff;
337
  }
338
  .modal-box input {
339
  width: 100%;
 
361
  .btn-success { background: #10b981; color: #fff; }
362
  .btn-danger { background: #ef4444; color: #fff; }
363
 
 
364
  .admin-drawer {
365
  display: none;
366
  position: fixed;
367
  top: 0;
368
  right: 0;
369
+ width: 520px;
370
+ max-width: 92vw;
371
  height: 100vh;
372
  background: #0f172a;
373
  border-left: 1px solid #334155;
 
389
  flex: 1;
390
  display: flex;
391
  flex-direction: column;
392
+ gap: 18px;
393
+ }
394
+ .theme-control-box {
395
+ background: #1e293b;
396
+ border: 1px solid #334155;
397
+ border-radius: 10px;
398
+ padding: 14px;
399
+ display: flex;
400
+ justify-content: space-between;
401
+ align-items: center;
402
  }
403
  .item-row {
404
  background: #1e293b;
405
  border: 1px solid #334155;
406
+ border-radius: 10px;
407
+ padding: 14px;
408
  display: flex;
409
  flex-direction: column;
410
+ gap: 10px;
411
  }
412
  .item-inputs {
413
  display: grid;
414
  grid-template-columns: 1fr 1fr;
415
  gap: 8px;
416
  }
417
+ .item-row input, .item-row select, .theme-control-box select {
418
  background: #0f172a;
419
  border: 1px solid #334155;
420
  color: #fff;
421
+ padding: 7px 10px;
422
  border-radius: 6px;
423
  font-size: 0.85rem;
424
  }
425
  </style>
426
  </head>
427
+ <body data-theme="dark-slate">
428
 
429
+ <!-- 頂部即時時鐘 -->
430
  <div class="top-bar">
431
  <div class="live-clock" id="liveClock">00:00:00</div>
432
  <div class="live-date" id="liveDate">LOADING CALENDAR...</div>
433
  </div>
434
 
435
+ <!-- 自由自適應畫布 -->
436
  <div id="canvas-workspace"></div>
437
 
438
+ <!-- 編輯狀態控制頂列 -->
439
  <div class="edit-banner" id="editBanner">
440
+ <span style="font-size:0.85rem; color:#38bdf8;">✦ 自由排版中:拖動卡片改變位置,拉右下角角標縮放</span>
441
+ <button class="btn btn-secondary" onclick="openAdminDrawer()">設定與項目</button>
442
  <button class="btn btn-success" onclick="exitEditMode()">完成並鎖定</button>
443
  </div>
444
 
445
+ <!-- 右下角隱密齒輪 -->
446
+ <div class="settings-trigger" onclick="openAuthModal()" title="排版設定">
447
+ <svg width="22" height="22" fill="currentColor" viewBox="0 0 24 24">
448
  <path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.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.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.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 .44-.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-.47-.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"/>
449
  </svg>
450
  </div>
 
452
  <!-- 密碼 Modal -->
453
  <div class="modal-backdrop" id="authModal">
454
  <div class="modal-box">
455
+ <h3>進入看板管理</h3>
456
  <p style="color:#94a3b8; font-size:0.8rem; margin-top:6px;">請輸入管理員密碼</p>
457
  <input type="password" id="authPassword" placeholder="PIN" maxlength="6" onkeydown="if(event.key==='Enter') verifyPassword()">
458
  <div style="display:flex; justify-content:center; gap:8px;">
 
462
  </div>
463
  </div>
464
 
465
+ <!-- 側拉管理抽屜 -->
466
  <div class="admin-drawer" id="adminDrawer">
467
  <div class="drawer-header">
468
+ <h3>看板設定與項目管理</h3>
469
  <button class="btn btn-secondary" onclick="closeAdminDrawer()">關閉</button>
470
  </div>
471
+
472
  <div class="drawer-content">
473
+ <!-- 主題風格選擇區塊 -->
474
+ <div class="theme-control-box">
475
+ <span style="font-weight:600;">整體版面風格</span>
476
+ <select id="themeSelect" onchange="changeTheme(this.value)">
477
+ <option value="dark-slate">深邃板岩灰 (Dark Slate)</option>
478
+ <option value="midnight-purple">星夜紫韻 (Midnight Purple)</option>
479
+ <option value="forest-calm">靜謐青綠 (Forest Calm)</option>
480
+ <option value="minimal-light">晨光清透 (Minimal Light)</option>
481
+ </select>
482
+ </div>
483
+
484
+ <div style="display:flex; justify-content:space-between; align-items:center; margin-top:8px;">
485
+ <span style="font-size:0.85rem; color:#94a3b8;">倒數項目清單</span>
486
  <button class="btn btn-primary" onclick="addNewEvent()">+ 新增卡片</button>
487
  </div>
488
+
489
  <div id="editorItemList" style="display:flex; flex-direction:column; gap:10px;"></div>
490
  </div>
491
+
492
  <div style="padding:16px; border-top:1px solid #1e293b; display:flex; justify-content:flex-end;">
493
+ <button class="btn btn-success" onclick="saveEditorList()">儲存並發布</button>
494
  </div>
495
  </div>
496
 
497
  <script>
498
+ // 精緻 SVG 向量圖示庫
499
  const ICONS = {
500
+ exam: `<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path></svg>`,
501
+ sports: `<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M6 9H4.5a2.5 2.5 0 0 1 0-5H6"></path><path d="M18 9h1.5a2.5 2.5 0 0 0 0-5H18"></path><path d="M4 22h16"></path><path d="M10 14.66V17c0 .55-.45 1-1 1H7v4h10v-4h-2c-.55 0-1-.45-1-1v-2.34c3.08-.63 5.4-3.3 5.4-6.66V4H4v4c0 3.36 2.32 6.03 5.4 6.66z"></path></svg>`,
502
+ event: `<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"></path><line x1="4" y1="22" x2="4" y2="15"></line></svg>`,
503
+ life: `<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>`
504
  };
505
 
506
+ // 初始預設資料
507
+ const DEFAULT_DATA = {
508
+ theme: "dark-slate",
509
+ cards: [
510
+ { id: '1', title: "第一次定期評量", date: "2026-10-15", cat: "exam", highlight: false, x: 100, y: 150, w: 300, h: 250 },
511
+ { id: '2', title: "校慶運動大會", date: "2026-11-20", cat: "sports", highlight: false, x: 100, y: 430, w: 300, h: 250 },
512
+ { id: '3', title: "國中教育���考", date: "2027-05-15", cat: "exam", highlight: true, x: 440, y: 150, w: 420, h: 530 }
513
+ ]
514
+ };
515
 
516
+ let appState = loadAppData();
517
  let isEditing = false;
518
 
519
+ function loadAppData() {
520
+ const saved = localStorage.getItem("classroom_dashboard_data");
521
  return saved ? JSON.parse(saved) : DEFAULT_DATA;
522
  }
523
 
524
+ function saveAppData() {
525
+ localStorage.setItem("classroom_dashboard_data", JSON.stringify(appState));
526
+ }
527
+
528
+ // 切換主題
529
+ function changeTheme(themeName) {
530
+ appState.theme = themeName;
531
+ document.body.setAttribute("data-theme", themeName);
532
  }
533
 
534
+ // 渲染整個自由畫布
535
  function renderBoard() {
536
+ document.body.setAttribute("data-theme", appState.theme);
537
  const ws = document.getElementById("canvas-workspace");
538
  ws.innerHTML = "";
539
 
540
  const today = new Date();
541
  today.setHours(0,0,0,0);
542
 
543
+ appState.cards.forEach(card => {
544
  const target = new Date(card.date + "T00:00:00");
545
  const diffDays = Math.ceil((target - today) / (1000 * 60 * 60 * 24));
546
  const displayDays = diffDays >= 0 ? diffDays : 0;
 
555
  el.style.width = `${card.w}px`;
556
  el.style.height = `${card.h}px`;
557
 
 
 
 
558
  el.innerHTML = `
559
+ <div class="card-content-box">
560
+ <div class="card-header-area">
561
+ <div class="card-title-row">
562
+ <div class="icon-wrapper">${ICONS[card.cat] || ICONS.life}</div>
563
+ <div class="card-title">${card.title}</div>
564
+ </div>
565
+ <div class="card-date">${card.date.replace(/-/g, '.')}</div>
566
+ </div>
567
+
568
+ <div class="countdown-body">
569
+ <span class="countdown-digits">${displayDays}</span>
570
+ <span class="countdown-unit">天</span>
571
+ </div>
572
+
573
+ <div class="card-footer-status">${badgeText}</div>
574
  </div>
575
+ <div class="resize-handle"></div>
 
 
 
 
 
 
576
  `;
577
 
578
  makeDraggable(el, card);
 
582
  });
583
  }
584
 
585
+ // 拖動定位
586
  function makeDraggable(el, cardData) {
587
  let isDragging = false;
588
  let startX, startY, initLeft, initTop;
 
598
 
599
  function onMouseMove(e) {
600
  if (!isDragging) return;
601
+ const newX = Math.max(0, Math.min(window.innerWidth - el.offsetWidth, initLeft + (e.clientX - startX)));
602
+ const newY = Math.max(0, Math.min(window.innerHeight - el.offsetHeight, initTop + (e.clientY - startY)));
 
 
 
603
  el.style.left = `${newX}px`;
604
  el.style.top = `${newY}px`;
605
  cardData.x = newX;
 
611
  el.style.zIndex = '';
612
  window.removeEventListener('mousemove', onMouseMove);
613
  window.removeEventListener('mouseup', onMouseUp);
614
+ saveAppData();
615
  }
616
 
617
  window.addEventListener('mousemove', onMouseMove);
 
619
  });
620
  }
621
 
622
+ // 右下角拖拉自適應縮放
623
  function makeResizable(handle, cardData, cardEl) {
624
  let isResizing = false;
625
  let startW, startH, startX, startY;
 
635
 
636
  function onMouseMove(e) {
637
  if (!isResizing) return;
638
+ // 限制最小尺寸,避免過小破版
639
+ const newW = Math.max(200, startW + (e.clientX - startX));
640
  const newH = Math.max(160, startH + (e.clientY - startY));
641
 
642
  cardEl.style.width = `${newW}px`;
643
  cardEl.style.height = `${newH}px`;
644
  cardData.w = newW;
645
  cardData.h = newH;
646
+ // container query 會自動完成內部文字與數字等比自適應放大,不需手動換算
 
 
 
 
 
647
  }
648
 
649
  function onMouseUp() {
650
  isResizing = false;
651
  window.removeEventListener('mousemove', onMouseMove);
652
  window.removeEventListener('mouseup', onMouseUp);
653
+ saveAppData();
654
  }
655
 
656
  window.addEventListener('mousemove', onMouseMove);
 
658
  });
659
  }
660
 
661
+ // 密碼與狀態
662
  function openAuthModal() {
663
  document.getElementById("authPassword").value = "";
664
  document.getElementById("authModal").style.display = "flex";
 
684
  function exitEditMode() {
685
  isEditing = false;
686
  document.body.classList.remove("is-editing");
687
+ saveAppData();
688
  }
689
 
690
+ // 抽屜面板控制
691
  function openAdminDrawer() {
692
+ document.getElementById("themeSelect").value = appState.theme;
693
  renderDrawerEditor();
694
  document.getElementById("adminDrawer").style.display = "flex";
695
  }
 
700
  function renderDrawerEditor() {
701
  const container = document.getElementById("editorItemList");
702
  container.innerHTML = "";
703
+ appState.cards.forEach((item, index) => {
704
  const row = document.createElement("div");
705
  row.className = "item-row";
706
  row.innerHTML = `
707
  <div class="item-inputs">
708
+ <input type="text" value="${item.title}" placeholder="名稱" onchange="appState.cards[${index}].title=this.value">
709
+ <input type="date" value="${item.date}" onchange="appState.cards[${index}].date=this.value">
710
  </div>
711
  <div style="display:flex; justify-content:space-between; align-items:center;">
712
+ <select onchange="appState.cards[${index}].cat=this.value">
713
  <option value="exam" ${item.cat==='exam'?'selected':''}>學業考試 (書本)</option>
714
  <option value="sports" ${item.cat==='sports'?'selected':''}>運動競賽 (獎盃)</option>
715
+ <option value="event" ${item.cat==='event'?'selected':''}>校慶活動 (旗幟)</option>
716
+ <option value="life" ${item.cat==='life'?'selected':''}>常態生活 (時鐘)</option>
717
  </select>
718
+ <label style="font-size:0.8rem; color:#cbd5e1; display:flex; align-items:center; gap:4px; cursor:pointer;">
719
+ <input type="checkbox" ${item.highlight ? 'checked':''} onchange="appState.cards[${index}].highlight=this.checked"> 重要高亮
720
  </label>
721
  <button class="btn btn-danger" style="padding:4px 8px;" onclick="removeCard(${index})">刪除</button>
722
  </div>
 
727
 
728
  function addNewEvent() {
729
  const todayStr = new Date().toISOString().split('T')[0];
730
+ appState.cards.push({
731
  id: String(Date.now()),
732
  title: "新倒數目標",
733
  date: todayStr,
734
  cat: "exam",
735
  highlight: false,
736
+ x: 100 + (appState.cards.length * 30),
737
  y: 200,
738
  w: 280,
739
+ h: 220
740
  });
741
  renderDrawerEditor();
742
  }
743
 
744
  function removeCard(idx) {
745
+ appState.cards.splice(idx, 1);
746
  renderDrawerEditor();
747
  }
748
 
749
  function saveEditorList() {
750
+ saveAppData();
751
  renderBoard();
752
  closeAdminDrawer();
753
  }
754
 
755
+ // 即時時鐘
756
  function updateClock() {
757
  const now = new Date();
758
  const h = String(now.getHours()).padStart(2, '0');