Lashtw commited on
Commit
145fee8
·
verified ·
1 Parent(s): dcd3732

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +596 -63
index.html CHANGED
@@ -2,60 +2,149 @@
2
  <html lang="zh-TW">
3
  <head>
4
  <meta charset="UTF-8">
5
- <title>班級重要日程倒數</title>
 
6
  <style>
7
- * { box-sizing: border-box; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans TC", sans-serif; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  body {
9
  min-height: 100vh;
10
- background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #0f172a 100%);
 
11
  display: flex;
12
  flex-direction: column;
13
  align-items: center;
14
  justify-content: center;
15
- color: #f8fafc;
16
  padding: 40px 20px;
 
 
 
17
  }
18
- .header {
19
- font-size: 1.8rem;
20
- font-weight: 300;
21
- letter-spacing: 4px;
22
- margin-bottom: 30px;
23
- color: #94a3b8;
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  }
 
 
25
  .grid {
26
- display: grid;
27
- grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
 
28
  gap: 25px;
29
  width: 100%;
30
- max-width: 1100px;
 
31
  }
32
- /* 質感毛玻璃卡片 */
 
33
  .card {
34
- background: rgba(255, 255, 255, 0.05);
35
- border: 1px solid rgba(255, 255, 255, 0.12);
36
- backdrop-filter: blur(16px);
37
- -webkit-backdrop-filter: blur(16px);
38
- border-radius: 20px;
39
- padding: 30px 24px;
 
 
 
 
 
40
  text-align: center;
41
- box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
42
  position: relative;
43
  overflow: hidden;
 
 
 
 
 
 
 
 
 
 
 
 
44
  }
45
- /* 會考等重大目標高亮邊框 */
46
  .card.highlight {
47
- border: 1px solid rgba(245, 158, 11, 0.5);
48
- background: radial-gradient(circle at top, rgba(245, 158, 11, 0.15) 0%, rgba(255, 255, 255, 0.03) 70%);
 
 
 
 
49
  }
 
50
  .card-title {
51
- font-size: 1.3rem;
52
- font-weight: 500;
53
- margin-bottom: 8px;
54
- letter-spacing: 1px;
55
  }
56
  .card-date {
57
  font-size: 0.85rem;
58
- color: #94a3b8;
59
  margin-bottom: 20px;
60
  }
61
  .countdown-box {
@@ -63,63 +152,380 @@
63
  align-items: baseline;
64
  justify-content: center;
65
  gap: 6px;
 
66
  }
67
  .days {
68
- font-size: 4.8rem;
69
- font-weight: 700;
70
  line-height: 1;
71
  letter-spacing: -2px;
72
- color: #ffffff;
73
  font-feature-settings: "tnum";
74
  }
75
  .card.highlight .days {
76
- color: #fbbf24;
77
- text-shadow: 0 0 25px rgba(245, 158, 11, 0.35);
78
  }
79
  .unit {
80
  font-size: 1.2rem;
81
- color: #cbd5e1;
82
- font-weight: 400;
83
  }
84
- .status {
85
- margin-top: 15px;
86
- font-size: 0.85rem;
87
- padding: 4px 12px;
88
  border-radius: 999px;
89
- display: inline-block;
90
  background: rgba(255, 255, 255, 0.08);
91
- color: #cbd5e1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  }
93
  </style>
94
  </head>
95
- <body>
96
 
97
- <div class="header">CLASS SCHEDULE & COUNTDOWN</div>
 
 
 
 
98
 
 
99
  <div class="grid" id="countdownGrid"></div>
100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  <script>
102
- // 依實際需求調整日程(格式:YYYY-MM-DD)
103
- const events = [
104
- { title: "第一次定期評量", date: "2026-10-15", highlight: false },
105
- { title: "校慶運動會", date: "2026-11-20", highlight: false },
106
- { title: "國中教育會考", date: "2027-05-15", highlight: true }
107
- ];
108
-
109
- function updateCountdowns() {
110
- const container = document.getElementById("countdownGrid");
111
- container.innerHTML = "";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
  const today = new Date();
114
  today.setHours(0, 0, 0, 0);
115
 
116
- events.forEach(ev => {
117
  const target = new Date(ev.date + "T00:00:00");
118
- const diffTime = target - today;
119
- const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
120
 
121
  let displayDays = diffDays >= 0 ? diffDays : 0;
122
- let statusText = diffDays > 0 ? "倒數中" : (diffDays === 0 ? "就是今天!" : "已結束");
123
 
124
  const card = document.createElement("div");
125
  card.className = `card ${ev.highlight ? 'highlight' : ''}`;
@@ -130,15 +536,142 @@
130
  <span class="days">${displayDays}</span>
131
  <span class="unit">天</span>
132
  </div>
133
- <div class="status">${statusText}</div>
134
  `;
135
- container.appendChild(card);
136
  });
137
  }
138
 
139
- updateCountdowns();
140
- // 每日跨夜自動更新
141
- setInterval(updateCountdowns, 60000 * 30);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  </script>
143
  </body>
144
  </html>
 
2
  <html lang="zh-TW">
3
  <head>
4
  <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Classroom Focus Display - 班級旗艦倒數看板</title>
7
  <style>
8
+ :root {
9
+ --bg-gradient: radial-gradient(circle at 20% 20%, #1e293b 0%, #0f172a 100%);
10
+ --card-bg: rgba(255, 255, 255, 0.05);
11
+ --card-border: rgba(255, 255, 255, 0.12);
12
+ --card-shadow: 0 20px 40px rgba(0, 0, 0, 0.35);
13
+ --primary-text: #ffffff;
14
+ --secondary-text: #94a3b8;
15
+ --accent-color: #38bdf8;
16
+ --highlight-color: #fbbf24;
17
+ --card-width: 320px;
18
+ --card-padding: 30px;
19
+ --digit-scale: 1;
20
+ --blur-strength: 16px;
21
+ }
22
+
23
+ /* 預設主題風格 */
24
+ body[data-theme="dark-slate"] {
25
+ --bg-gradient: radial-gradient(circle at 15% 15%, #1e293b 0%, #090d16 100%);
26
+ --accent-color: #38bdf8;
27
+ }
28
+ body[data-theme="midnight-purple"] {
29
+ --bg-gradient: radial-gradient(circle at 20% 20%, #2e1065 0%, #0b0217 100%);
30
+ --accent-color: #c084fc;
31
+ }
32
+ body[data-theme="forest-calm"] {
33
+ --bg-gradient: radial-gradient(circle at 20% 20%, #064e3b 0%, #021a14 100%);
34
+ --accent-color: #34d399;
35
+ }
36
+ body[data-theme="minimal-light"] {
37
+ --bg-gradient: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
38
+ --card-bg: rgba(255, 255, 255, 0.7);
39
+ --card-border: rgba(203, 213, 225, 0.8);
40
+ --card-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
41
+ --primary-text: #0f172a;
42
+ --secondary-text: #64748b;
43
+ --accent-color: #2563eb;
44
+ --highlight-color: #d97706;
45
+ --blur-strength: 8px;
46
+ }
47
+
48
+ * {
49
+ box-sizing: border-box;
50
+ margin: 0;
51
+ padding: 0;
52
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans TC", sans-serif;
53
+ }
54
+
55
  body {
56
  min-height: 100vh;
57
+ background: var(--bg-gradient);
58
+ color: var(--primary-text);
59
  display: flex;
60
  flex-direction: column;
61
  align-items: center;
62
  justify-content: center;
 
63
  padding: 40px 20px;
64
+ overflow-x: hidden;
65
+ transition: background 0.4s ease;
66
+ position: relative;
67
  }
68
+
69
+ /* 頂部時鐘與標題 */
70
+ .header-group {
71
+ text-align: center;
72
+ margin-bottom: 40px;
73
+ }
74
+ .current-clock {
75
+ font-size: 2.8rem;
76
+ font-weight: 700;
77
+ letter-spacing: -1px;
78
+ margin-bottom: 4px;
79
+ font-feature-settings: "tnum";
80
+ }
81
+ .current-date {
82
+ font-size: 1rem;
83
+ color: var(--secondary-text);
84
+ font-weight: 400;
85
+ letter-spacing: 2px;
86
+ text-transform: uppercase;
87
  }
88
+
89
+ /* 主展示網格 */
90
  .grid {
91
+ display: flex;
92
+ flex-wrap: wrap;
93
+ justify-content: center;
94
  gap: 25px;
95
  width: 100%;
96
+ max-width: 1400px;
97
+ transition: all 0.3s ease;
98
  }
99
+
100
+ /* 毛玻璃卡片 */
101
  .card {
102
+ width: var(--card-width);
103
+ padding: var(--card-padding);
104
+ background: var(--card-bg);
105
+ border: 1px solid var(--card-border);
106
+ backdrop-filter: blur(var(--blur-strength));
107
+ -webkit-backdrop-filter: blur(var(--blur-strength));
108
+ border-radius: 24px;
109
+ box-shadow: var(--card-shadow);
110
+ display: flex;
111
+ flex-direction: column;
112
+ align-items: center;
113
  text-align: center;
 
114
  position: relative;
115
  overflow: hidden;
116
+ transition: transform 0.25s ease, box-shadow 0.25s ease, width 0.2s ease, padding 0.2s ease;
117
+ }
118
+
119
+ .card::before {
120
+ content: '';
121
+ position: absolute;
122
+ top: 0;
123
+ left: 0;
124
+ right: 0;
125
+ height: 4px;
126
+ background: var(--accent-color);
127
+ opacity: 0.7;
128
  }
129
+
130
  .card.highlight {
131
+ border: 1.5px solid var(--highlight-color);
132
+ box-shadow: 0 15px 35px rgba(251, 191, 36, 0.15);
133
+ }
134
+ .card.highlight::before {
135
+ background: var(--highlight-color);
136
+ height: 6px;
137
  }
138
+
139
  .card-title {
140
+ font-size: 1.25rem;
141
+ font-weight: 600;
142
+ margin-bottom: 6px;
143
+ letter-spacing: 0.5px;
144
  }
145
  .card-date {
146
  font-size: 0.85rem;
147
+ color: var(--secondary-text);
148
  margin-bottom: 20px;
149
  }
150
  .countdown-box {
 
152
  align-items: baseline;
153
  justify-content: center;
154
  gap: 6px;
155
+ margin-bottom: 12px;
156
  }
157
  .days {
158
+ font-size: calc(4.6rem * var(--digit-scale));
159
+ font-weight: 800;
160
  line-height: 1;
161
  letter-spacing: -2px;
 
162
  font-feature-settings: "tnum";
163
  }
164
  .card.highlight .days {
165
+ color: var(--highlight-color);
166
+ text-shadow: 0 0 25px rgba(251, 191, 36, 0.35);
167
  }
168
  .unit {
169
  font-size: 1.2rem;
170
+ color: var(--secondary-text);
171
+ font-weight: 500;
172
  }
173
+ .status-badge {
174
+ font-size: 0.8rem;
175
+ padding: 5px 14px;
 
176
  border-radius: 999px;
 
177
  background: rgba(255, 255, 255, 0.08);
178
+ color: var(--secondary-text);
179
+ font-weight: 500;
180
+ }
181
+ .card.highlight .status-badge {
182
+ background: rgba(251, 191, 36, 0.15);
183
+ color: var(--highlight-color);
184
+ }
185
+
186
+ /* 右下角浮動設定齒輪(極簡隱匿) */
187
+ .settings-trigger {
188
+ position: fixed;
189
+ bottom: 24px;
190
+ right: 24px;
191
+ width: 44px;
192
+ height: 44px;
193
+ border-radius: 50%;
194
+ background: rgba(15, 23, 42, 0.6);
195
+ border: 1px solid rgba(255, 255, 255, 0.15);
196
+ backdrop-filter: blur(8px);
197
+ display: flex;
198
+ align-items: center;
199
+ justify-content: center;
200
+ cursor: pointer;
201
+ color: #94a3b8;
202
+ transition: all 0.2s ease;
203
+ opacity: 0.25;
204
+ z-index: 100;
205
+ }
206
+ .settings-trigger:hover {
207
+ opacity: 1;
208
+ transform: rotate(30deg);
209
+ color: #fff;
210
+ }
211
+
212
+ /* 密碼驗證 Modal */
213
+ .modal-backdrop {
214
+ display: none;
215
+ position: fixed;
216
+ inset: 0;
217
+ background: rgba(0, 0, 0, 0.7);
218
+ backdrop-filter: blur(10px);
219
+ z-index: 1000;
220
+ align-items: center;
221
+ justify-content: center;
222
+ }
223
+ .modal-box {
224
+ background: #1e293b;
225
+ border: 1px solid rgba(255, 255, 255, 0.15);
226
+ border-radius: 16px;
227
+ padding: 30px;
228
+ width: 320px;
229
+ box-shadow: 0 25px 50px rgba(0,0,0,0.5);
230
+ text-align: center;
231
+ }
232
+ .modal-box input {
233
+ width: 100%;
234
+ padding: 12px;
235
+ border-radius: 8px;
236
+ border: 1px solid #334155;
237
+ background: #0f172a;
238
+ color: #fff;
239
+ font-size: 1.1rem;
240
+ text-align: center;
241
+ letter-spacing: 4px;
242
+ margin: 15px 0 20px;
243
+ outline: none;
244
+ }
245
+ .modal-box input:focus {
246
+ border-color: var(--accent-color);
247
+ }
248
+ .btn {
249
+ padding: 10px 20px;
250
+ border-radius: 8px;
251
+ border: none;
252
+ font-weight: 600;
253
+ cursor: pointer;
254
+ transition: opacity 0.2s;
255
+ }
256
+ .btn-primary { background: #38bdf8; color: #0f172a; }
257
+ .btn-secondary { background: #334155; color: #fff; margin-right: 8px; }
258
+ .btn:hover { opacity: 0.9; }
259
+
260
+ /* 後台管理面板 (Drawer) */
261
+ .admin-drawer {
262
+ display: none;
263
+ position: fixed;
264
+ top: 0;
265
+ right: 0;
266
+ width: 600px;
267
+ max-width: 90vw;
268
+ height: 100vh;
269
+ background: #0f172a;
270
+ border-left: 1px solid rgba(255, 255, 255, 0.1);
271
+ box-shadow: -15px 0 40px rgba(0,0,0,0.6);
272
+ z-index: 2000;
273
+ flex-direction: column;
274
+ color: #e2e8f0;
275
+ }
276
+ .admin-header {
277
+ padding: 24px;
278
+ border-bottom: 1px solid #1e293b;
279
+ display: flex;
280
+ justify-content: space-between;
281
+ align-items: center;
282
+ }
283
+ .admin-header h2 { font-size: 1.3rem; font-weight: 600; }
284
+ .admin-body {
285
+ padding: 24px;
286
+ overflow-y: auto;
287
+ flex: 1;
288
+ display: flex;
289
+ flex-direction: column;
290
+ gap: 28px;
291
+ }
292
+
293
+ .config-section {
294
+ background: #1e293b;
295
+ border: 1px solid #334155;
296
+ padding: 20px;
297
+ border-radius: 12px;
298
+ }
299
+ .config-section h3 {
300
+ font-size: 1rem;
301
+ margin-bottom: 15px;
302
+ color: #38bdf8;
303
+ display: flex;
304
+ align-items: center;
305
+ gap: 8px;
306
+ }
307
+ .control-row {
308
+ display: flex;
309
+ justify-content: space-between;
310
+ align-items: center;
311
+ margin-bottom: 12px;
312
+ font-size: 0.9rem;
313
+ }
314
+ .control-row input[type="range"] {
315
+ width: 140px;
316
+ }
317
+
318
+ .event-item {
319
+ background: #090d16;
320
+ border: 1px solid #334155;
321
+ padding: 12px;
322
+ border-radius: 8px;
323
+ margin-bottom: 10px;
324
+ display: grid;
325
+ grid-template-columns: 1.2fr 1fr auto auto;
326
+ gap: 8px;
327
+ align-items: center;
328
+ }
329
+ .event-item input, .event-item select {
330
+ background: #1e293b;
331
+ border: 1px solid #334155;
332
+ color: #fff;
333
+ padding: 6px 8px;
334
+ border-radius: 6px;
335
+ font-size: 0.85rem;
336
+ }
337
+ .btn-danger {
338
+ background: #ef4444;
339
+ color: #fff;
340
+ padding: 6px 10px;
341
+ border-radius: 6px;
342
+ border: none;
343
+ cursor: pointer;
344
+ }
345
+
346
+ /* 抽屜內即時預覽小卡 */
347
+ .preview-canvas {
348
+ background: #090d16;
349
+ border: 1px dashed #334155;
350
+ border-radius: 12px;
351
+ padding: 20px;
352
+ display: flex;
353
+ justify-content: center;
354
+ align-items: center;
355
+ min-height: 180px;
356
+ overflow: hidden;
357
+ }
358
+
359
+ .admin-footer {
360
+ padding: 18px 24px;
361
+ border-top: 1px solid #1e293b;
362
+ display: flex;
363
+ justify-content: flex-end;
364
+ gap: 12px;
365
+ background: #0f172a;
366
  }
367
  </style>
368
  </head>
369
+ <body data-theme="dark-slate">
370
 
371
+ <!-- 頂部即時時鐘與日期 -->
372
+ <div class="header-group">
373
+ <div class="current-clock" id="liveClock">--:--:--</div>
374
+ <div class="current-date" id="liveDate">LOADING CALENDAR...</div>
375
+ </div>
376
 
377
+ <!-- 倒數看板展示區 -->
378
  <div class="grid" id="countdownGrid"></div>
379
 
380
+ <!-- 右下角齒輪 -->
381
+ <div class="settings-trigger" onclick="openAuthModal()" title="開啟管理設定">
382
+ <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
383
+ <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"/>
384
+ </svg>
385
+ </div>
386
+
387
+ <!-- 密碼驗證視窗 -->
388
+ <div class="modal-backdrop" id="authModal">
389
+ <div class="modal-box">
390
+ <h3>管理員驗證</h3>
391
+ <p style="color:#94a3b8; font-size:0.85rem; margin-top:6px;">請輸入通關密碼以編輯日程與外觀</p>
392
+ <input type="password" id="authPassword" placeholder="PIN" maxlength="6" autofocus onkeydown="if(event.key==='Enter') verifyPassword()">
393
+ <div>
394
+ <button class="btn btn-secondary" onclick="closeAuthModal()">取消</button>
395
+ <button class="btn btn-primary" onclick="verifyPassword()">進入設定</button>
396
+ </div>
397
+ </div>
398
+ </div>
399
+
400
+ <!-- 後台側拉管理面板 -->
401
+ <div class="admin-drawer" id="adminDrawer">
402
+ <div class="admin-header">
403
+ <h2>後台控制台 (Preferences)</h2>
404
+ <button class="btn btn-secondary" onclick="closeAdminDrawer()">✕ 關閉</button>
405
+ </div>
406
+
407
+ <div class="admin-body">
408
+ <!-- 區塊1: 即時外觀與排版調整 (含即時預覽) -->
409
+ <div class="config-section">
410
+ <h3>視覺與版面配置 (所見即所得)</h3>
411
+
412
+ <div class="control-row">
413
+ <label>主題色系風格</label>
414
+ <select id="themeSelect" onchange="applyPreviewStyles()" style="background:#090d16; color:#fff; border:1px solid #334155; padding:4px 8px; border-radius:6px;">
415
+ <option value="dark-slate">深邃板岩灰 (Dark Slate)</option>
416
+ <option value="midnight-purple">星夜紫韻 (Midnight Purple)</option>
417
+ <option value="forest-calm">靜謐青綠 (Forest Calm)</option>
418
+ <option value="minimal-light">晨光清透 (Minimal Light)</option>
419
+ </select>
420
+ </div>
421
+
422
+ <div class="control-row">
423
+ <label>卡片寬度 (<span id="widthVal">320px</span>)</label>
424
+ <input type="range" id="cardWidthSlider" min="240" max="460" value="320" oninput="applyPreviewStyles()">
425
+ </div>
426
+
427
+ <div class="control-row">
428
+ <label>數字字體比例 (<span id="digitScaleVal">1.0x</span>)</label>
429
+ <input type="range" id="digitScaleSlider" min="0.7" max="1.4" step="0.05" value="1" oninput="applyPreviewStyles()">
430
+ </div>
431
+
432
+ <div class="control-row">
433
+ <label>內邊距大小 (<span id="paddingVal">30px</span>)</label>
434
+ <input type="range" id="paddingSlider" min="16" max="48" value="30" oninput="applyPreviewStyles()">
435
+ </div>
436
+
437
+ <div style="margin-top:14px; margin-bottom:8px; font-size:0.85rem; color:#94a3b8;">效果預覽 (縮放比例檢視):</div>
438
+ <div class="preview-canvas">
439
+ <div class="card highlight" id="previewCardMock" style="pointer-events:none; transform: scale(0.85);">
440
+ <div class="card-title">國中教育會考</div>
441
+ <div class="card-date">2027.05.15</div>
442
+ <div class="countdown-box">
443
+ <span class="days" id="previewDays">240</span>
444
+ <span class="unit">天</span>
445
+ </div>
446
+ <div class="status-badge">重要目標</div>
447
+ </div>
448
+ </div>
449
+ </div>
450
+
451
+ <!-- 區塊2: 倒數項目管理 -->
452
+ <div class="config-section">
453
+ <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:12px;">
454
+ <h3>倒數項目設定</h3>
455
+ <button class="btn btn-primary" style="font-size:0.8rem; padding:5px 10px;" onclick="addEventRow()">+ 新增日程</button>
456
+ </div>
457
+ <div id="eventListContainer"></div>
458
+ </div>
459
+ </div>
460
+
461
+ <div class="admin-footer">
462
+ <button class="btn btn-secondary" onclick="closeAdminDrawer()">取消</button>
463
+ <button class="btn btn-primary" onclick="saveAndApplyAll()">儲存並發布變更</button>
464
+ </div>
465
+ </div>
466
+
467
  <script>
468
+ // 預設資料
469
+ const DEFAULT_CONFIG = {
470
+ theme: "dark-slate",
471
+ cardWidth: 320,
472
+ digitScale: 1,
473
+ cardPadding: 30,
474
+ events: [
475
+ { id: 1, title: "第一次定期評量", date: "2026-10-15", highlight: false },
476
+ { id: 2, title: "校慶運動會", date: "2026-11-20", highlight: false },
477
+ { id: 3, title: "國中教育會考", date: "2027-05-15", highlight: true }
478
+ ]
479
+ };
480
+
481
+ let state = loadConfig();
482
+
483
+ // 本地快取讀取與儲存
484
+ function loadConfig() {
485
+ const saved = localStorage.getItem("class_countdown_config");
486
+ return saved ? JSON.parse(saved) : DEFAULT_CONFIG;
487
+ }
488
+
489
+ function persistConfig() {
490
+ localStorage.setItem("class_countdown_config", JSON.stringify(state));
491
+ }
492
+
493
+ // 應用樣式變數
494
+ function applyStyles(theme, width, scale, padding) {
495
+ document.body.setAttribute("data-theme", theme);
496
+ document.documentElement.style.setProperty("--card-width", `${width}px`);
497
+ document.documentElement.style.setProperty("--digit-scale", scale);
498
+ document.documentElement.style.setProperty("--card-padding", `${padding}px`);
499
+ }
500
+
501
+ // 更新即時時鐘
502
+ function updateClock() {
503
+ const now = new Date();
504
+ const h = String(now.getHours()).padStart(2, '0');
505
+ const m = String(now.getMinutes()).padStart(2, '0');
506
+ const s = String(now.getSeconds()).padStart(2, '0');
507
+ document.getElementById("liveClock").textContent = `${h}:${m}:${s}`;
508
+
509
+ const options = { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' };
510
+ document.getElementById("liveDate").textContent = now.toLocaleDateString('zh-TW', options);
511
+ }
512
+ setInterval(updateClock, 1000);
513
+ updateClock();
514
+
515
+ // 渲染大屏倒數卡片
516
+ function renderCountdowns() {
517
+ const grid = document.getElementById("countdownGrid");
518
+ grid.innerHTML = "";
519
 
520
  const today = new Date();
521
  today.setHours(0, 0, 0, 0);
522
 
523
+ state.events.forEach(ev => {
524
  const target = new Date(ev.date + "T00:00:00");
525
+ const diffDays = Math.ceil((target - today) / (1000 * 60 * 60 * 24));
 
526
 
527
  let displayDays = diffDays >= 0 ? diffDays : 0;
528
+ let badgeText = diffDays > 0 ? `尚餘 ${diffDays} 天` : (diffDays === 0 ? "🔥 就是今天!" : "已圓滿結束");
529
 
530
  const card = document.createElement("div");
531
  card.className = `card ${ev.highlight ? 'highlight' : ''}`;
 
536
  <span class="days">${displayDays}</span>
537
  <span class="unit">天</span>
538
  </div>
539
+ <div class="status-badge">${badgeText}</div>
540
  `;
541
+ grid.appendChild(card);
542
  });
543
  }
544
 
545
+ // 密碼與 Modal 控制
546
+ function openAuthModal() {
547
+ document.getElementById("authPassword").value = "";
548
+ document.getElementById("authModal").style.display = "flex";
549
+ document.getElementById("authPassword").focus();
550
+ }
551
+ function closeAuthModal() {
552
+ document.getElementById("authModal").style.display = "none";
553
+ }
554
+ function verifyPassword() {
555
+ const pwd = document.getElementById("authPassword").value;
556
+ if (pwd === "902") {
557
+ closeAuthModal();
558
+ openAdminDrawer();
559
+ } else {
560
+ alert("密碼不正確,無法進入管理面板。");
561
+ document.getElementById("authPassword").value = "";
562
+ }
563
+ }
564
+
565
+ // 管理面板 Drawer
566
+ function openAdminDrawer() {
567
+ document.getElementById("themeSelect").value = state.theme;
568
+ document.getElementById("cardWidthSlider").value = state.cardWidth;
569
+ document.getElementById("digitScaleSlider").value = state.digitScale;
570
+ document.getElementById("paddingSlider").value = state.cardPadding;
571
+
572
+ updateSliderLabels();
573
+ renderEventEditorList();
574
+ applyPreviewStyles();
575
+
576
+ document.getElementById("adminDrawer").style.display = "flex";
577
+ }
578
+ function closeAdminDrawer() {
579
+ // 若取消,還原回已儲存的配置樣式
580
+ applyStyles(state.theme, state.cardWidth, state.digitScale, state.cardPadding);
581
+ document.getElementById("adminDrawer").style.display = "none";
582
+ }
583
+
584
+ function updateSliderLabels() {
585
+ document.getElementById("widthVal").textContent = document.getElementById("cardWidthSlider").value + "px";
586
+ document.getElementById("digitScaleVal").textContent = document.getElementById("digitScaleSlider").value + "x";
587
+ document.getElementById("paddingVal").textContent = document.getElementById("paddingSlider").value + "px";
588
+ }
589
+
590
+ // 即時預覽聯動
591
+ function applyPreviewStyles() {
592
+ updateSliderLabels();
593
+ const theme = document.getElementById("themeSelect").value;
594
+ const width = document.getElementById("cardWidthSlider").value;
595
+ const scale = document.getElementById("digitScaleSlider").value;
596
+ const padding = document.getElementById("paddingSlider").value;
597
+
598
+ // 讓後台背景與底層主看板即時隨之更動
599
+ applyStyles(theme, width, scale, padding);
600
+ }
601
+
602
+ // 編輯項目列表渲染
603
+ let editingEvents = [];
604
+ function renderEventEditorList() {
605
+ editingEvents = JSON.parse(JSON.stringify(state.events));
606
+ const container = document.getElementById("eventListContainer");
607
+ container.innerHTML = "";
608
+
609
+ editingEvents.forEach((item, index) => {
610
+ const row = document.createElement("div");
611
+ row.className = "event-item";
612
+ row.innerHTML = `
613
+ <input type="text" value="${item.title}" placeholder="名稱" onchange="editingEvents[${index}].title=this.value">
614
+ <input type="date" value="${item.date}" onchange="editingEvents[${index}].date=this.value">
615
+ <label style="font-size:0.75rem; color:#cbd5e1; display:flex; align-items:center; gap:4px; cursor:pointer;">
616
+ <input type="checkbox" ${item.highlight ? 'checked' : ''} onchange="editingEvents[${index}].highlight=this.checked"> 強調
617
+ </label>
618
+ <button class="btn-danger" onclick="removeEventRow(${index})">刪除</button>
619
+ `;
620
+ container.appendChild(row);
621
+ });
622
+ }
623
+
624
+ function addEventRow() {
625
+ const todayStr = new Date().toISOString().split('T')[0];
626
+ editingEvents.push({
627
+ id: Date.now(),
628
+ title: "新倒數項目",
629
+ date: todayStr,
630
+ highlight: false
631
+ });
632
+ refreshEditorRows();
633
+ }
634
+
635
+ function removeEventRow(idx) {
636
+ editingEvents.splice(idx, 1);
637
+ refreshEditorRows();
638
+ }
639
+
640
+ function refreshEditorRows() {
641
+ const container = document.getElementById("eventListContainer");
642
+ container.innerHTML = "";
643
+ editingEvents.forEach((item, index) => {
644
+ const row = document.createElement("div");
645
+ row.className = "event-item";
646
+ row.innerHTML = `
647
+ <input type="text" value="${item.title}" placeholder="名稱" onchange="editingEvents[${index}].title=this.value">
648
+ <input type="date" value="${item.date}" onchange="editingEvents[${index}].date=this.value">
649
+ <label style="font-size:0.75rem; color:#cbd5e1; display:flex; align-items:center; gap:4px; cursor:pointer;">
650
+ <input type="checkbox" ${item.highlight ? 'checked' : ''} onchange="editingEvents[${index}].highlight=this.checked"> 強調
651
+ </label>
652
+ <button class="btn-danger" onclick="removeEventRow(${index})">刪除</button>
653
+ `;
654
+ container.appendChild(row);
655
+ });
656
+ }
657
+
658
+ // 儲存所有更動
659
+ function saveAndApplyAll() {
660
+ state.theme = document.getElementById("themeSelect").value;
661
+ state.cardWidth = parseInt(document.getElementById("cardWidthSlider").value);
662
+ state.digitScale = parseFloat(document.getElementById("digitScaleSlider").value);
663
+ state.cardPadding = parseInt(document.getElementById("paddingSlider").value);
664
+ state.events = editingEvents;
665
+
666
+ persistConfig();
667
+ applyStyles(state.theme, state.cardWidth, state.digitScale, state.cardPadding);
668
+ renderCountdowns();
669
+ closeAdminDrawer();
670
+ }
671
+
672
+ // 初始化載入
673
+ applyStyles(state.theme, state.cardWidth, state.digitScale, state.cardPadding);
674
+ renderCountdowns();
675
  </script>
676
  </body>
677
  </html>